Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
2026-07-02 Rupert Daniel <rupert@algoriddim.com>
* Source/NSCFDictionary.m,
* Source/NSCFSet.m: Fix -countByEnumeratingWithState:objects:count:,
which created a fresh enumerator on every call so fast enumeration
never terminated. Iterate the backing hash table directly, keeping
the resume position in the enumeration state.
* Source/GSHashTable.c,
* Source/GSHashTable.h: Add GSHashTableGetKeysFromCursor(), an
allocation-free resumable key iterator used by the above.

2021-09-30 Frederik Seiffert <frederik@algoriddim.com>
* Source/CFDate.c: Fix logic in CFGregorianDateIsValid()

Expand Down
19 changes: 19 additions & 0 deletions Source/GSHashTable.c
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,25 @@ GSHashTableGetKeysAndValues (GSHashTableRef table, const void **keys,
}
}

CFIndex
GSHashTableGetKeysFromCursor (GSHashTableRef table, CFIndex *cursor,
const void **keybuf, CFIndex bufSize)
{
GSHashTableBucket *buckets = table->_buckets;
CFIndex capacity = table->_capacity;
CFIndex idx = *cursor;
CFIndex n = 0;

while (n < bufSize && idx < capacity)
{
if (buckets[idx].count > 0)
keybuf[n++] = buckets[idx].key;
++idx;
}
*cursor = idx;
return n;
}

const void *
GSHashTableGetValue (GSHashTableRef table, const void *key)
{
Expand Down
9 changes: 9 additions & 0 deletions Source/GSHashTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ GS_PRIVATE void
GSHashTableGetKeysAndValues (GSHashTableRef table, const void **keys,
const void **values);

/* Resumable key iterator. Writes up to bufSize keys into keybuf starting at
* bucket *cursor, advances *cursor past them, and returns the number written
* (0 once the table is exhausted). Reads live buckets in place and allocates
* nothing, so it is suitable as a fast-enumeration primitive.
Comment on lines +118 to +120

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* bucket *cursor, advances *cursor past them, and returns the number written
* (0 once the table is exhausted). Reads live buckets in place and allocates
* nothing, so it is suitable as a fast-enumeration primitive.
* bucket *cursor, advances *cursor past them, and returns the number of written keys
* (0 once the table is exhausted).

*/
GS_PRIVATE CFIndex
GSHashTableGetKeysFromCursor (GSHashTableRef table, CFIndex *cursor,
const void **keybuf, CFIndex bufSize);

GS_PRIVATE const void *GSHashTableGetValue (GSHashTableRef table,
const void *key);

Expand Down
18 changes: 13 additions & 5 deletions Source/NSCFDictionary.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include "NSCFType.h"
#include "CoreFoundation/CFDictionary.h"
#include "GSHashTable.h"

@interface NSCFDictionary : NSMutableDictionary
NSCFTYPE_VARS
Expand Down Expand Up @@ -136,11 +137,18 @@ - (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state
objects: (id[])stackbuf
count: (NSUInteger)len
{
NSEnumerator *enuM = [self keyEnumerator];

return [enuM countByEnumeratingWithState: state
objects: stackbuf
count: len];
/* Walk the buckets across calls, keeping the resume position in extra[0].
* Reading keys in place needs no per-call allocation, and returning 0 when
* exhausted ends the enumeration. */
CFIndex cursor = (state->state == 0) ? 0 : (CFIndex) state->extra[0];
CFIndex n = GSHashTableGetKeysFromCursor ((GSHashTableRef) self, &cursor,
(const void **) stackbuf,
(CFIndex) len);
state->extra[0] = (unsigned long) cursor;
state->itemsPtr = stackbuf;
state->mutationsPtr = (unsigned long *) self;
state->state += n;
return n;
}

- (void) setObject: anObject forKey: (id)aKey
Expand Down
18 changes: 12 additions & 6 deletions Source/NSCFSet.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include "NSCFType.h"
#include "CoreFoundation/CFSet.h"
#include "GSHashTable.h"

@interface NSCFSet : NSMutableSet
NSCFTYPE_VARS
Expand Down Expand Up @@ -108,12 +109,17 @@ - (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state
objects: (id*)stackbuf
count: (NSUInteger)len
{
// TODO: inefficient
NSEnumerator *enuM = [self objectEnumerator];

return [enuM countByEnumeratingWithState: state
objects: stackbuf
count: len];
/* Set elements are stored as the hash table's keys; enumerate them in place,
* keeping the resume position in extra[0]. */
CFIndex cursor = (state->state == 0) ? 0 : (CFIndex) state->extra[0];
CFIndex n = GSHashTableGetKeysFromCursor ((GSHashTableRef) self, &cursor,
(const void **) stackbuf,
(CFIndex) len);
state->extra[0] = (unsigned long) cursor;
state->itemsPtr = stackbuf;
state->mutationsPtr = (unsigned long *) self;
state->state += n;
return n;
}

- (void) addObject: (id)anObject
Expand Down
Loading