mirror of
https://github.com/gnustep/libs-base.git
synced 2025-05-30 08:21:25 +00:00
NSKeyValueCoding: Safe-Caching for -[NSObject valueForKey:] (#445)
* KVC Caching Implementation * Do not ignore struct name when comparing type encoding as NSPoint and NSSize have the same layout * Use fast-path when using Objective-C 2 * Guard old ValueForKey function when using the fast-path * Add basic NSKeyValueCoding tests * Update Copyright Years * NSKeyValueCoding+Caching: Add Versioning to IVar Slot * safe_caching: Remove Guards * Add type encoding helper header * Rename geometry structs (NSRect, NSPoint, NSSize) for toll-free bridging with CoreGraphics * Move CG struct definitions to CFCGTypes.h * Update known struct encoding prefixes * Windows 64-bit is LLP64 and not LP64 * Re-order to avoid complier warning --------- Co-authored-by: rfm <richardfrithmacdonald@gmail.com>
This commit is contained in:
parent
3e55c3b296
commit
ab5d06da6a
37 changed files with 1549 additions and 49 deletions
|
@ -48,6 +48,7 @@
|
|||
|
||||
#import "../GSPrivate.h"
|
||||
#import "../GSPThread.h"
|
||||
#import "../typeEncodingHelper.h"
|
||||
|
||||
#include <objc/Protocol.h>
|
||||
|
||||
|
@ -1317,7 +1318,8 @@ GSObjCGetVal(NSObject *self, const char *key, SEL sel,
|
|||
break;
|
||||
|
||||
case _C_STRUCT_B:
|
||||
if (GSSelectorTypesMatch(@encode(NSPoint), type))
|
||||
{
|
||||
if (IS_CGPOINT_ENCODING(type))
|
||||
{
|
||||
NSPoint v;
|
||||
|
||||
|
@ -1334,7 +1336,7 @@ GSObjCGetVal(NSObject *self, const char *key, SEL sel,
|
|||
}
|
||||
val = [NSValue valueWithPoint: v];
|
||||
}
|
||||
else if (GSSelectorTypesMatch(@encode(NSRange), type))
|
||||
else if (IS_NSRANGE_ENCODING(type))
|
||||
{
|
||||
NSRange v;
|
||||
|
||||
|
@ -1351,7 +1353,7 @@ GSObjCGetVal(NSObject *self, const char *key, SEL sel,
|
|||
}
|
||||
val = [NSValue valueWithRange: v];
|
||||
}
|
||||
else if (GSSelectorTypesMatch(@encode(NSRect), type))
|
||||
else if (IS_CGRECT_ENCODING(type))
|
||||
{
|
||||
NSRect v;
|
||||
|
||||
|
@ -1368,7 +1370,7 @@ GSObjCGetVal(NSObject *self, const char *key, SEL sel,
|
|||
}
|
||||
val = [NSValue valueWithRect: v];
|
||||
}
|
||||
else if (GSSelectorTypesMatch(@encode(NSSize), type))
|
||||
else if (IS_CGSIZE_ENCODING(type))
|
||||
{
|
||||
NSSize v;
|
||||
|
||||
|
@ -1410,6 +1412,7 @@ GSObjCGetVal(NSObject *self, const char *key, SEL sel,
|
|||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
#ifdef __GNUSTEP_RUNTIME__
|
||||
|
|
|
@ -359,6 +359,11 @@ NSZone.m \
|
|||
externs.m \
|
||||
objc-load.m
|
||||
|
||||
ifeq ($(OBJC_RUNTIME_LIB), ng)
|
||||
BASE_MFILES += \
|
||||
NSKeyValueCoding+Caching.m
|
||||
endif
|
||||
|
||||
ifneq ($(GNUSTEP_TARGET_OS), mingw32)
|
||||
ifneq ($(GNUSTEP_TARGET_OS), mingw64)
|
||||
ifneq ($(GNUSTEP_TARGET_OS), windows)
|
||||
|
@ -414,6 +419,11 @@ win32-load.h \
|
|||
NSCallBacks.h \
|
||||
tzfile.h
|
||||
|
||||
# Definitions for toll-free bridging of known structures
|
||||
# such as NSRect, NSPoint, or NSSize.
|
||||
COREFOUNDATION_HEADERS = \
|
||||
CFCGTypes.h
|
||||
|
||||
FOUNDATION_HEADERS = \
|
||||
Foundation.h \
|
||||
FoundationErrors.h \
|
||||
|
@ -586,7 +596,8 @@ NSZone.h
|
|||
HEADERS_INSTALL = \
|
||||
$(OBJECTIVEC2_HEADERS) \
|
||||
$(GNUSTEPBASE_HEADERS) \
|
||||
$(FOUNDATION_HEADERS)
|
||||
$(FOUNDATION_HEADERS) \
|
||||
$(COREFOUNDATION_HEADERS)
|
||||
|
||||
GENERATED_HFILES = \
|
||||
dynamic-load.h \
|
||||
|
|
|
@ -56,6 +56,11 @@ after-install::
|
|||
done
|
||||
endif
|
||||
after-install::
|
||||
$(MKDIRS) $(GNUSTEP_HEADERS)/CoreFoundation
|
||||
for file in $(COREFOUNDATION_HEADERS); do \
|
||||
$(INSTALL_DATA) ../Headers/CoreFoundation/$$file \
|
||||
$(GNUSTEP_HEADERS)/CoreFoundation/$$file ; \
|
||||
done
|
||||
$(MKDIRS) $(GNUSTEP_HEADERS)/GNUstepBase
|
||||
for file in $(GNUSTEPBASE_HEADERS); do \
|
||||
$(INSTALL_DATA) ../Headers/GNUstepBase/$$file \
|
||||
|
|
55
Source/NSKeyValueCoding+Caching.h
Normal file
55
Source/NSKeyValueCoding+Caching.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/** Key-Value Coding Safe Caching Support
|
||||
Copyright (C) 2024 Free Software Foundation, Inc.
|
||||
|
||||
Written by: Hugo Melder <hugo@algoriddim.com>
|
||||
Created: August 2024
|
||||
|
||||
This file is part of the GNUstep Base Library.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* It turns out that valueForKey: is a very expensive operation, and a major
|
||||
* bottleneck for Key-Value Observing and other operations such as sorting
|
||||
* an array by key.
|
||||
*
|
||||
* The accessor search patterns for Key-Value observing are discussed in the
|
||||
* Apple Key-Value Coding Programming Guide. The return value may be
|
||||
* encapuslated into an NSNumber or NSValue object, depending on the Objective-C
|
||||
* type encoding of the return value. This means that once valueForKey: found an
|
||||
* existing accessor, the Objective-C type encoding of the accessor is
|
||||
* retrieved. We then go through a huge switch case to determine the right way
|
||||
* to invoke the IMP and potentially encapsulate the return type. The resulting
|
||||
* object is then returned.
|
||||
* The algorithm for setValue:ForKey: is similar.
|
||||
*
|
||||
* We can speed this up by caching the IMP of the accessor in a hash table.
|
||||
* However, without proper versioning, this quickly becomes very dangerous.
|
||||
* The user might exchange implementations, or add new ones expecting the
|
||||
* search pattern invariant to still hold. If we clamp onto an IMP, this
|
||||
* invariant no longer holds.
|
||||
*
|
||||
* We will make use of libobjc2's safe caching to avoid this.
|
||||
*
|
||||
* Note that the caching is opaque. You will only need to redirect all
|
||||
* valueForKey: calls to the function below.
|
||||
*/
|
||||
|
||||
#import "Foundation/NSString.h"
|
||||
|
||||
id
|
||||
valueForKeyWithCaching(id obj, NSString *aKey);
|
639
Source/NSKeyValueCoding+Caching.m
Normal file
639
Source/NSKeyValueCoding+Caching.m
Normal file
|
@ -0,0 +1,639 @@
|
|||
/** Key-Value Coding Safe Caching Support.
|
||||
Copyright (C) 2024 Free Software Foundation, Inc.
|
||||
|
||||
Written by: Hugo Melder <hugo@algoriddim.com>
|
||||
Created: August 2024
|
||||
|
||||
This file is part of the GNUstep Base Library.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110 USA.
|
||||
*/
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/encoding.h>
|
||||
#import <objc/slot.h>
|
||||
|
||||
#import "common.h" // for likely and unlikely
|
||||
#import "typeEncodingHelper.h"
|
||||
#import "Foundation/NSKeyValueCoding.h"
|
||||
#import "Foundation/NSMethodSignature.h"
|
||||
#import "Foundation/NSValue.h"
|
||||
#import "Foundation/NSInvocation.h"
|
||||
#import "NSKeyValueCoding+Caching.h"
|
||||
|
||||
|
||||
struct _KVCCacheSlot
|
||||
{
|
||||
Class cls;
|
||||
SEL selector;
|
||||
const char *types;
|
||||
uintptr_t hash;
|
||||
// The slot version returned by objc_get_slot2.
|
||||
// Set to zero when this is caching an ivar lookup
|
||||
uint64_t version;
|
||||
// If selector is zero, we cache the ivar offset,
|
||||
// otherwise the IMP of the accessor.
|
||||
// Use the corresponding get functions below.
|
||||
union
|
||||
{
|
||||
IMP imp;
|
||||
intptr_t offset;
|
||||
// Just for readability when checking for emptyness
|
||||
intptr_t contents;
|
||||
};
|
||||
id (*get)(struct _KVCCacheSlot *, id);
|
||||
};
|
||||
|
||||
static inline uintptr_t
|
||||
_KVCCacheSlotHash(const void *ptr)
|
||||
{
|
||||
struct _KVCCacheSlot *a = (struct _KVCCacheSlot *) ptr;
|
||||
return (uintptr_t) a->cls ^ (uintptr_t) a->hash;
|
||||
}
|
||||
|
||||
static inline BOOL
|
||||
_KVCCacheSlotEqual(const void *ptr1, const void *ptr2)
|
||||
{
|
||||
struct _KVCCacheSlot *a = (struct _KVCCacheSlot *) ptr1;
|
||||
struct _KVCCacheSlot *b = (struct _KVCCacheSlot *) ptr2;
|
||||
|
||||
return a->cls == b->cls && a->hash == b->hash;
|
||||
}
|
||||
|
||||
void inline _KVCCacheSlotRelease(const void *ptr)
|
||||
{
|
||||
free((struct _KVCCacheSlot *) ptr);
|
||||
}
|
||||
|
||||
// We only need a hash table not a map
|
||||
#define GSI_MAP_HAS_VALUE 0
|
||||
#define GSI_MAP_RETAIN_KEY(M, X)
|
||||
#define GSI_MAP_RELEASE_KEY(M, X) (_KVCCacheSlotRelease(X.ptr))
|
||||
#define GSI_MAP_HASH(M, X) (_KVCCacheSlotHash(X.ptr))
|
||||
#define GSI_MAP_EQUAL(M, X, Y) (_KVCCacheSlotEqual(X.ptr, Y.ptr))
|
||||
#define GSI_MAP_KTYPES GSUNION_PTR
|
||||
#import "GNUstepBase/GSIMap.h"
|
||||
#import "GSPThread.h"
|
||||
|
||||
/*
|
||||
* Templating for poor people:
|
||||
* We need to call IMP with the correct function signature and box
|
||||
* the return value accordingly.
|
||||
*/
|
||||
#define KVC_CACHE_FUNC(_type, _fnname, _cls, _meth) \
|
||||
static id _fnname(struct _KVCCacheSlot *slot, id obj) \
|
||||
{ \
|
||||
_type val = ((_type(*)(id, SEL)) slot->imp)(obj, slot->selector); \
|
||||
return [_cls _meth:val]; \
|
||||
}
|
||||
#define KVC_CACHE_IVAR_FUNC(_type, _fnname, _cls, _meth) \
|
||||
static id _fnname##ForIvar(struct _KVCCacheSlot *slot, id obj) \
|
||||
{ \
|
||||
_type val = *(_type *) ((char *) obj + slot->offset); \
|
||||
return [_cls _meth:val]; \
|
||||
}
|
||||
|
||||
#define CACHE_NSNUMBER_GEN_FUNCS(_type, _fnname, _numberMethName) \
|
||||
KVC_CACHE_FUNC(_type, _fnname, NSNumber, numberWith##_numberMethName) \
|
||||
KVC_CACHE_IVAR_FUNC(_type, _fnname, NSNumber, numberWith##_numberMethName)
|
||||
|
||||
#define CACHE_NSVALUE_GEN_FUNCS(_type, _fnname, _valueMethName) \
|
||||
KVC_CACHE_FUNC(_type, _fnname, NSValue, valueWith##_valueMethName) \
|
||||
KVC_CACHE_IVAR_FUNC(_type, _fnname, NSValue, valueWith##_valueMethName)
|
||||
|
||||
// Ignore the alignment warning when casting the obj + offset address
|
||||
// to the proper type.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wcast-align"
|
||||
|
||||
CACHE_NSNUMBER_GEN_FUNCS(char, _getBoxedChar, Char);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(int, _getBoxedInt, Int);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(short, _getBoxedShort, Short);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(long, _getBoxedLong, Long);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(long long, _getBoxedLongLong, LongLong);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(unsigned char, _getBoxedUnsignedChar, UnsignedChar);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(unsigned int, _getBoxedUnsignedInt, UnsignedInt);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(unsigned short, _getBoxedUnsignedShort, UnsignedShort);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(unsigned long, _getBoxedUnsignedLong, UnsignedLong);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(unsigned long long, _getBoxedUnsignedLongLong,
|
||||
UnsignedLongLong);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(float, _getBoxedFloat, Float);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(double, _getBoxedDouble, Double);
|
||||
CACHE_NSNUMBER_GEN_FUNCS(bool, _getBoxedBool, Bool);
|
||||
|
||||
CACHE_NSVALUE_GEN_FUNCS(NSPoint, _getBoxedNSPoint, Point);
|
||||
CACHE_NSVALUE_GEN_FUNCS(NSRange, _getBoxedNSRange, Range);
|
||||
CACHE_NSVALUE_GEN_FUNCS(NSRect, _getBoxedNSRect, Rect);
|
||||
CACHE_NSVALUE_GEN_FUNCS(NSSize, _getBoxedNSSize, Size);
|
||||
|
||||
static id
|
||||
_getBoxedId(struct _KVCCacheSlot *slot, id obj)
|
||||
{
|
||||
id val = ((id(*)(id, SEL)) slot->imp)(obj, slot->selector);
|
||||
return val;
|
||||
}
|
||||
static id
|
||||
_getBoxedIdForIvar(struct _KVCCacheSlot *slot, id obj)
|
||||
{
|
||||
id val = *(id *) ((char *) obj + slot->offset);
|
||||
return val;
|
||||
}
|
||||
static id
|
||||
_getBoxedClass(struct _KVCCacheSlot *slot, id obj)
|
||||
{
|
||||
Class val = ((Class(*)(id, SEL)) slot->imp)(obj, slot->selector);
|
||||
return val;
|
||||
}
|
||||
static id
|
||||
_getBoxedClassForIvar(struct _KVCCacheSlot *slot, id obj)
|
||||
{
|
||||
Class val = *(Class *) ((char *) obj + slot->offset);
|
||||
return val;
|
||||
}
|
||||
|
||||
// TODO: This can be optimised and is still very expensive
|
||||
static id
|
||||
_getBoxedStruct(struct _KVCCacheSlot *slot, id obj)
|
||||
{
|
||||
NSInvocation *inv;
|
||||
const char *types = slot->types;
|
||||
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes: types];
|
||||
size_t retSize = [sig methodReturnLength];
|
||||
char ret[retSize];
|
||||
|
||||
inv = [NSInvocation invocationWithMethodSignature: sig];
|
||||
[inv setSelector: slot->selector];
|
||||
[inv invokeWithTarget: obj];
|
||||
[inv getReturnValue: ret];
|
||||
|
||||
return [NSValue valueWithBytes:ret objCType:[sig methodReturnType]];
|
||||
}
|
||||
static id
|
||||
_getBoxedStructForIvar(struct _KVCCacheSlot *slot, id obj)
|
||||
{
|
||||
const char *end = objc_skip_typespec(slot->types);
|
||||
size_t length = end - slot->types;
|
||||
char returnType[length + 1];
|
||||
memcpy(returnType, slot->types, length);
|
||||
returnType[length] = '\0';
|
||||
|
||||
return [NSValue valueWithBytes:((char *) obj + slot->offset)
|
||||
objCType:returnType];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
static struct _KVCCacheSlot
|
||||
_getBoxedBlockForIVar(NSString *key, Ivar ivar)
|
||||
{
|
||||
const char *encoding = ivar_getTypeEncoding(ivar);
|
||||
struct _KVCCacheSlot slot = {};
|
||||
// Return a zeroed out slot. It is the caller's responsibility to call
|
||||
// valueForUndefinedKey:
|
||||
if (unlikely(encoding == NULL))
|
||||
{
|
||||
return slot;
|
||||
}
|
||||
|
||||
slot.offset = ivar_getOffset(ivar);
|
||||
slot.types = encoding;
|
||||
// Get the current objc_method_cache_version as we do not explicitly
|
||||
// request a new slot when looking up ivars.
|
||||
slot.version = objc_method_cache_version;
|
||||
|
||||
switch (encoding[0])
|
||||
{
|
||||
case '@': {
|
||||
slot.get = _getBoxedIdForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'B': {
|
||||
slot.get = _getBoxedBoolForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'l': {
|
||||
slot.get = _getBoxedLongForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'f': {
|
||||
slot.get = _getBoxedFloatForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'd': {
|
||||
slot.get = _getBoxedDoubleForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'i': {
|
||||
slot.get = _getBoxedIntForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'I': {
|
||||
slot.get = _getBoxedUnsignedIntForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'L': {
|
||||
slot.get = _getBoxedUnsignedLongForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'q': {
|
||||
slot.get = _getBoxedLongLongForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'Q': {
|
||||
slot.get = _getBoxedUnsignedLongLongForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'c': {
|
||||
slot.get = _getBoxedCharForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 's': {
|
||||
slot.get = _getBoxedShortForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'C': {
|
||||
slot.get = _getBoxedUnsignedCharForIvar;
|
||||
return slot;
|
||||
}
|
||||
case 'S': {
|
||||
slot.get = _getBoxedUnsignedShortForIvar;
|
||||
return slot;
|
||||
}
|
||||
case '#': {
|
||||
slot.get = _getBoxedClassForIvar;
|
||||
return slot;
|
||||
}
|
||||
case '{': {
|
||||
if (IS_NSRANGE_ENCODING(encoding))
|
||||
{
|
||||
slot.get = _getBoxedNSRangeForIvar;
|
||||
return slot;
|
||||
}
|
||||
else if (IS_CGRECT_ENCODING(encoding))
|
||||
{
|
||||
slot.get = _getBoxedNSRectForIvar;
|
||||
return slot;
|
||||
}
|
||||
else if (IS_CGPOINT_ENCODING(encoding))
|
||||
{
|
||||
slot.get = _getBoxedNSPointForIvar;
|
||||
return slot;
|
||||
}
|
||||
else if (IS_CGSIZE_ENCODING(encoding))
|
||||
{
|
||||
slot.get = _getBoxedNSSizeForIvar;
|
||||
return slot;
|
||||
}
|
||||
|
||||
slot.get = _getBoxedStructForIvar;
|
||||
return slot;
|
||||
}
|
||||
default:
|
||||
slot.contents = 0;
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
static struct _KVCCacheSlot
|
||||
_getBoxedBlockForMethod(NSString *key, Method method, SEL sel, uint64_t version)
|
||||
{
|
||||
const char *encoding = method_getTypeEncoding(method);
|
||||
struct _KVCCacheSlot slot = {};
|
||||
if (unlikely(encoding == NULL))
|
||||
{
|
||||
// Return a zeroed out slot. It is the caller's responsibility to call
|
||||
// valueForUndefinedKey: or parse unknown structs (when type encoding
|
||||
// starts with '{')
|
||||
return slot;
|
||||
}
|
||||
|
||||
slot.imp = method_getImplementation(method);
|
||||
slot.selector = sel;
|
||||
slot.types = encoding;
|
||||
slot.version = version;
|
||||
|
||||
// TODO: Move most commonly used types up the switch statement
|
||||
switch (encoding[0])
|
||||
{
|
||||
case '@': {
|
||||
slot.get = _getBoxedId;
|
||||
return slot;
|
||||
}
|
||||
case 'B': {
|
||||
slot.get = _getBoxedBool;
|
||||
return slot;
|
||||
}
|
||||
case 'l': {
|
||||
slot.get = _getBoxedLong;
|
||||
return slot;
|
||||
}
|
||||
case 'f': {
|
||||
slot.get = _getBoxedFloat;
|
||||
return slot;
|
||||
}
|
||||
case 'd': {
|
||||
slot.get = _getBoxedDouble;
|
||||
return slot;
|
||||
}
|
||||
case 'i': {
|
||||
slot.get = _getBoxedInt;
|
||||
return slot;
|
||||
}
|
||||
case 'I': {
|
||||
slot.get = _getBoxedUnsignedInt;
|
||||
return slot;
|
||||
}
|
||||
case 'L': {
|
||||
slot.get = _getBoxedUnsignedLong;
|
||||
return slot;
|
||||
}
|
||||
case 'q': {
|
||||
slot.get = _getBoxedLongLong;
|
||||
return slot;
|
||||
}
|
||||
case 'Q': {
|
||||
slot.get = _getBoxedUnsignedLongLong;
|
||||
return slot;
|
||||
}
|
||||
case 'c': {
|
||||
slot.get = _getBoxedChar;
|
||||
return slot;
|
||||
}
|
||||
case 's': {
|
||||
slot.get = _getBoxedShort;
|
||||
return slot;
|
||||
}
|
||||
case 'C': {
|
||||
slot.get = _getBoxedUnsignedChar;
|
||||
return slot;
|
||||
}
|
||||
case 'S': {
|
||||
slot.get = _getBoxedUnsignedShort;
|
||||
return slot;
|
||||
}
|
||||
case '#': {
|
||||
slot.get = _getBoxedClass;
|
||||
return slot;
|
||||
}
|
||||
case '{': {
|
||||
if (IS_NSRANGE_ENCODING(encoding))
|
||||
{
|
||||
slot.get = _getBoxedNSRange;
|
||||
return slot;
|
||||
}
|
||||
else if (IS_CGRECT_ENCODING(encoding))
|
||||
{
|
||||
slot.get = _getBoxedNSRect;
|
||||
return slot;
|
||||
}
|
||||
else if (IS_CGPOINT_ENCODING(encoding))
|
||||
{
|
||||
slot.get = _getBoxedNSPoint;
|
||||
return slot;
|
||||
}
|
||||
else if (IS_CGSIZE_ENCODING(encoding))
|
||||
{
|
||||
slot.get = _getBoxedNSSize;
|
||||
return slot;
|
||||
}
|
||||
|
||||
slot.get = _getBoxedStruct;
|
||||
return slot;
|
||||
}
|
||||
default:
|
||||
slot.contents = 0;
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
// libobjc2 does not offer an API for recursively looking up a slot with
|
||||
// just the class and a selector.
|
||||
// The behaviour of this function is similar to that of class_getInstanceMethod
|
||||
// and recurses into the super classes. Additionally, we ask the class, if it
|
||||
// can resolve the instance method dynamically by calling -[NSObject
|
||||
// resolveInstanceMethod:].
|
||||
//
|
||||
// objc_slot2 has the same struct layout as objc_method.
|
||||
Method _Nullable _class_getMethodRecursive(Class aClass, SEL aSelector,
|
||||
uint64_t *version)
|
||||
{
|
||||
struct objc_slot2 *slot;
|
||||
|
||||
if (0 == aClass)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (0 == aSelector)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Do a dtable lookup to find out which class the method comes from.
|
||||
slot = objc_get_slot2(aClass, aSelector, version);
|
||||
if (NULL != slot)
|
||||
{
|
||||
return (Method) slot;
|
||||
}
|
||||
|
||||
// Ask if class is able to dynamically register this method
|
||||
if ([aClass resolveInstanceMethod:aSelector])
|
||||
{
|
||||
return (Method) _class_getMethodRecursive(aClass, aSelector, version);
|
||||
}
|
||||
|
||||
// Recurse into super classes
|
||||
return (Method) _class_getMethodRecursive(class_getSuperclass(aClass),
|
||||
aSelector, version);
|
||||
}
|
||||
|
||||
static struct _KVCCacheSlot
|
||||
ValueForKeyLookup(Class cls, NSObject *self, NSString *boxedKey,
|
||||
const char *key, unsigned size)
|
||||
{
|
||||
const char *name;
|
||||
char buf[size + 5];
|
||||
char lo;
|
||||
char hi;
|
||||
SEL sel = 0;
|
||||
Method meth = NULL;
|
||||
uint64_t version = 0;
|
||||
struct _KVCCacheSlot slot = {};
|
||||
|
||||
if (unlikely(size == 0))
|
||||
{
|
||||
return slot;
|
||||
}
|
||||
|
||||
memcpy(buf, "_get", 4);
|
||||
memcpy(&buf[4], key, size);
|
||||
buf[size + 4] = '\0';
|
||||
lo = buf[4];
|
||||
hi = islower(lo) ? toupper(lo) : lo;
|
||||
buf[4] = hi;
|
||||
|
||||
// 1.1 Check if the _get<key> accessor method exists
|
||||
name = &buf[1]; // getKey
|
||||
sel = sel_registerName(name);
|
||||
if ((meth = _class_getMethodRecursive(cls, sel, &version)) != NULL)
|
||||
{
|
||||
return _getBoxedBlockForMethod(boxedKey, meth, sel, version);
|
||||
}
|
||||
|
||||
// 1.2 Check if the <key> accessor method exists
|
||||
buf[4] = lo;
|
||||
name = &buf[4]; // key
|
||||
sel = sel_registerName(name);
|
||||
if ((meth = _class_getMethodRecursive(cls, sel, &version)) != NULL)
|
||||
{
|
||||
return _getBoxedBlockForMethod(boxedKey, meth, sel, version);
|
||||
}
|
||||
|
||||
// 1.3 Check if the is<key> accessor method exists
|
||||
buf[2] = 'i';
|
||||
buf[3] = 's';
|
||||
buf[4] = hi;
|
||||
name = &buf[2]; // isKey
|
||||
sel = sel_registerName(name);
|
||||
if ((meth = _class_getMethodRecursive(cls, sel, &version)) != NULL)
|
||||
{
|
||||
return _getBoxedBlockForMethod(boxedKey, meth, sel, version);
|
||||
}
|
||||
|
||||
// 1.4 Check if the _<key> accessor method exists. Otherwise check
|
||||
// if we are allowed to access the instance variables directly.
|
||||
buf[3] = '_';
|
||||
buf[4] = lo;
|
||||
name = &buf[3]; // _key
|
||||
sel = sel_registerName(name);
|
||||
if ((meth = _class_getMethodRecursive(cls, sel, &version)) != NULL)
|
||||
{
|
||||
return _getBoxedBlockForMethod(boxedKey, meth, sel, version);
|
||||
}
|
||||
|
||||
// Step 2. and 3. (NSArray and NSSet accessors) are implemented
|
||||
// in the respective classes.
|
||||
|
||||
// 4. Last try: Ivar access
|
||||
if ([cls accessInstanceVariablesDirectly] == YES)
|
||||
{
|
||||
// 4.1 Check if the _<key> ivar exists
|
||||
Ivar ivar = class_getInstanceVariable(cls, name);
|
||||
if (ivar != NULL)
|
||||
{ // _key
|
||||
return _getBoxedBlockForIVar(boxedKey, ivar);
|
||||
}
|
||||
|
||||
// 4.2 Check if the _is<Key> ivar exists
|
||||
buf[1] = '_';
|
||||
buf[2] = 'i';
|
||||
buf[3] = 's';
|
||||
buf[4] = hi;
|
||||
name = &buf[1]; // _isKey
|
||||
ivar = class_getInstanceVariable(cls, name);
|
||||
if (ivar != NULL)
|
||||
{
|
||||
return _getBoxedBlockForIVar(boxedKey, ivar);
|
||||
}
|
||||
|
||||
// 4.3 Check if the <key> ivar exists
|
||||
buf[4] = lo;
|
||||
name = &buf[4]; // key
|
||||
ivar = class_getInstanceVariable(cls, name);
|
||||
if (ivar != NULL)
|
||||
{
|
||||
return _getBoxedBlockForIVar(boxedKey, ivar);
|
||||
}
|
||||
|
||||
// 4.4 Check if the is<Key> ivar exists
|
||||
buf[4] = hi;
|
||||
name = &buf[2]; // isKey
|
||||
ivar = class_getInstanceVariable(cls, name);
|
||||
if (ivar != NULL)
|
||||
{
|
||||
return _getBoxedBlockForIVar(boxedKey, ivar);
|
||||
}
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
id
|
||||
valueForKeyWithCaching(id obj, NSString *aKey)
|
||||
{
|
||||
struct _KVCCacheSlot *cachedSlot = NULL;
|
||||
GSIMapNode node = NULL;
|
||||
static GSIMapTable_t cacheTable = {};
|
||||
static gs_mutex_t cacheTableLock = GS_MUTEX_INIT_STATIC;
|
||||
|
||||
Class cls = object_getClass(obj);
|
||||
// Fill out the required fields for hashing
|
||||
struct _KVCCacheSlot slot = {.cls = cls, .hash = [aKey hash]};
|
||||
|
||||
GS_MUTEX_LOCK(cacheTableLock);
|
||||
if (cacheTable.zone == 0)
|
||||
{
|
||||
// TODO: Tweak initial capacity
|
||||
GSIMapInitWithZoneAndCapacity(&cacheTable, NSDefaultMallocZone(), 64);
|
||||
}
|
||||
node = GSIMapNodeForKey(&cacheTable, (GSIMapKey) (void *) &slot);
|
||||
GS_MUTEX_UNLOCK(cacheTableLock);
|
||||
|
||||
if (node == NULL)
|
||||
{
|
||||
// Lookup the getter
|
||||
slot
|
||||
= ValueForKeyLookup(cls, obj, aKey, [aKey UTF8String], [aKey length]);
|
||||
if (slot.contents != 0)
|
||||
{
|
||||
slot.cls = cls;
|
||||
slot.hash = [aKey hash];
|
||||
|
||||
// Copy slot to heap
|
||||
cachedSlot
|
||||
= (struct _KVCCacheSlot *) malloc(sizeof(struct _KVCCacheSlot));
|
||||
memcpy(cachedSlot, &slot, sizeof(struct _KVCCacheSlot));
|
||||
|
||||
GS_MUTEX_LOCK(cacheTableLock);
|
||||
node = GSIMapAddKey(&cacheTable, (GSIMapKey) (void *) cachedSlot);
|
||||
GS_MUTEX_UNLOCK(cacheTableLock);
|
||||
}
|
||||
else
|
||||
{
|
||||
return [obj valueForUndefinedKey:aKey];
|
||||
}
|
||||
}
|
||||
cachedSlot = node->key.ptr;
|
||||
|
||||
// Check if a new method was registered. If this is the case,
|
||||
// the objc_method_cache_version was incremented and we need to update the
|
||||
// cache.
|
||||
if (objc_method_cache_version != cachedSlot->version)
|
||||
{
|
||||
// Lookup the getter
|
||||
// TODO: We can optimise this by supplying a hint (return type etc.)
|
||||
// as it is unlikely, that the return type has changed.
|
||||
slot
|
||||
= ValueForKeyLookup(cls, obj, aKey, [aKey UTF8String], [aKey length]);
|
||||
|
||||
// Update entry
|
||||
GS_MUTEX_LOCK(cacheTableLock);
|
||||
memcpy(cachedSlot, &slot, sizeof(struct _KVCCacheSlot));
|
||||
GS_MUTEX_UNLOCK(cacheTableLock);
|
||||
}
|
||||
|
||||
return cachedSlot->get(cachedSlot, obj);
|
||||
}
|
|
@ -41,6 +41,9 @@
|
|||
#include "NSKeyValueMutableArray.m"
|
||||
#include "NSKeyValueMutableSet.m"
|
||||
|
||||
#if defined(__OBJC2__)
|
||||
#import "NSKeyValueCoding+Caching.h"
|
||||
#endif
|
||||
|
||||
/* this should move into autoconf once it's accepted */
|
||||
#define WANT_DEPRECATED_KVC_COMPAT 1
|
||||
|
@ -144,6 +147,7 @@ SetValueForKey(NSObject *self, id anObject, const char *key, unsigned size)
|
|||
GSObjCSetVal(self, key, anObject, sel, type, size, off);
|
||||
}
|
||||
|
||||
#if !defined(__OBJC2__)
|
||||
static id ValueForKey(NSObject *self, const char *key, unsigned size)
|
||||
{
|
||||
SEL sel = 0;
|
||||
|
@ -166,12 +170,12 @@ static id ValueForKey(NSObject *self, const char *key, unsigned size)
|
|||
|
||||
name = &buf[1]; // getKey
|
||||
sel = sel_getUid(name);
|
||||
if (sel == 0 || [self respondsToSelector: sel] == NO)
|
||||
if ([self respondsToSelector: sel] == NO)
|
||||
{
|
||||
buf[4] = lo;
|
||||
name = &buf[4]; // key
|
||||
sel = sel_getUid(name);
|
||||
if (sel == 0 || [self respondsToSelector: sel] == NO)
|
||||
if ([self respondsToSelector: sel] == NO)
|
||||
{
|
||||
buf[4] = hi;
|
||||
buf[3] = 's';
|
||||
|
@ -190,13 +194,13 @@ static id ValueForKey(NSObject *self, const char *key, unsigned size)
|
|||
buf[4] = hi;
|
||||
name = buf; // _getKey
|
||||
sel = sel_getUid(name);
|
||||
if (sel == 0 || [self respondsToSelector: sel] == NO)
|
||||
if ([self respondsToSelector: sel] == NO)
|
||||
{
|
||||
buf[4] = lo;
|
||||
buf[3] = '_';
|
||||
name = &buf[3]; // _key
|
||||
sel = sel_getUid(name);
|
||||
if (sel == 0 || [self respondsToSelector: sel] == NO)
|
||||
if ([self respondsToSelector: sel] == NO)
|
||||
{
|
||||
sel = 0;
|
||||
}
|
||||
|
@ -229,6 +233,7 @@ static id ValueForKey(NSObject *self, const char *key, unsigned size)
|
|||
}
|
||||
return GSObjCGetVal(self, key, sel, type, size, off);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@implementation NSObject(KeyValueCoding)
|
||||
|
@ -513,6 +518,9 @@ static id ValueForKey(NSObject *self, const char *key, unsigned size)
|
|||
|
||||
- (id) valueForKey: (NSString*)aKey
|
||||
{
|
||||
#if defined(__OBJC2__)
|
||||
return valueForKeyWithCaching(self, aKey);
|
||||
#else
|
||||
unsigned size = [aKey length] * 8;
|
||||
char key[size + 1];
|
||||
|
||||
|
@ -521,6 +529,7 @@ static id ValueForKey(NSObject *self, const char *key, unsigned size)
|
|||
encoding: NSUTF8StringEncoding];
|
||||
size = strlen(key);
|
||||
return ValueForKey(self, key, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
*/
|
||||
|
||||
#import "common.h"
|
||||
#import "typeEncodingHelper.h"
|
||||
#import "Foundation/NSValue.h"
|
||||
#import "Foundation/NSCoder.h"
|
||||
#import "Foundation/NSDictionary.h"
|
||||
|
@ -411,28 +412,28 @@ static NSLock *placeholderLock;
|
|||
size = strlen(objctype)+1;
|
||||
[coder encodeValueOfObjCType: @encode(unsigned) at: &size];
|
||||
[coder encodeArrayOfObjCType: @encode(signed char) count: size at: objctype];
|
||||
if (strncmp("{_NSSize=", objctype, 9) == 0)
|
||||
if (strncmp(CGSIZE_ENCODING_PREFIX, objctype, strlen(CGSIZE_ENCODING_PREFIX)) == 0)
|
||||
{
|
||||
NSSize v = [self sizeValue];
|
||||
|
||||
[coder encodeValueOfObjCType: objctype at: &v];
|
||||
return;
|
||||
}
|
||||
else if (strncmp("{_NSPoint=", objctype, 10) == 0)
|
||||
else if (strncmp(CGPOINT_ENCODING_PREFIX, objctype, strlen(CGPOINT_ENCODING_PREFIX)) == 0)
|
||||
{
|
||||
NSPoint v = [self pointValue];
|
||||
|
||||
[coder encodeValueOfObjCType: objctype at: &v];
|
||||
return;
|
||||
}
|
||||
else if (strncmp("{_NSRect=", objctype, 9) == 0)
|
||||
else if (strncmp(CGRECT_ENCODING_PREFIX, objctype, strlen(CGRECT_ENCODING_PREFIX)) == 0)
|
||||
{
|
||||
NSRect v = [self rectValue];
|
||||
|
||||
[coder encodeValueOfObjCType: objctype at: &v];
|
||||
return;
|
||||
}
|
||||
else if (strncmp("{_NSRange=", objctype, 10) == 0)
|
||||
else if (strncmp(NSRANGE_ENCODING_PREFIX, objctype, strlen(NSRANGE_ENCODING_PREFIX)) == 0)
|
||||
{
|
||||
NSRange v = [self rangeValue];
|
||||
|
||||
|
@ -480,13 +481,13 @@ static NSLock *placeholderLock;
|
|||
[coder decodeArrayOfObjCType: @encode(signed char)
|
||||
count: size
|
||||
at: (void*)objctype];
|
||||
if (strncmp("{_NSSize=", objctype, 9) == 0)
|
||||
if (strncmp(CGSIZE_ENCODING_PREFIX, objctype, strlen(CGSIZE_ENCODING_PREFIX)) == 0)
|
||||
c = [abstractClass valueClassWithObjCType: @encode(NSSize)];
|
||||
else if (strncmp("{_NSPoint=", objctype, 10) == 0)
|
||||
else if (strncmp(CGPOINT_ENCODING_PREFIX, objctype, strlen(CGPOINT_ENCODING_PREFIX)) == 0)
|
||||
c = [abstractClass valueClassWithObjCType: @encode(NSPoint)];
|
||||
else if (strncmp("{_NSRect=", objctype, 9) == 0)
|
||||
else if (strncmp(CGRECT_ENCODING_PREFIX, objctype, strlen(CGRECT_ENCODING_PREFIX)) == 0)
|
||||
c = [abstractClass valueClassWithObjCType: @encode(NSRect)];
|
||||
else if (strncmp("{_NSRange=", objctype, 10) == 0)
|
||||
else if (strncmp(NSRANGE_ENCODING_PREFIX, objctype, strlen(NSRANGE_ENCODING_PREFIX)) == 0)
|
||||
c = [abstractClass valueClassWithObjCType: @encode(NSRange)];
|
||||
else
|
||||
c = [abstractClass valueClassWithObjCType: objctype];
|
||||
|
|
60
Source/typeEncodingHelper.h
Normal file
60
Source/typeEncodingHelper.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/** Type-Encoding Helper
|
||||
Copyright (C) 2024 Free Software Foundation, Inc.
|
||||
|
||||
Written by: Hugo Melder <hugo@algoriddim.com>
|
||||
Created: August 2024
|
||||
|
||||
This file is part of the GNUstep Base Library.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110 USA.
|
||||
*/
|
||||
|
||||
#ifndef __TYPE_ENCODING_HELPER_H
|
||||
#define __TYPE_ENCODING_HELPER_H
|
||||
|
||||
/*
|
||||
* Type-encoding for known structs in Foundation and CoreGraphics.
|
||||
* From macOS 14.4.1 23E224 arm64:
|
||||
|
||||
* @encoding(NSRect) -> {CGRect={CGPoint=dd}{CGSize=dd}}
|
||||
* @encoding(CGRect) -> {CGRect={CGPoint=dd}{CGSize=dd}}
|
||||
* @encoding(NSPoint) -> {CGPoint=dd}
|
||||
* @encoding(CGPoint) -> {CGPoint=dd}
|
||||
* @encoding(NSSize) -> {CGSize=dd}
|
||||
* @encoding(CGSize) -> {CGSize=dd}
|
||||
* @encoding(NSRange) -> {_NSRange=QQ}
|
||||
* @encoding(CFRange) -> {?=qq}
|
||||
*
|
||||
* Note that NSRange and CFRange are not toll-free bridged.
|
||||
* You cannot pass a CFRange to +[NSValue valueWithRange:]
|
||||
* as type encoding is different.
|
||||
*
|
||||
* We cannot enforce this using static asserts, as @encode
|
||||
* is not a constexpr. It is therefore checked in
|
||||
* Tests/base/KVC/type_encoding.m
|
||||
*/
|
||||
|
||||
static const char *CGPOINT_ENCODING_PREFIX = "{CGPoint=";
|
||||
static const char *CGSIZE_ENCODING_PREFIX = "{CGSize=";
|
||||
static const char *CGRECT_ENCODING_PREFIX = "{CGRect=";
|
||||
static const char *NSRANGE_ENCODING_PREFIX = "{_NSRange=";
|
||||
|
||||
#define IS_CGPOINT_ENCODING(encoding) (strncmp(encoding, CGPOINT_ENCODING_PREFIX, strlen(CGPOINT_ENCODING_PREFIX)) == 0)
|
||||
#define IS_CGSIZE_ENCODING(encoding) (strncmp(encoding, CGSIZE_ENCODING_PREFIX, strlen(CGSIZE_ENCODING_PREFIX)) == 0)
|
||||
#define IS_CGRECT_ENCODING(encoding) (strncmp(encoding, CGRECT_ENCODING_PREFIX, strlen(CGRECT_ENCODING_PREFIX)) == 0)
|
||||
#define IS_NSRANGE_ENCODING(encoding) (strncmp(encoding, NSRANGE_ENCODING_PREFIX, strlen(NSRANGE_ENCODING_PREFIX)) == 0)
|
||||
|
||||
#endif /* __TYPE_ENCODING_HELPER_H */
|
Loading…
Add table
Add a link
Reference in a new issue