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:
Hugo Melder 2024-10-29 06:12:34 -07:00 committed by GitHub
parent 4f0a8d60c2
commit 6eef1c3289
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 1549 additions and 49 deletions

View file

@ -0,0 +1,126 @@
/** CFCGTypes.h - CoreFoundation header file for CG types
Copyright (C) 2024 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Created: October 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 _CFCGTypes_h_GNUSTEP_BASE_INCLUDE
#define _CFCGTypes_h_GNUSTEP_BASE_INCLUDE
#include <float.h>
#include <stdint.h>
#define CF_DEFINES_CG_TYPES
#if defined(__has_attribute) && __has_attribute(objc_boxable)
# define CF_BOXABLE __attribute__((objc_boxable))
#else
# define CF_BOXABLE
#endif
#if (defined(__LP64__) && __LP64__) || defined(_WIN64)
# define CGFLOAT_TYPE double
# define CGFLOAT_IS_DOUBLE 1
# define CGFLOAT_MIN DBL_MIN
# define CGFLOAT_MAX DBL_MAX
# define CGFLOAT_EPSILON DBL_EPSILON
#else
# define CGFLOAT_TYPE float
# define CGFLOAT_IS_DOUBLE 0
# define CGFLOAT_MIN FLT_MIN
# define CGFLOAT_MAX FLT_MAX
# define CGFLOAT_EPSILON FLT_EPSILON
#endif
typedef CGFLOAT_TYPE CGFloat;
#define CGFLOAT_DEFINED 1
struct
CGPoint {
CGFloat x;
CGFloat y;
};
typedef struct CF_BOXABLE CGPoint CGPoint;
struct CGSize {
CGFloat width;
CGFloat height;
};
typedef struct CF_BOXABLE CGSize CGSize;
#define CGVECTOR_DEFINED 1
struct CGVector {
CGFloat dx;
CGFloat dy;
};
typedef struct CF_BOXABLE CGVector CGVector;
struct CGRect {
CGPoint origin;
CGSize size;
};
typedef struct CF_BOXABLE CGRect CGRect;
enum
{
CGRectMinXEdge = 0,
CGRectMinYEdge = 1,
CGRectMaxXEdge = 2,
CGRectMaxYEdge = 3
};
typedef struct CGAffineTransform CGAffineTransform;
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};
#define CF_DEFINES_CGAFFINETRANSFORMCOMPONENTS
/* |------------------ CGAffineTransformComponents ----------------|
*
* | a b 0 | | sx 0 0 | | 1 0 0 | | cos(t) sin(t) 0 | | 1 0 0 |
* | c d 0 | = | 0 sy 0 | * | sh 1 0 | * |-sin(t) cos(t) 0 | * | 0 1 0 |
* | tx ty 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | tx ty 1 |
* CGAffineTransform scale shear rotation translation
*/
typedef struct CGAffineTransformComponents CGAffineTransformComponents;
struct CGAffineTransformComponents {
/* Scale factors in X and Y dimensions. Negative values indicate flipping along that axis. */
CGSize scale;
/* Shear distortion along the horizontal axis. A value of 0 means no shear. */
CGFloat horizontalShear;
/* Rotation angle in radians around the origin. Sign convention may vary
* based on the coordinate system used. */
CGFloat rotation;
/* Translation or displacement along the X and Y axes. */
CGVector translation;
};
#endif // _CFCGTypes_h_GNUSTEP_BASE_INCLUDE

View file

@ -25,10 +25,12 @@
#ifndef __NSGeometry_h_GNUSTEP_BASE_INCLUDE
#define __NSGeometry_h_GNUSTEP_BASE_INCLUDE
#import <GNUstepBase/GSVersionMacros.h>
#import <CoreFoundation/CFCGTypes.h>
#ifdef __OBJC__
#import <objc/objc.h>
#import <Foundation/NSString.h>
#endif
#if defined(__cplusplus)
extern "C" {
@ -56,12 +58,7 @@ extern "C" {
CGFloat y;
}</example>
<p>Represents a 2-d cartesian position.</p> */
typedef struct _NSPoint NSPoint;
struct _NSPoint
{
CGFloat x;
CGFloat y;
};
typedef struct CGPoint NSPoint;
#if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST)
/** Array of NSPoint structs. */
@ -76,12 +73,7 @@ typedef NSPoint *NSPointPointer;
CGFloat height;
}</example>
<p>Floating point rectangle size.</p> */
typedef struct _NSSize NSSize;
struct _NSSize
{
CGFloat width;
CGFloat height;
};
typedef struct CGSize NSSize;
#if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST)
/** Array of NSSize structs. */
@ -97,12 +89,7 @@ typedef NSSize *NSSizePointer;
}</example>
<p>Rectangle.</p> */
typedef struct _NSRect NSRect;
struct _NSRect
{
NSPoint origin;
NSSize size;
};
typedef struct CGRect NSRect;
#if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST)
/** Array of NSRect structs. */

View file

@ -101,19 +101,6 @@ typedef uintptr_t NSUInteger;
# define NSUIntegerMax UINTPTR_MAX
#endif /* !defined(NSINTEGER_DEFINED) */
#if !defined(CGFLOAT_DEFINED)
#if GS_SIZEOF_VOIDP == 8
#define CGFLOAT_IS_DBL 1
typedef double CGFloat;
#define CGFLOAT_MIN DBL_MIN
#define CGFLOAT_MAX DBL_MAX
#else
typedef float CGFloat;
#define CGFLOAT_MIN FLT_MIN
#define CGFLOAT_MAX FLT_MAX
#endif
#endif /* !defined(CGFLOAT_DEFINED) */
#define NSINTEGER_DEFINED 1
#define CGFLOAT_DEFINED 1
#ifndef NS_AUTOMATED_REFCOUNT_UNAVAILABLE

View file

@ -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__

View file

@ -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 \

View file

@ -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 \

View 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);

View 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);
}

View file

@ -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
}

View file

@ -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];

View 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 */

View file

@ -0,0 +1,56 @@
#import <Foundation/NSKeyValueCoding.h>
#import <Foundation/NSValue.h>
#import <objc/runtime.h>
#import "Testing.h"
@interface TestClass : NSObject
- (float)floatVal;
@end
@implementation TestClass
- (float)floatVal
{
return 1.0f;
}
@end
static float
getFloatValIMP(id receiver, SEL cmd)
{
return 2.0f;
}
void
testInstallingNewMethodAfterCaching(void)
{
TestClass *obj = [TestClass new];
START_SET("Installing Methods after initial Cache")
// Initial lookups
PASS_EQUAL([obj valueForKey:@"floatVal"], [NSNumber numberWithFloat:1.0f],
"Initial lookup has the correct value");
// Slots are now cached
// Register getFloatVal which should be used if available according to search
// pattern
SEL sel = sel_registerName("getFloatVal");
class_addMethod([TestClass class], sel, (IMP) getFloatValIMP, "f@:");
PASS_EQUAL([obj valueForKey:@"floatVal"], [NSNumber numberWithFloat:2.0f],
"Slot was correctly invalidated");
END_SET("Installing Methods after initial Cache")
[obj release];
}
int
main(int argc, char *argv[])
{
testInstallingNewMethodAfterCaching();
return 0;
}

View file

@ -0,0 +1,191 @@
#import <Foundation/NSKeyValueCoding.h>
#import <Foundation/NSValue.h>
#import "Testing.h"
// For ivars: _<key>, _is<Key>, <key>, or is<Key>, in that order.
// For methods: get<Key>, <key>, is<Key>, or _<key> in that order.
@interface SearchOrder : NSObject
{
long long _longLong;
long long _isLongLong;
long long longLong;
long long isLongLong;
unsigned char _isUnsignedChar;
unsigned char unsignedChar;
unsigned char isUnsignedChar;
unsigned int unsignedInt;
unsigned int isUnsignedInt;
unsigned long isUnsignedLong;
}
- (instancetype)init;
- (signed char)getChar;
- (signed char)char;
- (signed char)isChar;
- (signed char)_char;
- (int)int;
- (int)isInt;
- (int)_int;
- (short)isShort;
- (short)_short;
- (long)_long;
@end
@implementation SearchOrder
- (instancetype)init
{
self = [super init];
if (self)
{
_longLong = LLONG_MAX;
_isLongLong = LLONG_MAX - 1;
longLong = LLONG_MAX - 2;
isLongLong = LLONG_MAX - 3;
_isUnsignedChar = UCHAR_MAX;
unsignedChar = UCHAR_MAX - 1;
isUnsignedChar = UCHAR_MAX - 2;
unsignedInt = UINT_MAX;
isUnsignedInt = UINT_MAX - 1;
isUnsignedLong = ULONG_MAX;
}
return self;
}
- (signed char)getChar
{
return SCHAR_MAX;
}
- (signed char)char
{
return SCHAR_MAX - 1;
}
- (signed char)isChar
{
return SCHAR_MAX - 2;
}
- (signed char)_char
{
return SCHAR_MAX - 3;
}
- (int)int
{
return INT_MAX;
}
- (int)isInt
{
return INT_MAX - 1;
}
- (int)_int
{
return INT_MAX - 2;
}
- (short)isShort
{
return SHRT_MAX;
}
- (short)_short
{
return SHRT_MAX - 1;
}
- (long)_long
{
return LONG_MAX;
}
@end
@interface SearchOrderNoIvarAccess : NSObject
{
bool _boolVal;
bool _isBoolVal;
bool boolVal;
bool isBoolVal;
}
@end
@implementation SearchOrderNoIvarAccess
+ (BOOL)accessInstanceVariablesDirectly
{
return NO;
}
@end
static void
testSearchOrder(void)
{
SearchOrder *so = [SearchOrder new];
START_SET("Search Order");
PASS_EQUAL([so valueForKey:@"char"], [NSNumber numberWithChar:SCHAR_MAX],
"get<Key> is used when available");
PASS_EQUAL([so valueForKey:@"int"], [NSNumber numberWithInt:INT_MAX],
"<key> is used when get<Key> is not available");
PASS_EQUAL([so valueForKey:@"short"], [NSNumber numberWithShort:SHRT_MAX],
"is<Key> is used when get<Key> and <key> is not available");
PASS_EQUAL(
[so valueForKey:@"long"], [NSNumber numberWithLong:LONG_MAX],
"_<key> is used when get<Key>, <key>, and is<Key> is not available");
PASS_EQUAL(
[so valueForKey:@"longLong"], [NSNumber numberWithLongLong:LLONG_MAX],
"_<key> ivar is used when get<Key>, <key>, and is<Key> is not available");
PASS_EQUAL(
[so valueForKey:@"unsignedChar"],
[NSNumber numberWithUnsignedChar:UCHAR_MAX],
"_is<Key> ivar is used when get<Key>, <key>, and is<Key> is not available");
PASS_EQUAL(
[so valueForKey:@"unsignedInt"], [NSNumber numberWithUnsignedInt:UINT_MAX],
"<key> ivar is used when get<Key>, <key>, and is<Key> is not available");
PASS_EQUAL(
[so valueForKey:@"unsignedLong"],
[NSNumber numberWithUnsignedLong:ULONG_MAX],
"is<Key> ivar is used when get<Key>, <key>, and is<Key> is not available");
END_SET("Search Order");
[so release];
}
static void
testIvarAccess(void)
{
SearchOrderNoIvarAccess *so = [SearchOrderNoIvarAccess new];
START_SET("Search Order Ivar Access");
PASS_EXCEPTION([so valueForKey:@"boolVal"], NSUndefinedKeyException,
"Does not return protected ivar");
END_SET("Search Order Ivar Access");
[so release];
}
int
main(int argc, char *argv[])
{
testSearchOrder();
testIvarAccess();
return 0;
}

View file

@ -0,0 +1,16 @@
#import <Foundation/NSGeometry.h>
#import "Testing.h"
#import "../../../Source/typeEncodingHelper.h"
int main(int argc, char *argv[]) {
START_SET("Known Struct Type Encodings")
PASS(strncmp(@encode(NSPoint), CGPOINT_ENCODING_PREFIX, strlen(CGPOINT_ENCODING_PREFIX)) == 0, "CGPoint encoding");
PASS(strncmp(@encode(NSSize), CGSIZE_ENCODING_PREFIX, strlen(CGSIZE_ENCODING_PREFIX)) == 0, "CGSize encoding");
PASS(strncmp(@encode(NSRect), CGRECT_ENCODING_PREFIX, strlen(CGRECT_ENCODING_PREFIX)) == 0, "CGRect encoding");
PASS(strncmp(@encode(NSRange), NSRANGE_ENCODING_PREFIX, strlen(NSRANGE_ENCODING_PREFIX)) == 0, "NSRange encoding");
END_SET("Known Struct Type Encodings")
return 0;
}

354
Tests/base/KVC/types.m Normal file
View file

@ -0,0 +1,354 @@
#include "GNUstepBase/GSObjCRuntime.h"
#import <Foundation/NSKeyValueCoding.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSGeometry.h>
#import <Foundation/NSArray.h>
#include "Testing.h"
/*
* Testing key-value coding on accessors and instance variable
* with all supported types.
*
* Please note that 'Class', `SEL`, unions, and pointer types are
* not coding-compliant on macOS.
*/
typedef struct
{
int x;
float y;
} MyStruct;
@interface ReturnTypes : NSObject
{
signed char _iChar;
int _iInt;
short _iShort;
long _iLong;
long long _iLongLong;
unsigned char _iUnsignedChar;
unsigned int _iUnsignedInt;
unsigned short _iUnsignedShort;
unsigned long _iUnsignedLong;
unsigned long long _iUnsignedLongLong;
float _iFloat;
double _iDouble;
bool _iBool;
// Not coding-compliant on macOS
// const char *_iCharPtr;
// int *_iIntPtr;
// Class _iCls;
// void *_iUnknownType; // Type encoding: ?
// MyUnion _iMyUnion;
id _iId;
NSPoint _iNSPoint;
NSRange _iNSRange;
NSRect _iNSRect;
NSSize _iNSSize;
MyStruct _iMyStruct;
}
- (instancetype)init;
- (signed char)mChar; // Type encoding: c
- (int)mInt; // Type encoding: i
- (short)mShort; // Type encoding: s
- (long)mLong; // Type encoding: l
- (long long)mLongLong; // Type encoding: q
- (unsigned char)mUnsignedChar; // Type encoding: C
- (unsigned int)mUnsignedInt; // Type encoding: I
- (unsigned short)mUnsignedShort; // Type encoding: S
- (unsigned long)mUnsignedLong; // Type encoding: L
- (unsigned long long)mUnsignedLongLong; // Type encoding: Q
- (float)mFloat; // Type encoding: f
- (double)mDouble; // Type encoding: d
- (bool)mBool; // Type encoding: B
- (id)mId; // Type encoding: @
- (NSPoint)mNSPoint;
- (NSRange)mNSRange;
- (NSRect)mNSRect;
- (NSSize)mNSSize;
- (MyStruct)mMyStruct;
@end
@implementation ReturnTypes
- (instancetype)init
{
self = [super init];
if (self)
{
MyStruct my = {.x = 42, .y = 3.14f};
_iChar = SCHAR_MIN;
_iShort = SHRT_MIN;
_iInt = INT_MIN;
_iLong = LONG_MIN;
_iUnsignedChar = 0;
_iUnsignedInt = 0;
_iUnsignedShort = 0;
_iUnsignedLong = 0;
_iUnsignedLongLong = 0;
_iFloat = 123.4f;
_iDouble = 123.45678;
_iBool = true;
_iId = @"id";
_iNSPoint = NSMakePoint(1.0, 2.0);
_iNSRange = NSMakeRange(1, 2);
_iNSRect = NSMakeRect(1.0, 2.0, 3.0, 4.0);
_iNSSize = NSMakeSize(1.0, 2.0);
_iMyStruct = my;
}
return self;
}
- (void)mVoid
{}
- (signed char)mChar
{
return SCHAR_MAX;
}
- (short)mShort
{
return SHRT_MAX;
}
- (int)mInt
{
return INT_MAX;
}
- (long)mLong
{
return LONG_MAX;
}
- (long long)mLongLong
{
return LLONG_MAX;
}
- (unsigned char)mUnsignedChar
{
return UCHAR_MAX;
}
- (unsigned int)mUnsignedInt
{
return UINT_MAX;
}
- (unsigned short)mUnsignedShort
{
return USHRT_MAX;
}
- (unsigned long)mUnsignedLong
{
return ULONG_MAX;
}
- (unsigned long long)mUnsignedLongLong
{
return ULLONG_MAX;
}
- (float)mFloat
{
return 123.45f;
}
- (double)mDouble
{
return 123.456789;
}
- (bool)mBool
{
return true;
}
- (id)mId
{
return @"id";
}
- (NSPoint)mNSPoint
{
return NSMakePoint(1.0, 2.0);
}
- (NSRange)mNSRange
{
return NSMakeRange(1, 2);
}
- (NSRect)mNSRect
{
return NSMakeRect(1.0, 2.0, 3.0, 4.0);
}
- (NSSize)mNSSize
{
return NSMakeSize(1.0, 2.0);
}
- (MyStruct)mMyStruct
{
MyStruct s = {.x = 1, .y = 2.0};
return s;
}
@end
static void
testAccessors(void)
{
ReturnTypes *rt = [ReturnTypes new];
NSPoint p = NSMakePoint(1.0, 2.0);
NSRange r = NSMakeRange(1, 2);
NSRect re = NSMakeRect(1.0, 2.0, 3.0, 4.0);
NSSize s = NSMakeSize(1.0, 2.0);
MyStruct ms = {.x = 1, .y = 2.0};
START_SET("Accessors");
PASS_EQUAL([rt valueForKey:@"mChar"], [NSNumber numberWithChar:SCHAR_MAX],
"Accessor returns char");
PASS_EQUAL([rt valueForKey:@"mInt"], [NSNumber numberWithInt:INT_MAX],
"Accessor returns int");
PASS_EQUAL([rt valueForKey:@"mShort"], [NSNumber numberWithShort:SHRT_MAX],
"Accessor returns short");
PASS_EQUAL([rt valueForKey:@"mLong"], [NSNumber numberWithLong:LONG_MAX],
"Accessor returns long");
PASS_EQUAL([rt valueForKey:@"mLongLong"],
[NSNumber numberWithLongLong:LLONG_MAX],
"Accessor returns long long");
PASS_EQUAL([rt valueForKey:@"mUnsignedChar"],
[NSNumber numberWithUnsignedChar:UCHAR_MAX],
"Accessor returns unsigned char");
PASS_EQUAL([rt valueForKey:@"mUnsignedInt"],
[NSNumber numberWithUnsignedInt:UINT_MAX],
"Accessor returns unsigned int");
PASS_EQUAL([rt valueForKey:@"mUnsignedShort"],
[NSNumber numberWithUnsignedShort:USHRT_MAX],
"Accessor returns unsigned short");
PASS_EQUAL([rt valueForKey:@"mUnsignedLong"],
[NSNumber numberWithUnsignedLong:ULONG_MAX],
"Accessor returns unsigned long");
PASS_EQUAL([rt valueForKey:@"mUnsignedLongLong"],
[NSNumber numberWithUnsignedLongLong:ULLONG_MAX],
"Accessor returns unsigned long long");
PASS_EQUAL([rt valueForKey:@"mFloat"], [NSNumber numberWithFloat:123.45f],
"Accessor returns float");
PASS_EQUAL([rt valueForKey:@"mDouble"],
[NSNumber numberWithDouble:123.456789], "Accessor returns double");
PASS_EQUAL([rt valueForKey:@"mBool"], [NSNumber numberWithBool:true],
"Accessor returns bool");
PASS_EQUAL([rt valueForKey:@"mId"], @"id", "Accessor returns id");
PASS_EQUAL([rt valueForKey:@"mNSPoint"], [NSValue valueWithPoint:p],
"Accessor returns NSPoint");
PASS_EQUAL([rt valueForKey:@"mNSRange"], [NSValue valueWithRange:r],
"Accessor returns NSRange");
PASS_EQUAL([rt valueForKey:@"mNSRect"], [NSValue valueWithRect:re],
"Accessor returns NSRect");
PASS_EQUAL([rt valueForKey:@"mNSSize"], [NSValue valueWithSize:s],
"Accessor returns NSSize");
PASS_EQUAL([rt valueForKey:@"mMyStruct"],
[NSValue valueWithBytes:&ms objCType:@encode(MyStruct)],
"Accessor returns MyStruct");
END_SET("Accessors");
[rt release];
}
static void
testIvars(void)
{
ReturnTypes *rt = [ReturnTypes new];
NSPoint p = NSMakePoint(1.0, 2.0);
NSRange r = NSMakeRange(1, 2);
NSRect re = NSMakeRect(1.0, 2.0, 3.0, 4.0);
NSSize s = NSMakeSize(1.0, 2.0);
MyStruct ms = {.x = 42, .y = 3.14f};
START_SET("Ivars");
PASS_EQUAL([rt valueForKey:@"iChar"], [NSNumber numberWithChar:SCHAR_MIN],
"Ivar returns char");
PASS_EQUAL([rt valueForKey:@"iInt"], [NSNumber numberWithInt:INT_MIN],
"Ivar returns int");
PASS_EQUAL([rt valueForKey:@"iShort"], [NSNumber numberWithShort:SHRT_MIN],
"Ivar returns short");
PASS_EQUAL([rt valueForKey:@"iLong"], [NSNumber numberWithLong:LONG_MIN],
"Ivar returns long");
PASS_EQUAL([rt valueForKey:@"iUnsignedChar"],
[NSNumber numberWithUnsignedChar:0], "Ivar returns unsigned char");
PASS_EQUAL([rt valueForKey:@"iUnsignedInt"],
[NSNumber numberWithUnsignedInt:0], "Ivar returns unsigned int");
PASS_EQUAL([rt valueForKey:@"iUnsignedShort"],
[NSNumber numberWithUnsignedShort:0],
"Ivar returns unsigned short");
PASS_EQUAL([rt valueForKey:@"iUnsignedLong"],
[NSNumber numberWithUnsignedLong:0], "Ivar returns unsigned long");
PASS_EQUAL([rt valueForKey:@"iUnsignedLongLong"],
[NSNumber numberWithUnsignedLongLong:0],
"Ivar returns unsigned long long");
PASS_EQUAL([rt valueForKey:@"iFloat"], [NSNumber numberWithFloat:123.4f],
"Ivar returns float");
PASS_EQUAL([rt valueForKey:@"iDouble"], [NSNumber numberWithDouble:123.45678],
"Ivar returns double");
PASS_EQUAL([rt valueForKey:@"iBool"], [NSNumber numberWithBool:true],
"Ivar returns bool");
PASS_EQUAL([rt valueForKey:@"iId"], @"id", "Ivar returns id");
PASS_EQUAL([rt valueForKey:@"iNSPoint"], [NSValue valueWithPoint:p],
"Ivar returns NSPoint");
PASS_EQUAL([rt valueForKey:@"iNSRange"], [NSValue valueWithRange:r],
"Ivar returns NSRange");
PASS_EQUAL([rt valueForKey:@"iNSRect"], [NSValue valueWithRect:re],
"Ivar returns NSRect");
PASS_EQUAL([rt valueForKey:@"iNSSize"], [NSValue valueWithSize:s],
"Ivar returns NSSize");
/* Welcome to another session of: Why GCC ObjC is a buggy mess.
*
* You'd expect that the type encoding of an ivar would be the same as @encode.
*
* Ivar var = class_getInstanceVariable([ReturnTypes class], "_iMyStruct");
* const char *type = ivar_getTypeEncoding(var);
* NSLog(@"Type encoding of iMyStruct: %s", type);
*
* So type should be equal to @encode(MyStruct) ({?=if})
*
* On GCC this is not the case. The type encoding of the ivar is {?="x"i"y"f}.
* This leads to failure of the following test.
*
* So mark this as hopeful until we stop supporting buggy compilers.
*/
testHopeful = YES;
PASS_EQUAL([rt valueForKey:@"iMyStruct"],
[NSValue valueWithBytes:&ms objCType:@encode(MyStruct)],
"Ivar returns MyStruct");
testHopeful = NO;
END_SET("Ivars");
[rt release];
}
int
main(int argc, char *argv[])
{
testAccessors();
testIvars();
return 0;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.