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
6 changes: 6 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
2026-06-28 Todd White <todd.white@thalion.global>
* Source/GSCArray.c (GSCArrayQuickSort): Pass the correct partition
lengths to the recursive calls; the right partition length was one
too large, reading one element past the end of the array.
* Tests/CFArray/mutablearray.m: Regression test sorting >16 values.

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

Expand Down
4 changes: 2 additions & 2 deletions Source/GSCArray.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ GSCArrayQuickSort (const void **array, CFIndex length,
}
GS_EXCHANGE_VALUES (array[stored], array[length - 1]);

GSCArrayQuickSort (array, stored - 1, comparator, context);
GSCArrayQuickSort (array + stored + 1, length - stored, comparator,
GSCArrayQuickSort (array, stored, comparator, context);
GSCArrayQuickSort (array + stored + 1, length - stored - 1, comparator,
context);
}
GSCArrayInsertionSort (array, length, comparator, context);
Expand Down
46 changes: 45 additions & 1 deletion Tests/CFArray/mutablearray.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "CoreFoundation/CFArray.h"
#include "../CFTesting.h"
#include <string.h>
#include <stdlib.h>

#define ARRAY_SIZE 5
const CFIndex array[ARRAY_SIZE] = { 5, 2, 3, 4, 1 };
Expand Down Expand Up @@ -41,7 +42,50 @@ int main (void)

n = CFArrayBSearchValues (ma, CFRangeMake(0, len), (const void*)6, comp, NULL);
PASS_CF(n == 5, "Index of value between values is %d.", (int)n);


{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Let's use a randomly shuffled range [0; n] as the test input data, and test for a few different values of n.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in f457ca8. The test now sorts a fixed-seed Fisher–Yates shuffle of a contiguous range for sizes 1,2,5,15,16,17,33,64,100 (straddling the insertion-sort/quicksort threshold) and asserts the exact ascending result each time.

One note: I build the input via a pre-sized mutable copy instead of incremental appends, because growing a mutable CFArray across its capacity boundary trips a separate out-of-bounds write in CFArrayCheckCapacityAndGrow/CFArrayReplaceValues (unrelated to this fix). Happy to file that separately.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction on the note above: that CFArray grow-path overflow is not unreported — it is already fixed in #45 (recompute the destination pointer after CFArrayCheckCapacityAndGrow reallocs, and grow to at least the requested capacity). I re-root-caused it here (use-after-realloc write in CFArrayReplaceValues) and confirmed #45 resolves it under ASan. This test just pre-sizes to keep the two PRs independent.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does the test pass on CoreFoundation on MacOS?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Someone else will have to test - I have no access to Mac hardware.

/* Sort a shuffled permutation of 1..sz for a range of sizes that straddle
the 16-element threshold between the insertion sort and the quicksort
path (the latter used to read one element past the end of the array).
The sorted result must be exactly 1, 2, ..., sz. */
const CFIndex sizes[] = { 1, 2, 5, 15, 16, 17, 33, 64, 100 };
const void *shuf[100];
CFIndex s;

srand (1);
for (s = 0; s < (CFIndex)(sizeof(sizes) / sizeof(sizes[0])); s++)
{
CFIndex sz = sizes[s];
CFArrayRef src;
CFMutableArrayRef big;
Boolean ordered = true;
CFIndex i;

for (i = 0; i < sz; i++)
shuf[i] = (const void *)(CFIndex)(i + 1);
for (i = sz - 1; i > 0; i--)
{
CFIndex j = rand () % (i + 1);
const void *t = shuf[i];
shuf[i] = shuf[j];
shuf[j] = t;
}

src = CFArrayCreate (NULL, shuf, sz, NULL);
big = CFArrayCreateMutableCopy (NULL, sz, src);
CFArraySortValues (big, CFRangeMake (0, sz), comp, NULL);

for (i = 0; i < sz; i++)
if ((CFIndex) CFArrayGetValueAtIndex (big, i) != i + 1)
ordered = false;

PASS_CF(ordered && CFArrayGetCount (big) == sz,
"Shuffled range of %d values sorts to ascending order.", (int)sz);
CFRelease (big);
CFRelease (src);
}
}

return 0;
}