fixup for removal from mutable array

This commit is contained in:
rfm 2025-01-01 13:39:13 +00:00
parent 46d19b5dd5
commit bbd1f03183
3 changed files with 235 additions and 43 deletions

View file

@ -1,3 +1,12 @@
2025-01-01 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSKeyValueMutableArray.m: Fix mutable array support so that
we can remove items from multiple indices at the same time and avoid
problems where the array being accessed is set by copy rendering our
cached infromation invalid (issue #480).
* Tests/base/KVC/remove.m: adapted test by Rupert Daniel for removal
of items from mutable array.
2024-12-31 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSTimeZone.m: Add GNUSTEP_BUILTIN_TZ environment variable to

View file

@ -30,14 +30,15 @@
@interface NSKeyValueMutableArray : NSMutableArray
{
@protected
id object;
NSString *key;
id object;
NSString *key;
NSMutableArray *array;
BOOL otherChangeInProgress;
BOOL notifiesObservers;
}
+ (NSKeyValueMutableArray *) arrayForKey: (NSString *)aKey ofObject: (id)anObject;
+ (NSKeyValueMutableArray *) arrayForKey: (NSString*)aKey
ofObject: (id)anObject;
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject;
@end
@ -51,10 +52,10 @@
}
+ (id) arrayForKey: (NSString *)aKey ofObject: (id)anObject
withCapitalizedKey: (const char *)capitalized;
withCapitalizedKey: (const char *)capitalized;
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject
withCapitalizedKey: (const char *)capitalized;
withCapitalizedKey: (const char *)capitalized;
@end
@ -65,10 +66,10 @@
}
+ (id) arrayForKey: (NSString *)aKey ofObject: (id)anObject
withCapitalizedKey: (const char *)capitalized;
withCapitalizedKey: (const char *)capitalized;
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject
withCapitalizedKey: (const char *)capitalized;
withCapitalizedKey: (const char *)capitalized;
@end
@ -84,6 +85,12 @@
@end
/* NB. For removal of objects we can remove multiple objects at the same
* time and the notifications are sent with an NSIndexSet specifying the
* indices which are being altered. We therefore funnuel all the other
* removal methods through the -removeObjectsAtIndexes: method so that
* the subclasses only need to implement that one themselves.
*/
@implementation NSKeyValueMutableArray
+ (NSKeyValueMutableArray *) arrayForKey: (NSString *)aKey
@ -93,6 +100,7 @@
unsigned size = [aKey maximumLengthOfBytesUsingEncoding:
NSUTF8StringEncoding];
char keybuf[size + 1];
[aKey getCString: keybuf
maxLength: size + 1
encoding: NSUTF8StringEncoding];
@ -126,8 +134,8 @@
object = anObject;
key = [aKey copy];
otherChangeInProgress = NO;
notifiesObservers =
[[anObject class] automaticallyNotifiesObserversForKey: aKey];
notifiesObservers
= [[anObject class] automaticallyNotifiesObserversForKey: aKey];
}
return self;
}
@ -155,9 +163,21 @@
[self insertObject: anObject atIndex: [self count]];
}
- (void) removeFirstObject
{
NSUInteger count = [self count];
if (0 == count)
{
return;
}
[self removeObjectAtIndex: 0];
}
- (void) removeLastObject
{
NSUInteger count = [self count];
if (0 == count)
{
return;
@ -165,19 +185,78 @@
[self removeObjectAtIndex: (count - 1)];
}
- (void) removeObject: (id)anObject
{
NSUInteger count = [self count];
if (count > 0)
{
NSMutableIndexSet *indexes = nil;
while (count-- > 0)
{
if ([[self objectAtIndex: count] isEqual: anObject])
{
if (nil == indexes)
{
indexes = [NSMutableIndexSet indexSet];
}
[indexes addIndex: count];
}
}
if (indexes)
{
[self removeObjectsAtIndexes: indexes];
}
}
}
- (void) removeObjectAtIndex: (NSUInteger)index
{
if (index != NSNotFound)
{
[self removeObjectsAtIndexes: [NSIndexSet indexSetWithIndex: index]];
}
}
- (void) removeObjectsFromIndices: (NSUInteger*)indices
numIndices: (NSUInteger)count
{
if (count > 0)
{
NSMutableIndexSet *indexes = nil;
while (count-- > 0)
{
if (indices[count] != NSNotFound)
{
if (nil == indexes)
{
indexes = [NSMutableIndexSet indexSet];
}
[indexes addIndex: indices[count]];
}
}
if (indexes)
{
[self removeObjectsAtIndexes: indexes];
}
}
}
@end
@implementation NSKeyValueFastMutableArray
+ (id) arrayForKey: (NSString *)aKey ofObject: (id)anObject
withCapitalizedKey: (const char *)capitalized
withCapitalizedKey: (const char *)capitalized
{
return [[[self alloc] initWithKey: aKey ofObject: anObject
withCapitalizedKey: capitalized] autorelease];
}
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject
withCapitalizedKey: (const char *)capitalized
withCapitalizedKey: (const char *)capitalized
{
SEL insert;
SEL remove;
@ -227,24 +306,33 @@
[super dealloc];
}
- (void) removeObjectAtIndex: (NSUInteger)index
- (void) removeObjectsAtIndexes: (NSIndexSet*)indexes
{
NSIndexSet *indexes = nil;
NSUInteger index = [indexes lastIndex];
if (nil == indexes || NSNotFound == index)
{
return;
}
if (notifiesObservers && !otherChangeInProgress)
{
indexes = [NSIndexSet indexSetWithIndex: index];
[object willChange: NSKeyValueChangeRemoval
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
}
[removeObjectInvocation setArgument: &index atIndex: 2];
[removeObjectInvocation invoke];
while ((index = [indexes indexLessThanIndex: index]) != NSNotFound)
{
[removeObjectInvocation setArgument: &index atIndex: 2];
[removeObjectInvocation invoke];
}
if (notifiesObservers && !otherChangeInProgress)
{
[object didChange: NSKeyValueChangeRemoval
valuesAtIndexes: indexes
forKey: key];
forKey: key];
}
}
@ -256,8 +344,8 @@
{
indexes = [NSIndexSet indexSetWithIndex: index];
[object willChange: NSKeyValueChangeInsertion
valuesAtIndexes: indexes
forKey: key];
valuesAtIndexes: indexes
forKey: key];
}
[insertObjectInvocation setArgument: &anObject atIndex: 2];
[insertObjectInvocation setArgument: &index atIndex: 3];
@ -265,7 +353,7 @@
if (notifiesObservers && !otherChangeInProgress)
{
[object didChange: NSKeyValueChangeInsertion
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
}
}
@ -279,8 +367,8 @@
otherChangeInProgress = YES;
indexes = [NSIndexSet indexSetWithIndex: index];
[object willChange: NSKeyValueChangeReplacement
valuesAtIndexes: indexes
forKey: key];
valuesAtIndexes: indexes
forKey: key];
}
if (replaceObjectInvocation)
{
@ -296,26 +384,25 @@
if (notifiesObservers && !otherChangeInProgress)
{
[object didChange: NSKeyValueChangeReplacement
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
otherChangeInProgress = NO;
}
}
@end
@implementation NSKeyValueSlowMutableArray
+ (id) arrayForKey: (NSString *)aKey ofObject: (id)anObject
withCapitalizedKey: (const char *)capitalized
withCapitalizedKey: (const char *)capitalized
{
return [[[self alloc] initWithKey: aKey ofObject: anObject
withCapitalizedKey: capitalized] autorelease];
withCapitalizedKey: capitalized] autorelease];
}
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject
withCapitalizedKey: (const char *)capitalized;
withCapitalizedKey: (const char *)capitalized;
{
SEL set = NSSelectorFromString([NSString stringWithFormat:
@ -337,21 +424,29 @@
return self;
}
- (void) removeObjectAtIndex: (NSUInteger)index
- (void) removeObjectsAtIndexes: (NSIndexSet*)indexes
{
NSIndexSet *indexes = nil;
NSMutableArray *temp;
NSUInteger index = [indexes lastIndex];
NSMutableArray *temp;
if (nil == indexes || NSNotFound == index)
{
return;
}
if (notifiesObservers && !otherChangeInProgress)
{
indexes = [NSIndexSet indexSetWithIndex: index];
[object willChange: NSKeyValueChangeRemoval
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
}
temp = [NSMutableArray arrayWithArray: [object valueForKey: key]];
[temp removeObjectAtIndex: index];
while ((index = [indexes indexLessThanIndex: index]) != NSNotFound)
{
[temp removeObjectAtIndex: index];
}
[setArrayInvocation setArgument: &temp atIndex: 2];
[setArrayInvocation invoke];
@ -359,21 +454,21 @@
if (notifiesObservers && !otherChangeInProgress)
{
[object didChange: NSKeyValueChangeRemoval
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
}
}
- (void) insertObject: (id)anObject atIndex: (NSUInteger)index
{
NSIndexSet *indexes = nil;
NSIndexSet *indexes = nil;
NSMutableArray *temp;
if (notifiesObservers && !otherChangeInProgress)
{
indexes = [NSIndexSet indexSetWithIndex: index];
[object willChange: NSKeyValueChangeInsertion
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
}
@ -386,7 +481,7 @@
if (notifiesObservers && !otherChangeInProgress)
{
[object didChange: NSKeyValueChangeInsertion
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
}
}
@ -400,7 +495,7 @@
{
indexes = [NSIndexSet indexSetWithIndex: index];
[object willChange: NSKeyValueChangeReplacement
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
}
@ -414,12 +509,11 @@
if (notifiesObservers && !otherChangeInProgress)
{
[object didChange: NSKeyValueChangeReplacement
valuesAtIndexes: indexes
valuesAtIndexes: indexes
forKey: key];
}
}
@end
@ -447,11 +541,11 @@
maxLength: size + 1
encoding: NSUTF8StringEncoding];
if (!GSObjCFindVariable (anObject, cKeyPtr, &type, &size, &offset))
found = GSObjCFindVariable (anObject, ++cKeyPtr, &type, &size, &offset);
if (!GSObjCFindVariable(anObject, cKeyPtr, &type, &size, &offset))
found = GSObjCFindVariable(anObject, ++cKeyPtr, &type, &size, &offset);
if (found)
{
array = GSObjCGetVal (anObject, cKeyPtr, NULL, type, size, offset);
array = GSObjCGetVal(anObject, cKeyPtr, NULL, type, size, offset);
}
else
{
@ -482,9 +576,14 @@
}
}
- (void) removeObjectAtIndex: (NSUInteger)index
- (void) removeObjectsAtIndexes: (NSIndexSet*)indexes
{
NSIndexSet *indexes = nil;
NSUInteger index = [indexes lastIndex];
if (nil == indexes || NSNotFound == index)
{
return;
}
if (notifiesObservers)
{
@ -494,6 +593,10 @@
forKey: key];
}
[array removeObjectAtIndex: index];
while ((index = [indexes indexLessThanIndex: index]) != NSNotFound)
{
[array removeObjectAtIndex: index];
}
if (notifiesObservers)
{
[object didChange: NSKeyValueChangeRemoval
@ -526,6 +629,7 @@
{
NSIndexSet *indexes = nil;
NSUInteger count = [array count];
if (0 == count)
{
return;

79
Tests/base/KVC/remove.m Normal file
View file

@ -0,0 +1,79 @@
#import "ObjectTesting.h"
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
{
NSArray *items;
}
@property (nonatomic, copy) NSArray *items;
- (void) addItem: (id)item;
- (NSArray*) items;
- (void) removeItem: (id)item;
- (void) setItems: (NSArray*)a;
@end
@implementation TestObject
- (void) dealloc
{
DESTROY(items);
DEALLOC
}
- (instancetype) init
{
if ((self = [super init]) != nil)
{
ASSIGN(items, [NSMutableArray array]);
}
return self;
}
- (void) addItem: (id)item
{
[[self mutableArrayValueForKeyPath: @"items"] addObject: item];
}
- (NSArray*) items
{
return items;
}
- (void) removeItem: (id)item
{
[[self mutableArrayValueForKeyPath: @"items"] removeObject: item];
}
- (void) setItems: (NSArray*)a
{
ASSIGNCOPY(items, a);
}
@end
int main(int argc,char **argv)
{
ENTER_POOL
NSString *s1 = [NSString stringWithFormat: @"Moose1"];
NSString *s2 = [NSString stringWithFormat: @"Moose2"];
// Removing s1 then s2 works
TestObject *t1 = AUTORELEASE([[TestObject alloc] init]);
[t1 addItem: s1];
[t1 addItem: s2];
PASS_RUNS(({ [t1 removeItem: s1]; [t1 removeItem: s2]; }),
"array remove first t last")
// Removing s2 then s1 throws exception
TestObject *t2 = AUTORELEASE([[TestObject alloc] init]);
[t2 addItem: s1];
[t2 addItem: s2];
PASS_RUNS(({ [t2 removeItem: s2]; [t2 removeItem: s1]; }),
"array remove last to first")
LEAVE_POOL
return 0;
}