Merge branch 'master' of github.com:gnustep/libs-base

This commit is contained in:
Gregory John Casamento 2025-01-27 10:40:24 -05:00
commit 1e51260895
20 changed files with 420 additions and 102 deletions

4
.gitignore vendored
View file

@ -98,7 +98,6 @@ compile_commands.json
# Documentation
Documentation/Base*
Documentation/General
Documentation/manual
Documentation/ReleaseNotes
Documentation/ANNOUNCE
Documentation/*.pdf
@ -108,6 +107,7 @@ Documentation/*.aux
Documentation/*.toc
Documentation/INSTALL
Documentation/NEWS
Documentation/manual/manual.*
**/dependencies
Source/Base.gsdoc
Source/BaseAdditions.gsdoc
Source/BaseAdditions.gsdoc

View file

@ -152,10 +152,9 @@ the NSApplicationMain function in a gui application).
GNUstep's NSInvocations and Distributed Objects code involves detailed
manipulation of the stack and function calls using a library that
implements a Foreign-Function Interface (FFI), such as the
libffi library. Use of libffi is automatically enabled if the libffi
library is found (and the same with ffcall, although libffi takes
precedence), unless specifically disabled with @code{--disable-do}.
implements a Foreign-Function Interface (FFI); the libffi library.
Use of libffi is automatically enabled if the libffi library is found ,
unless specifically disabled with @code{--disable-do}.
@node Compilation, , Configuration, Top
@section Compilation

View file

@ -451,6 +451,24 @@ of statistics collection is only incurred when it is active. To access the
statistics, use the set of @code{GSDebugAllocation...()} functions defined in
@code{NSDebug.h}.
In addition to basic statistics (but at higher performance cose), the
@code{GSDebugAllocation...()} functions provide detailed records of when and
where objects are allocated/deallocated. This can be useful when debugging
for memory leaks.
Finally, for pinpoint accuracy, the -trackOwnership method can be called on
an individual object to turn on tracking of the lifetime of that object. In
this case a stack trace is printed logging every ownership event (retain,
release, or dealloc) and a log is printed at process exit if the object
has not been deallocated. The same method may be called on a class to
track every object of that class. This method is declared in
@code{NSObject+GNUstepBase.h}. Tracking the life of an individual object is
particularly useful if a leak checker (eg when your program was built using
@code{(make asan=yes)} or run under valgrind) has reported a leak and the
cause of the leak is hard to find: the leak checker will have told you the
stack trace where the leaked memory was allocated, so you can change your
code to start tracking immediately after that and see exacly what happened
to the object ownership after its creation.
@section Assertions
@cindex assertions

View file

@ -130,7 +130,7 @@ top-level directory of the package. A non-GNUstep Objective-C file may be
compiled by adding @code{-lobjc on} at the command line.
@subsection Debug and Profile Information
@subsection Debug, Profile and Sanitization
By default the Makefile Package does not flag the compiler to generate debugging
@ -143,10 +143,15 @@ therefore necessary to override the optimization flag when running Make if both
debugging information and optimization is required. Use the variable OPTFLAG to
override the optimization flag.
By default the Makefile Package does not instruct the compiler to create profiling
information that is generated by typing:
By default the Makefile Package does not instruct the compiler to create
profiling information that is generated by typing:
@code{make profile=yes}
By default the Makefile Package does not instruct the compiler to create
address and leak sanitization information. This is turned on by typing:
@code{make asan=yes}
@sp 1
@subsection Static, Shared and DLLs

View file

@ -124,7 +124,7 @@ in the same area of memory, or allocate in chunks - perhaps for performance
reasons, you may create a Zone from where you would allocate those objects by
using the @code{NSCreateZone} function. This will minimise the paging
required by your application when accessing those objects frequently.
In all normal yuse however, you should confine yourself to the default zone.
In all normal use however, you should confine yourself to the default zone.
Low level memory allocation is performed by the @code{NSAllocateObject()}
function. This is rarely used but available when you require more advanced
@ -139,6 +139,10 @@ will probably not need to worry about Zones at all; unless performance is
critical, you can just use the methods without zone arguments, that take the
default zone.
With the ObjC-2 (NG) setup, the use of zones is obsoleted: the runtime
library performs the actual allocation of objects and ignores the zone
information.
@subsection Memory Deallocation
@cindex memory deallocation
@ -159,6 +163,9 @@ As with @code{alloc}, the underlying implementation utilizes a function
(@code{NSDeallocateObject()}) that can be used by your code if you know what
you are doing.
With the ObjC-2 (NG) setup, the use of zones is obsoleted: the runtime
library performs the freeing of memory used by objects.
@section Memory Management
@cindex memory management
@ -180,6 +187,13 @@ pools which provide a degree of automated memory management. This gives
a good degree of control over memory management, but requires some care
in following simple rules. It's pretty efficient.
@item Automated Reference Counts (ARC)@*
Only available when using the ObjC-2 (NG) environment rather than classic
Objective-C. In this case the compiler generates code to use the retain
count and autorelease pools. The use of ARC can be turned on/off for
individual files.
@end itemize
The recommended approach is to use some standard macros defined in
@ -226,6 +240,12 @@ object gets deallocated.
[c release]; // Calls 'release' ... (retain count 0) then 'dealloc'
@end example
Retain count is best understood using the concept of ownership. When we
retain an object we own it and are responsible for releasing it again.
When nobody owns an object (its retain count is zero) it is deallocated.
The retain count of an object is the number of places which own the object
and have therefore undertaken to release it when they have finished with it.
One way of thinking about the initial retain count of 1 on the object is that
a call to @code{alloc} (or @code{copy}) implicitly calls @code{retain} as
well. There are a couple of default conventions about how @code{retain} and
@ -256,7 +276,7 @@ Thus, a typical usage pattern is:
Retain and release must also be used for instance variables that are objects:
@example
- (void)setFoo:(FooClass *newFoo)
- (void) setFoo: (FooClass *newFoo)
@{
// first, assert reference to newFoo
[newFoo retain];
@ -268,11 +288,15 @@ Retain and release must also be used for instance variables that are objects:
@}
@end example
To write portable code (which will work with both the classic retain counting
mechanism and with ARC) you should use the macros RETAIN(expr) and
RELEASE(expr) along with the DESTROY(lvalue) and ASSIGN(lvalue, expr) macros.
Because of this retain/release management, it is safest to use accessor
methods to set variables even within a class:
@example
- (void)resetFoo
- (void) resetFoo
@{
FooClass *foo = [[FooClass alloc] init];
[self setFoo: foo];
@ -301,17 +325,17 @@ general you need to be careful in these cases that retains and releases match.
One important case where the retain/release system has difficulties is when
an object needs to be transferred or handed off to another. You don't want
to retain the transferred object in the transferring code, but neither do you
want the object to be destroyed before the handoff can take place. The
want the object to be destroyed before the hand-off can take place. The
OpenStep/GNUstep solution to this is the @i{autorelease pool}. An
autorelease pool is a special mechanism that will retain objects it is given
for a limited time -- always enough for a transfer to take place. This
mechanism is accessed by calling @code{autorelease} on an object instead of
@code{release}. @code{Autorelease} first adds the object to the active
autorelease pool, which retains it, then sends a @code{release} to the
object. At some point later on, the pool will send the object a second
@code{release} message, but by this time the object will presumably either
have been retained by some other code, or is no longer needed and can thus be
deallocated. For example:
autorelease pool, which retains it, then sends a @code{release} to the object.
At some point later on (when the pool is destroyed), the pool will send the
object a second @code{release} message, but by this time the object will
presumably either have been retained by some other code, or is no longer
needed and can thus be deallocated. For example:
@example
- (NSString *) getStatus
@ -346,6 +370,9 @@ stored and used later on however, it should be retained:
[currentStatus retain];
@end example
To write portable code (for both classic retain counting and ARC) you should
use the AUTORELEASE(expr) macro.
@b{Convenience Constructors}
A special case of object transfer occurs when a @i{convenience} constructor is
@ -372,7 +399,7 @@ retain it if you want to hold onto it for a while.
An autorelease pool is created automatically if you are using the GNUstep
GUI classes, however if you are just using the GNUstep Base classes for a
nongraphical application, you must create and release autorelease pools
non-graphical application, you must create and release autorelease pools
yourself:
@example
@ -387,7 +414,7 @@ pool itself:
[pool release];
@end example
To achieve finer control over autorelease behavior you may also create
To achieve finer control over autorelease behaviour you may also create
additional pools and release them in a nested manner. Calls to
@code{autorelease} will always use the most recently created pool.
@ -395,6 +422,11 @@ Finally, note that @code{autorelease} calls are significantly slower than
plain @code{release}. Therefore you should only use them when they are
necessary.
The best way to manage autorelease pools is using macros which will work
both for the classic system or when using ARC. The ENTER_POOL macro
begins a block in which a new pool handles autoreleases and the LEAVE_POOL
macro ends that block and destroys the autorelease pool.
@subsubsection Avoiding Retain Cycles
@ -408,6 +440,21 @@ careful with your designs. If you notice a situation where a retain cycle
could arise, remove at least one of the links in the chain, but not in such a
way that references to deallocated objects might be mistakenly used.
To help solve the problem of retain cycles you can use weak references
to break a cycle. The runtime library provides functions to handle weak
references so that you can safely check to see whether the reference is
to an object that still exists or not. To manage that the objc_storeWeak()
function is used whenever assigning a value to the variable (instead of
retaining the value), and the objc_loadWeak() function is used to retrieve
the value from the variable ... the retrieved value will be nil if the
object has been deallocated. With the ObjC-2 (Next Generation) environment
you can use the keyword `weak' to tell the compiler to automatically insert
calls to those runtime functions whenever a value is written to or read from
the variable.
NB. weak references are relatively inefficient since each time objc_loadWeak()
is called it both retains and autorelease the referenced value so that it
will continue to exist for long enough for your code to work with it.
@subsubsection Summary
@ -416,18 +463,18 @@ The following summarizes the retain/release-related methods:
@multitable @columnfractions 0.25 0.75
@item Method @tab Description
@item @code{-retain}
@tab increases the reference count of an object by 1
@tab increases the retain count of an object by 1
@item @code{-release}
@tab decreases the reference count of an object by 1
@tab decreases the retain count of an object by 1
@item @code{-autorelease}
@tab decreases the reference count of an object by 1 at some stage in the future
@tab decreases the retain count of an object by 1 at some stage in the future
@item @code{+alloc} and @code{+allocWithZone:}
@tab allocates memory for an object, and returns it with retain count of 1
@item @code{-copy}, @code{-mutableCopy}, @code{copyWithZone:} and @code{-mutableCopyWithZone:}
@tab makes a copy of an object, and returns it with retain count of 1
@item @code{-init} and any method whose name begins with @code{init}
@tab initialises the receiver, returning the retain count unchanged.
@code{-init} has had no effect on the reference count.
@code{-init} has had no effect on the retain count.
@item @code{-new} and any method whose name begins with @code{new}
@tab allocates memory for an object, initialises it, and returns the result.
@item @code{-dealloc}
@ -473,6 +520,57 @@ ownership rules (how you should use the returned values) remain the same.
Special examples: delegate, target
@end ignore
@subsubsection Leak Checking
Looking at the following code:
@example
#import "Client.h"
@@implementation Client
- (void) executeCallSequence
@{
NSString *str = [NSString stringWithFormat: @@"one little string: %d\n", 100];
const char *strCharPtr = [str cString];
@}
@@end
int main(int argv, char** argc)
@{
Client *client = [[Client alloc] init];
[[NSAutoreleasePool alloc] init];
[client executeCallSequence];
return 0;
@}
@end example
So, what do we expect this to do if we build the program with leak checking ('make asan=yes') or run it with a separate leak checker such as valgrind?
Firstly this code creates a Client instance, owned by the main function. This is because +alloc returns an instance owned by the caller, and -init consumes its receiver and returns an instance owned by the caller, so the alloc/init sequence produces an instance owned by the main function.
Next it creates/enters an autorelease pool, owned by the main function.
Next it executes the method '-[Client executeCallSequence]' which:
Creates an NSString which is NOT owned by the method.
The +stringWithFormat: method creates a new instance and adds it to the current autorelease pool before returning it.
Creates a C string, which is NOT owned by the method.
A non-object return value can't be retained or released, but it conforms to the convention that the memory is not owned by the caller, so the caller need not free it. The -cString method is free to manage that however it likes (for instance it might return a pointer to some internal memory which exists until the NSString object is deallocated), but typically what's returned is a pointer to memory inside some other object which has been autoreleased.
Finally, the 'return' command means that the program exits with a status of zero.
A simple look at the basic retain count and autorelease rules would say that all the memory is leaked (because the program contains no call to release anything), but there's a bit of behind the scenes magic: when a thread exits it releases all the autorelease pools created in it which were not already released. That's not to say that the failure to release the autorelease pool was not a bug (the code should have released it), just that there is a fail-safe behaviour to protect multithreaded programs from this particular programmer error.
So when you consider that, you can see that the autorelease pool is deallocated so the memory of the pool is actually freed, and the memory of the NSString and C-String inside it are therefore also freed.
This leaves us with the memory of the Client object being leaked. However, the idea that any unfreed memory is a leak is too simplistic (leak checkers would be useless if they reported so much) so the leak checker only reports some unfreed memory ... stuff that can't be reached from various standard routes. The main case is that anything pointed to by global or static variables is not considered leaked, but also anything pointed to by a variable in the main() function is not considered leaked. This is why the Client instance would not normally be reported by a leak checker.
@subsection ObjC-2 and Automated Reference Counting
@cindex ObjC-2 , automated reference counting
@ -496,13 +594,13 @@ manual reference counting required when ARC is not available.
@tab @code{[foo autorelease];}
@item @code{ASSIGN(foo, bar);}
@tab @code{[bar retain]; [foo release]; foo = bar;}
@tab @code{id tmp = [bar retain]; [foo release]; foo = tmp;}
@item @code{ASSIGNCOPY(foo, bar);}
@tab @code{[foo release]; foo = [bar copy];}
@tab @code{id tmp = [bar copy]; [foo release]; foo = tmp;}
@item @code{ASSIGNMUTABLECOPY(foo, bar);}
@tab @code{[foo release]; foo = [bar mutableCopy];}
@tab @code{id tmp = [bar mutableCopy]; [foo release]; foo = tmp;}
@item @code{DESTROY(foo);}
@tab @code{[foo release]; foo = nil;}

View file

@ -40,12 +40,6 @@ All files in the @file{Documentation}, @file{Examples},
@file{Tools}, @file{config}, and @file{macosx} directories are covered
under the GPL.
With GNUstep-Base, we strongly recommend the use of the ffcall
libraries, which provides stack frame handling for NSInvocation and
NSConnection. "Ffcall is under GNU GPL. As a special exception, if used
in GNUstep or in derivate works of GNUstep, the included parts of ffcall
are under GNU LGPL" (Text in quotes provided by the author of ffcall).
@section How can you help?
@itemize @bullet

View file

@ -29,8 +29,12 @@
#define CF_DEFINES_CG_TYPES
#if defined(__has_attribute) && __has_attribute(objc_boxable)
# define CF_BOXABLE __attribute__((objc_boxable))
#if defined __has_attribute
# if __has_attribute(objc_boxable)
# define CF_BOXABLE __attribute__((objc_boxable))
# else
# define CF_BOXABLE
# endif
#else
# define CF_BOXABLE
#endif

View file

@ -97,12 +97,3 @@
}
@end
/**
* objc_enumerationMutation() is called whenever a collection mutates in the
* middle of fast enumeration.
*/
void objc_enumerationMutation(id obj)
{
[NSException raise: NSGenericException
format: @"Collection %@ was mutated while being enumerated", obj];
}

View file

@ -44,13 +44,15 @@
/* This Key Value Observing Implementation is tied to libobjc2 */
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSKeyValueObserving.h>
#import <Foundation/NSException.h>
#import "Foundation/NSObject.h"
#import "Foundation/NSString.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSArray.h"
#import "Foundation/NSSet.h"
#import "Foundation/NSKeyValueObserving.h"
#import "Foundation/NSException.h"
#import "GSPrivate.h"
#if defined(__OBJC2__)
@ -114,7 +116,7 @@
// From NSKVOSwizzling
void
_NSKVOEnsureKeyWillNotify(id object, NSString *key);
_NSKVOEnsureKeyWillNotify(id object, NSString *key) GS_ATTRIB_PRIVATE;
#endif
@ -123,6 +125,7 @@ _NSKVOEnsureKeyWillNotify(id object, NSString *key);
*/
@interface
NSObject (NSKeyValueObservingPrivate)
- (Class)_underlyingClass;
- (void)_notifyObserversOfChangeForKey:(NSString *)key
oldValue:(id)oldValue
newValue:(id)newValue;

View file

@ -53,7 +53,7 @@
typedef void (^DispatchChangeBlock)(_NSKVOKeyObserver *);
NSString *
static NSString *
_NSKVCSplitKeypath(NSString *keyPath, NSString *__autoreleasing *pRemainder)
{
NSRange result = [keyPath rangeOfString:@"."];
@ -333,7 +333,11 @@ _addNestedObserversAndOptionallyDependents(_NSKVOKeyObserver *keyObserver,
// Aggregate all keys whose values will affect us.
if (dependents)
{
Class cls = [object class];
// Make sure to retrieve the underlying class of the observee.
// This is just [object class] for an NSObject derived class.
// When observing an object through a proxy, we instead use KVC
// to optain the underlying class.
Class cls = [object _underlyingClass];
NSSet *valueInfluencingKeys = [cls keyPathsForValuesAffectingValueForKey: key];
if (valueInfluencingKeys.count > 0)
{
@ -1123,6 +1127,11 @@ static const NSString *_NSKeyValueChangeOldSetValue
@implementation
NSObject (NSKeyValueObservingPrivate)
- (Class)_underlyingClass
{
return [self class];
}
- (void)_notifyObserversOfChangeForKey: (NSString *)key
oldValue: (id)oldValue
newValue: (id)newValue
@ -1254,3 +1263,19 @@ NSSet (NSKeyValueObserving)
@end
#pragma endregion
#pragma region KVO forwarding - NSProxy category
@implementation
NSProxy (NSKeyValueObserving)
- (Class)_underlyingClass
{
// Retrieve the underlying class via KVC
// Note that we assume that the class is KVC-compliant, when KVO is used
return [(NSObject *)self valueForKey: @"class"];
}
@end
#pragma endregion

View file

@ -476,7 +476,7 @@ GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplPointer, void *);
break; \
}
SEL
static SEL
KVCSetterForPropertyName(NSObject *self, const char *key)
{
SEL sel = nil;
@ -649,7 +649,7 @@ _NSKVOEnsureUnorderedCollectionWillNotify(id object, NSString *key,
}
}
char *
static char *
mutableBufferFromString(NSString *string)
{
NSUInteger lengthInBytes = [string length] + 1;
@ -666,6 +666,8 @@ void
_NSKVOEnsureKeyWillNotify(id object, NSString *key)
{
char *rawKey;
Class cls;
Class underlyingCls;
// Since we cannot replace the isa of tagged pointer objects, we can't swizzle
// them.
@ -674,8 +676,17 @@ _NSKVOEnsureKeyWillNotify(id object, NSString *key)
return;
}
cls = [object class];
underlyingCls = [object _underlyingClass];
// If cls differs from underlyingCls, object is actually a proxy.
// Retrieve the underlying object with KVC.
if (cls != underlyingCls)
{
object = [object valueForKey: @"self"];
}
// A class is allowed to decline automatic swizzling for any/all of its keys.
if (![[object class] automaticallyNotifiesObserversForKey: key])
if (![underlyingCls automaticallyNotifiesObserversForKey: key])
{
return;
}

View file

@ -49,6 +49,7 @@
*/
#import "Foundation/NSString.h"
#import "GSPrivate.h"
id
valueForKeyWithCaching(id obj, NSString *aKey);
valueForKeyWithCaching(id obj, NSString *aKey) GS_ATTRIB_PRIVATE;

View file

@ -426,7 +426,7 @@ _getBoxedBlockForMethod(NSString *key, Method method, SEL sel, uint64_t version)
// resolveInstanceMethod:].
//
// objc_slot2 has the same struct layout as objc_method.
Method _Nullable _class_getMethodRecursive(Class aClass, SEL aSelector,
static Method _Nullable _class_getMethodRecursive(Class aClass, SEL aSelector,
uint64_t *version)
{
struct objc_slot2 *slot;

View file

@ -83,6 +83,18 @@
#endif
#endif
/* objc_enumerationMutation() is called whenever a collection mutates in the
* middle of fast enumeration. We need to have this defined and linked into
* any code that uses fast enumeration, so we define it in NSObject.h
* This symbol is exported to take precedence over the weak symbol provided
* by the runtime library.
*/
GS_EXPORT void objc_enumerationMutation(id obj)
{
[NSException raise: NSGenericException
format: @"Collection %@ was mutated while being enumerated", obj];
}
/* platforms which do not support weak */
#if defined (__WIN32)
#define WEAK_ATTRIBUTE

View file

@ -44,6 +44,7 @@ static Class concreteClass = Nil;
void **_contents;
unsigned _capacity;
unsigned _grow_factor;
unsigned long _version;
}
@end
@ -203,7 +204,7 @@ static Class concreteClass = Nil;
{
NSInteger count;
state->mutationsPtr = (unsigned long *)&state->mutationsPtr;
state->mutationsPtr = state->mutationsPtr;
count = MIN(len, [self count] - state->state);
if (count > 0)
{
@ -312,6 +313,8 @@ static Class concreteClass = Nil;
NSUInteger insert = 0;
NSUInteger i;
_version++;
/* We can't use memmove here for __weak pointers, because that would omit the
* required read barriers. We could use objc_memmoveCollectable() for strong
* pointers, but we may as well use the same code path for everything
@ -329,6 +332,7 @@ static Class concreteClass = Nil;
}
}
_count = insert;
_version++;
}
- (id) copyWithZone: (NSZone*)zone
@ -355,6 +359,16 @@ static Class concreteClass = Nil;
return _count;
}
- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state
objects: (__unsafe_unretained id[])stackbuf
count: (NSUInteger)len
{
state->mutationsPtr = &_version;
return [super countByEnumeratingWithState: state
objects: stackbuf
count: len];
}
- (void) dealloc
{
int i;
@ -529,6 +543,7 @@ static Class concreteClass = Nil;
- (void) removePointerAtIndex: (NSUInteger)index
{
_version++;
if (index >= _count)
{
[self _raiseRangeExceptionWithIndex: index from: _cmd];
@ -539,15 +554,18 @@ static Class concreteClass = Nil;
pointerFunctionsMove(&_pf, &_contents[index-1], &_contents[index]);
}
_contents[--_count] = NULL;
_version++;
}
- (void) replacePointerAtIndex: (NSUInteger)index withPointer: (void*)item
{
_version++;
if (index >= _count)
{
[self _raiseRangeExceptionWithIndex: index from: _cmd];
}
pointerFunctionsReplace(&_pf, &_contents[index], item);
_version++;
}
@ -555,6 +573,7 @@ static Class concreteClass = Nil;
- (void) setCount: (NSUInteger)count
{
_version++;
if (count > _count)
{
#if ZEROING
@ -629,6 +648,7 @@ static Class concreteClass = Nil;
pointerFunctionsRelinquish(&_pf, &_contents[_count]);
}
}
_version++;
}
@end

View file

@ -303,7 +303,7 @@ progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
*
* libcurl does not unfold HTTP "folded headers" (deprecated since RFC 7230).
*/
size_t
static size_t
header_callback(char *ptr, size_t size, size_t nitems, void *userdata)
{
NSURLSessionTask *task;
@ -689,7 +689,7 @@ header_callback(char *ptr, size_t size, size_t nitems, void *userdata)
} /* header_callback */
/* CURLOPT_READFUNCTION: read callback for data uploads */
size_t
static size_t
read_callback(char *buffer, size_t size, size_t nitems, void *userdata)
{
NSURLSession *session;

View file

@ -31,9 +31,6 @@
#import "GSInternal.h"
GS_PRIVATE_INTERNAL(NSXMLElement)
extern void cleanup_namespaces(xmlNodePtr node, xmlNsPtr ns);
extern void ensure_oldNs(xmlNodePtr node);
@implementation NSXMLElement
- (void) dealloc

View file

@ -25,6 +25,7 @@
#define _INCLUDED_NSXMLPRIVATE_H
#import "common.h"
#import "GSPrivate.h"
#ifdef HAVE_LIBXML
@ -65,6 +66,9 @@
*/
#define XMLSTRING(X) ((const unsigned char*)[X UTF8String])
void cleanup_namespaces(xmlNodePtr node, xmlNsPtr ns) GS_ATTRIB_PRIVATE;
BOOL ensure_oldNs(xmlNodePtr node) GS_ATTRIB_PRIVATE;
inline static unsigned char *XMLStringCopy(NSString *source)
{
char *xmlstr;

View file

@ -1,37 +1,68 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSOrderedSet.h>
#import <Foundation/Foundation.h>
#import "ObjectTesting.h"
#import "../../../Source/GSFastEnumeration.h"
static SEL add;
static SEL set;
static SEL key;
@implementation NSPointerArray (TestHelpers)
- (void) addObject: (id)anObject
{
[self addPointer: anObject];
}
- (void) removeObject: (id)anObject
{
int i = [self count];
while (i-- > 0)
{
if ([self pointerAtIndex: i] == (void*)anObject)
{
[self removePointerAtIndex: i];
}
}
}
@end
void fast_enumeration_mutation_add(id mutableCollection)
{
NSUInteger i = 0;
NSUInteger i = 0;
NSUInteger c = [mutableCollection count]/2;
FOR_IN(id, o, mutableCollection)
if (i == [mutableCollection count]/2) {
if ([mutableCollection isKindOfClass: [NSMutableDictionary class]]) {
[mutableCollection setObject: @"boom" forKey: @"boom"];
} else {
[mutableCollection addObject: @"boom"];
if (i == c)
{
if ([mutableCollection respondsToSelector: set])
{
[mutableCollection setObject: @"boom" forKey: @"boom"];
}
else
{
[mutableCollection addObject: @"boom"];
}
}
}
i++;
END_FOR_IN(mutableCollection)
}
void fast_enumeration_mutation_remove(id mutableCollection)
{
NSUInteger i = 0;
NSUInteger i = 0;
NSUInteger c = [mutableCollection count]/2;
FOR_IN(id, o, mutableCollection)
if (i == [mutableCollection count]/2) {
if ([mutableCollection isKindOfClass: [NSMutableDictionary class]]) {
[mutableCollection removeObjectForKey: o];
} else {
[mutableCollection removeObject: o];
if (i == c)
{
if ([mutableCollection respondsToSelector: key])
{
[mutableCollection removeObjectForKey: o];
}
else
{
[mutableCollection removeObject: o];
}
}
}
i++;
END_FOR_IN(mutableCollection)
}
@ -50,7 +81,20 @@ void test_fast_enumeration(id collection, NSArray *objects)
}
PASS_EQUAL(returnedObjects, objects, "fast enumeration returns all objects")
id mutableCollection = [collection mutableCopy];
id mutableCollection;
if ([collection respondsToSelector: @selector(mutableCopyWithZone:)])
{
mutableCollection = AUTORELEASE([collection mutableCopy]);
}
else if ([collection respondsToSelector: add]
|| [collection respondsToSelector: set])
{
mutableCollection = collection; // It has a method to mutate it
}
else
{
return; // No mutable version
}
PASS_EXCEPTION(
fast_enumeration_mutation_add(mutableCollection),
NSGenericException,
@ -59,18 +103,22 @@ void test_fast_enumeration(id collection, NSArray *objects)
fast_enumeration_mutation_remove(mutableCollection),
NSGenericException,
"Fast enumeration mutation remove properly calls @\"NSGenericException\"")
[mutableCollection release];
}
int main()
{
NSAutoreleasePool *arp = [NSAutoreleasePool new];
NSMutableArray *objects = [NSMutableArray array];
int i;
for (i = 0; i < 10000; i++) {
[objects addObject: [NSString stringWithFormat: @"%.4d", i]];
}
NSAutoreleasePool *arp = [NSAutoreleasePool new];
NSMutableArray *objects = [NSMutableArray array];
int i;
add = @selector(addObject:);
set = @selector(setObject:forKey:);
key = @selector(removeObjectForKey:);
for (i = 0; i < 1000; i++)
{
[objects addObject: [NSString stringWithFormat: @"%.4d", i]];
}
START_SET("NSArray")
id array = [NSArray arrayWithArray: objects];
@ -92,6 +140,30 @@ int main()
test_fast_enumeration(dict, objects);
END_SET("NSDictionary")
START_SET("NSMapTable")
id map = [NSMapTable strongToStrongObjectsMapTable];
FOR_IN(id, o, objects)
[map setObject: o forKey: o];
END_FOR_IN(objects)
test_fast_enumeration(map, objects);
END_SET("NSMapTable")
START_SET("NSHashTable")
id table = [NSHashTable weakObjectsHashTable];
FOR_IN(id, o, objects)
[table addObject: o];
END_FOR_IN(objects)
test_fast_enumeration(table, objects);
END_SET("NSHashTable")
START_SET("NSPointerArray")
id array = [NSPointerArray weakObjectsPointerArray];
FOR_IN(id, o, objects)
[array addPointer: o];
END_FOR_IN(objects)
test_fast_enumeration(array, objects);
END_SET("NSPointerArray")
[arp release]; arp = nil;
return 0;
}

View file

@ -96,9 +96,41 @@
@end
@interface Wrapper : NSObject
{
TProxy *_proxy;
}
- (instancetype) initWithProxy: (TProxy *) proxy;
- (TProxy *) proxy;
@end
@implementation Wrapper
- (instancetype) initWithProxy: (TProxy *) proxy
{
self = [super init];
if (self)
{
_proxy = proxy;
}
return self;
}
- (TProxy *) proxy
{
return _proxy;
}
@end
@interface Observer: NSObject
{
int count;
NSArray *keys;
}
- (void)runTest;
@ -107,10 +139,13 @@
@implementation Observer
- (void)runTest
- (void)simpleKeypathTest
{
Observee *obj = [[Observee alloc] init];
TProxy *proxy = [[TProxy alloc] initWithProxiedObject:obj];
keys = [NSArray arrayWithObjects: @"derivedName", @"name", nil];
count = 0;
[(Observee *)proxy addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
[(Observee *)proxy addObserver:self forKeyPath:@"derivedName" options:NSKeyValueObservingOptionNew context:NULL];
@ -128,21 +163,50 @@
[obj release];
}
- (void)nestedKeypathTest
{
Observee *obj = [[Observee alloc] init];
TProxy *proxy = [[TProxy alloc] initWithProxiedObject:obj];
Wrapper *w = [[Wrapper alloc] initWithProxy: proxy];
keys = [NSArray arrayWithObjects: @"proxy.derivedName", @"proxy.name", nil];
count = 0;
[w addObserver:self forKeyPath:@"proxy.name" options:NSKeyValueObservingOptionNew context:NULL];
[w addObserver:self forKeyPath:@"proxy.derivedName" options:NSKeyValueObservingOptionNew context:NULL];
[((Observee *)proxy) setName: @"MOO"];
PASS(count == 2, "Got two change notifications");
[obj setName: @"BAH"];
PASS(count == 4, "Got two change notifications");
[w removeObserver:self forKeyPath:@"proxy.name" context:NULL];
[w removeObserver:self forKeyPath:@"proxy.derivedName" context:NULL];
[w release];
[proxy release];
[obj release];
count = 0;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
count += 1;
switch (count) {
case 1:
PASS_EQUAL(keyPath, @"derivedName", "change notification for dependent key 'derivedName' is emitted first");
PASS_EQUAL(keyPath, [keys objectAtIndex: 0], "change notification for dependent key 'derivedName' is emitted first");
break;
case 2:
PASS_EQUAL(keyPath, @"name", "'name' change notification for proxy is second");
PASS_EQUAL(keyPath, [keys objectAtIndex: 1], "'name' change notification for proxy is second");
break;
case 3:
PASS_EQUAL(keyPath, @"derivedName", "'derivedName' change notification for object is third");
PASS_EQUAL(keyPath, [keys objectAtIndex: 0], "'derivedName' change notification for object is third");
break;
case 4:
PASS_EQUAL(keyPath, @"name", "'name' change notification for object is fourth");
PASS_EQUAL(keyPath, [keys objectAtIndex: 1], "'name' change notification for object is fourth");
break;
default:
PASS(0, "unexpected -[Observer observeValueForKeyPath:ofObject:change:context:] callback");
@ -154,15 +218,15 @@
int
main(int argc, char *argv[])
{
NSAutoreleasePool *arp = [NSAutoreleasePool new];
START_SET("KVO Proxy Tests")
Observer *obs = [Observer new];
testHopeful = YES;
[obs runTest];
[obs simpleKeypathTest];
[obs nestedKeypathTest];
testHopeful = NO;
[obs release];
DESTROY(arp);
END_SET("KVO Proxy Tests")
return 0;
}