diff --git a/.gitignore b/.gitignore index 056c6a9a96..5a0e14323d 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file +Source/BaseAdditions.gsdoc diff --git a/Documentation/install.texi b/Documentation/install.texi index fce471a952..af8b9d034f 100644 --- a/Documentation/install.texi +++ b/Documentation/install.texi @@ -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 diff --git a/Documentation/manual/ExceptionHandling.texi b/Documentation/manual/ExceptionHandling.texi index b55a17bb33..d46f110db1 100644 --- a/Documentation/manual/ExceptionHandling.texi +++ b/Documentation/manual/ExceptionHandling.texi @@ -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 diff --git a/Documentation/manual/GNUstepMake.texi b/Documentation/manual/GNUstepMake.texi index 51551b64b8..4ad0c0167c 100644 --- a/Documentation/manual/GNUstepMake.texi +++ b/Documentation/manual/GNUstepMake.texi @@ -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 diff --git a/Documentation/manual/WorkingWithObjects.texi b/Documentation/manual/WorkingWithObjects.texi index ff24f11753..faac28cfe5 100644 --- a/Documentation/manual/WorkingWithObjects.texi +++ b/Documentation/manual/WorkingWithObjects.texi @@ -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;} diff --git a/Documentation/readme.texi b/Documentation/readme.texi index 12902cf3aa..73f76a8116 100644 --- a/Documentation/readme.texi +++ b/Documentation/readme.texi @@ -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 diff --git a/Headers/CoreFoundation/CFCGTypes.h b/Headers/CoreFoundation/CFCGTypes.h index 13cef938a5..959edc99fb 100644 --- a/Headers/CoreFoundation/CFCGTypes.h +++ b/Headers/CoreFoundation/CFCGTypes.h @@ -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 diff --git a/Source/NSEnumerator.m b/Source/NSEnumerator.m index cf6735b33e..96ca2e365b 100644 --- a/Source/NSEnumerator.m +++ b/Source/NSEnumerator.m @@ -97,12 +97,3 @@ - (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state } @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]; -} diff --git a/Source/NSKVOInternal.h b/Source/NSKVOInternal.h index 7748f0defc..60b3dd0cfd 100644 --- a/Source/NSKVOInternal.h +++ b/Source/NSKVOInternal.h @@ -44,13 +44,15 @@ /* This Key Value Observing Implementation is tied to libobjc2 */ -#import -#import -#import -#import -#import -#import -#import +#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; diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index 9dd31562c7..2b5f7a381c 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -53,7 +53,7 @@ This code is licensed under the MIT License (MIT). typedef void (^DispatchChangeBlock)(_NSKVOKeyObserver *); -NSString * +static NSString * _NSKVCSplitKeypath(NSString *keyPath, NSString *__autoreleasing *pRemainder) { NSRange result = [keyPath rangeOfString:@"."]; @@ -333,7 +333,11 @@ - (bool) isEmpty // 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 @@ - (void)didChangeValueForKey: (NSString *)key @implementation NSObject (NSKeyValueObservingPrivate) +- (Class)_underlyingClass +{ + return [self class]; +} + - (void)_notifyObserversOfChangeForKey: (NSString *)key oldValue: (id)oldValue newValue: (id)newValue @@ -1254,3 +1263,19 @@ - (void)removeObserver: (id)observer forKeyPath:(NSString *)keyPath @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 diff --git a/Source/NSKVOSwizzling.m b/Source/NSKVOSwizzling.m index 5b9965baaa..2837210484 100644 --- a/Source/NSKVOSwizzling.m +++ b/Source/NSKVOSwizzling.m @@ -476,7 +476,7 @@ static void funcName(id self, SEL _cmd, type val) \ break; \ } -SEL +static SEL KVCSetterForPropertyName(NSObject *self, const char *key) { SEL sel = nil; @@ -649,7 +649,7 @@ static void funcName(id self, SEL _cmd, type val) \ } } -char * +static char * mutableBufferFromString(NSString *string) { NSUInteger lengthInBytes = [string length] + 1; @@ -666,6 +666,8 @@ static void funcName(id self, SEL _cmd, type val) \ _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 @@ static void funcName(id self, SEL _cmd, type val) \ 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; } diff --git a/Source/NSKeyValueCoding+Caching.h b/Source/NSKeyValueCoding+Caching.h index 0241975407..aff3455c16 100644 --- a/Source/NSKeyValueCoding+Caching.h +++ b/Source/NSKeyValueCoding+Caching.h @@ -49,6 +49,7 @@ */ #import "Foundation/NSString.h" +#import "GSPrivate.h" id -valueForKeyWithCaching(id obj, NSString *aKey); \ No newline at end of file +valueForKeyWithCaching(id obj, NSString *aKey) GS_ATTRIB_PRIVATE; diff --git a/Source/NSKeyValueCoding+Caching.m b/Source/NSKeyValueCoding+Caching.m index 11643c8faf..6a054d268f 100644 --- a/Source/NSKeyValueCoding+Caching.m +++ b/Source/NSKeyValueCoding+Caching.m @@ -426,7 +426,7 @@ static id _fnname(struct _KVCCacheSlot *slot, id obj) \ // 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; diff --git a/Source/NSObject.m b/Source/NSObject.m index eb74ca365b..4f6eb259d7 100644 --- a/Source/NSObject.m +++ b/Source/NSObject.m @@ -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 diff --git a/Source/NSPointerArray.m b/Source/NSPointerArray.m index ccc785f0a0..d5faa1d4e2 100644 --- a/Source/NSPointerArray.m +++ b/Source/NSPointerArray.m @@ -44,6 +44,7 @@ @interface NSConcretePointerArray : NSPointerArray void **_contents; unsigned _capacity; unsigned _grow_factor; + unsigned long _version; } @end @@ -203,7 +204,7 @@ - (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state { 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 @@ - (void) compact 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 @@ - (void) compact } } _count = insert; + _version++; } - (id) copyWithZone: (NSZone*)zone @@ -355,6 +359,16 @@ - (NSUInteger) count 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 @@ - (NSPointerFunctions*) pointerFunctions - (void) removePointerAtIndex: (NSUInteger)index { + _version++; if (index >= _count) { [self _raiseRangeExceptionWithIndex: index from: _cmd]; @@ -539,15 +554,18 @@ - (void) removePointerAtIndex: (NSUInteger)index 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 @@ - (void) replacePointerAtIndex: (NSUInteger)index withPointer: (void*)item - (void) setCount: (NSUInteger)count { + _version++; if (count > _count) { #if ZEROING @@ -629,6 +648,7 @@ - (void) setCount: (NSUInteger)count pointerFunctionsRelinquish(&_pf, &_contents[_count]); } } + _version++; } @end diff --git a/Source/NSURLSessionTask.m b/Source/NSURLSessionTask.m index 1ca664564d..7adc88370f 100644 --- a/Source/NSURLSessionTask.m +++ b/Source/NSURLSessionTask.m @@ -303,7 +303,7 @@ @interface _GSMutableInsensitiveDictionary : NSMutableDictionary * * 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 @@ @interface _GSMutableInsensitiveDictionary : NSMutableDictionary } /* 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; diff --git a/Source/NSXMLElement.m b/Source/NSXMLElement.m index 7fe43cfa60..02b5941c70 100644 --- a/Source/NSXMLElement.m +++ b/Source/NSXMLElement.m @@ -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 diff --git a/Source/NSXMLPrivate.h b/Source/NSXMLPrivate.h index 63f41d1200..29a5b83009 100644 --- a/Source/NSXMLPrivate.h +++ b/Source/NSXMLPrivate.h @@ -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; diff --git a/Tests/base/NSFastEnumeration/basic.m b/Tests/base/NSFastEnumeration/basic.m index 9fd52d4f04..a2e9e0e3da 100644 --- a/Tests/base/NSFastEnumeration/basic.m +++ b/Tests/base/NSFastEnumeration/basic.m @@ -1,37 +1,68 @@ -#import -#import -#import -#import -#import +#import #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; } diff --git a/Tests/base/NSKVOSupport/proxy.m b/Tests/base/NSKVOSupport/proxy.m index 30a9dffa19..3aa1a3b823 100644 --- a/Tests/base/NSKVOSupport/proxy.m +++ b/Tests/base/NSKVOSupport/proxy.m @@ -96,9 +96,41 @@ - (void) dealloc @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 @@ - (void)runTest; @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 @@ - (void)runTest [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 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N 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; }