Skip to content

Commit e12b9df

Browse files
committed
Make sorting stable
Make user-exposed sorts stable, by storing the position of elements in the original array, and using those positions as a fallback comparison criterion. The base sort is still hybrid q/insert. The use of true/false comparison functions is deprecated (but still supported) and should be replaced by -1/0/1 comparison functions, driven by the <=> operator. RFC: https://wiki.php.net/rfc/stable_sorting Closes GH-5236.
1 parent f9462fe commit e12b9df

13 files changed

+393
-205
lines changed

UPGRADING

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,9 +437,19 @@ PHP 8.0 UPGRADE NOTES
437437
respect the inherited locale without an explicit setlocale() call. An
438438
explicit setlocale() call is now always required if you wish to change any
439439
locale component from the default.
440-
. Remove deprecated DES fallback in crypt(). If an unknown salt format is
440+
. Removed deprecated DES fallback in crypt(). If an unknown salt format is
441441
passed to crypt(), the function will fail with *0 instead of falling back
442442
to a weak DES hash now.
443+
. The result of sorting functions may have changed, if the array contains
444+
equal-comparing elements.
445+
. Sort comparison functions return true/false will now throw a deprecation
446+
warning and should be replaced with an implementation return an integer
447+
smaller than, equal to, or greater than zero.
448+
449+
// Replace
450+
usort($array, fn($a, $b) => $a > $b);
451+
// With
452+
usort($array, fn($a, $b) => $a <=> $b);
443453

444454
- Sysvmsg:
445455
. msg_get_queue() will now return an SysvMessageQueue object rather than a
@@ -584,6 +594,10 @@ PHP 8.0 UPGRADE NOTES
584594

585595
$proc = proc_open($command, [['pty'], ['pty'], ['pty']], $pipes);
586596

597+
. Sorting functions are now stable, which means that equal-comparing elements
598+
will retain their original order.
599+
RFC: https://wiki.php.net/rfc/stable_sorting
600+
587601
- Zip:
588602
. Extension updated to version 1.19.0
589603
. New ZipArchive::lastId property to get index value of last added entry.

Zend/zend_hash.c

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2453,15 +2453,15 @@ ZEND_API void zend_hash_bucket_swap(Bucket *p, Bucket *q)
24532453
zend_ulong h;
24542454
zend_string *key;
24552455

2456-
ZVAL_COPY_VALUE(&val, &p->val);
2456+
val = p->val;
24572457
h = p->h;
24582458
key = p->key;
24592459

2460-
ZVAL_COPY_VALUE(&p->val, &q->val);
2460+
p->val = q->val;
24612461
p->h = q->h;
24622462
p->key = q->key;
24632463

2464-
ZVAL_COPY_VALUE(&q->val, &val);
2464+
q->val = val;
24652465
q->h = h;
24662466
q->key = key;
24672467
}
@@ -2470,23 +2470,23 @@ ZEND_API void zend_hash_bucket_renum_swap(Bucket *p, Bucket *q)
24702470
{
24712471
zval val;
24722472

2473-
ZVAL_COPY_VALUE(&val, &p->val);
2474-
ZVAL_COPY_VALUE(&p->val, &q->val);
2475-
ZVAL_COPY_VALUE(&q->val, &val);
2473+
val = p->val;
2474+
p->val = q->val;
2475+
q->val = val;
24762476
}
24772477

24782478
ZEND_API void zend_hash_bucket_packed_swap(Bucket *p, Bucket *q)
24792479
{
24802480
zval val;
24812481
zend_ulong h;
24822482

2483-
ZVAL_COPY_VALUE(&val, &p->val);
2483+
val = p->val;
24842484
h = p->h;
24852485

2486-
ZVAL_COPY_VALUE(&p->val, &q->val);
2486+
p->val = q->val;
24872487
p->h = q->h;
24882488

2489-
ZVAL_COPY_VALUE(&q->val, &val);
2489+
q->val = val;
24902490
q->h = h;
24912491
}
24922492

@@ -2498,28 +2498,34 @@ ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, b
24982498
IS_CONSISTENT(ht);
24992499
HT_ASSERT_RC1(ht);
25002500

2501-
if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) { /* Doesn't require sorting */
2501+
if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) {
2502+
/* Doesn't require sorting */
25022503
return;
25032504
}
25042505

25052506
if (HT_IS_WITHOUT_HOLES(ht)) {
2506-
i = ht->nNumUsed;
2507+
/* Store original order of elements in extra space to allow stable sorting. */
2508+
for (i = 0; i < ht->nNumUsed; i++) {
2509+
Z_EXTRA(ht->arData[i].val) = i;
2510+
}
25072511
} else {
2512+
/* Remove holes and store original order. */
25082513
for (j = 0, i = 0; j < ht->nNumUsed; j++) {
25092514
p = ht->arData + j;
25102515
if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) continue;
25112516
if (i != j) {
25122517
ht->arData[i] = *p;
25132518
}
2519+
Z_EXTRA(ht->arData[i].val) = i;
25142520
i++;
25152521
}
2522+
ht->nNumUsed = i;
25162523
}
25172524

2518-
sort((void *)ht->arData, i, sizeof(Bucket), (compare_func_t) compar,
2525+
sort((void *)ht->arData, ht->nNumUsed, sizeof(Bucket), (compare_func_t) compar,
25192526
(swap_func_t)(renumber? zend_hash_bucket_renum_swap :
25202527
((HT_FLAGS(ht) & HASH_FLAG_PACKED) ? zend_hash_bucket_packed_swap : zend_hash_bucket_swap)));
25212528

2522-
ht->nNumUsed = i;
25232529
ht->nInternalPointer = 0;
25242530

25252531
if (renumber) {

ext/spl/tests/bug67539.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ $it = new ArrayIterator(array_fill(0,2,'X'), 1 );
77

88
function badsort($a, $b) {
99
$GLOBALS['it']->unserialize($GLOBALS['it']->serialize());
10-
return TRUE;
10+
return 0;
1111
}
1212

1313
$it->uksort('badsort');

0 commit comments

Comments
 (0)