@@ -103,6 +103,7 @@ PHP_MINIT_FUNCTION(array) /* {{{ */
103
103
104
104
REGISTER_LONG_CONSTANT ("SORT_REGULAR" , PHP_SORT_REGULAR , CONST_CS | CONST_PERSISTENT );
105
105
REGISTER_LONG_CONSTANT ("SORT_NUMERIC" , PHP_SORT_NUMERIC , CONST_CS | CONST_PERSISTENT );
106
+ REGISTER_LONG_CONSTANT ("SORT_STRICT" , PHP_SORT_STRICT , CONST_CS | CONST_PERSISTENT );
106
107
REGISTER_LONG_CONSTANT ("SORT_STRING" , PHP_SORT_STRING , CONST_CS | CONST_PERSISTENT );
107
108
REGISTER_LONG_CONSTANT ("SORT_LOCALE_STRING" , PHP_SORT_LOCALE_STRING , CONST_CS | CONST_PERSISTENT );
108
109
REGISTER_LONG_CONSTANT ("SORT_NATURAL" , PHP_SORT_NATURAL , CONST_CS | CONST_PERSISTENT );
@@ -349,6 +350,104 @@ static zend_always_inline int php_array_data_compare_unstable_i(Bucket *f, Bucke
349
350
}
350
351
/* }}} */
351
352
353
+ /* return int to be compatible with compare_func_t */
354
+ static int hash_zval_strict_function (zval * z1 , zval * z2 ) /* {{{ */
355
+ {
356
+ ZVAL_DEREF (z1 );
357
+ ZVAL_DEREF (z2 );
358
+ // If the types are different, compare based on type.
359
+ // (Values of different types can't be identical.)
360
+ int t1 = Z_TYPE_P (z1 );
361
+ int t2 = Z_TYPE_P (z2 );
362
+ if ( t1 != t2 ) {
363
+ return (t1 > t2 ) ? 1 : -1 ;
364
+ }
365
+ // The most important thing about this comparison mode is that the result
366
+ // is 0 when zend_is_identical, and non-zero otherwise. This test is
367
+ // done first to make it easier to verify this property by inspection.
368
+ // (Arrays are excluded as an optimization, to avoid a redudant
369
+ // deep inspection.)
370
+ if (t1 != IS_ARRAY && zend_is_identical (z1 , z2 )) {
371
+ return 0 ;
372
+ }
373
+ // Both types are the same *but the values are not identical*
374
+ // Below this point, the return value for non-array values
375
+ // should always be non-zero.
376
+ switch (t1 ) {
377
+ case IS_LONG :
378
+ return Z_LVAL_P (z1 ) > Z_LVAL_P (z2 ) ? 1 : -1 ;
379
+
380
+ case IS_DOUBLE :
381
+ return Z_DVAL_P (z1 ) > Z_DVAL_P (z2 ) ? 1 : -1 ;
382
+
383
+ case IS_STRING :
384
+ return zend_binary_strcmp (
385
+ Z_STRVAL_P (z1 ), Z_STRLEN_P (z1 ),
386
+ Z_STRVAL_P (z2 ), Z_STRLEN_P (z2 )
387
+ );
388
+
389
+ case IS_ARRAY :
390
+ // Do a recursive comparison. This is consistent with the test
391
+ // for arrays in zend_is_identical, but unlike that method it
392
+ // provides a meaningful ordering in the case of non-identity
393
+ // as well.
394
+ return zend_hash_compare (
395
+ Z_ARRVAL_P (z1 ), Z_ARRVAL_P (z2 ),
396
+ (compare_func_t ) hash_zval_strict_function , 1 /* ordered */
397
+ );
398
+
399
+ case IS_OBJECT :
400
+ {
401
+ // Start with a recursive comparison like for arrays, for consistency.
402
+ // (This is deliberately not using the user-defined `compare` handler,
403
+ // nor is it using zend_std_compare_objects() because that uses
404
+ // zend_compare when examining properties, not a strict comparison.)
405
+ zend_object * zobj1 = Z_OBJ_P (z1 );
406
+ zend_object * zobj2 = Z_OBJ_P (z2 );
407
+ rebuild_object_properties (zobj1 );
408
+ rebuild_object_properties (zobj2 );
409
+ // zend_std_compare_objects() uses unordered comparison, but that
410
+ // leads to a unpredictable sort: with unordered the properties are
411
+ // compared in the order they appear in the *first* object so
412
+ // `compare(a,b)` is not guaranteed to be the same as `-compare(b,a)`.
413
+ int c = zend_hash_compare (
414
+ zobj1 -> properties , zobj2 -> properties ,
415
+ (compare_func_t ) hash_zval_strict_function , 1 /* ordered */
416
+ );
417
+ if (c != 0 ) {
418
+ return (c > 0 ) ? 1 : -1 ;
419
+ }
420
+ // Fall back on spl_object_id() value, which will probably vary
421
+ // non-deterministically between runs (alas).
422
+ ZEND_ASSERT (zobj1 -> handle != zobj2 -> handle );
423
+ return (zobj1 -> handle > zobj2 -> handle ) ? 1 : -1 ;
424
+ }
425
+
426
+ case IS_RESOURCE :
427
+ // This will also likely vary non-deterministically between runs.
428
+ return Z_RES_HANDLE_P (z1 ) > Z_RES_HANDLE_P (z2 ) ? 1 : -1 ;
429
+
430
+ case IS_REFERENCE :
431
+ ZEND_ASSERT (0 && "Should have been dereferenced above" );
432
+
433
+ case IS_UNDEF :
434
+ case IS_NULL :
435
+ case IS_FALSE :
436
+ case IS_TRUE :
437
+ default :
438
+ ZEND_ASSERT (0 && "Values w/ same type should be identical" );
439
+ return 0 ;
440
+ }
441
+ }
442
+ /* }}} */
443
+
444
+
445
+ static zend_always_inline int php_array_data_compare_strict_unstable_i (Bucket * f , Bucket * s ) /* {{{ */
446
+ {
447
+ return hash_zval_strict_function (& f -> val , & s -> val );
448
+ }
449
+ /* }}} */
450
+
352
451
static zend_always_inline int php_array_data_compare_numeric_unstable_i (Bucket * f , Bucket * s ) /* {{{ */
353
452
{
354
453
return numeric_compare_function (& f -> val , & s -> val );
@@ -405,6 +504,7 @@ DEFINE_SORT_VARIANTS(key_compare_string_case);
405
504
DEFINE_SORT_VARIANTS (key_compare_string );
406
505
DEFINE_SORT_VARIANTS (key_compare_string_locale );
407
506
DEFINE_SORT_VARIANTS (data_compare );
507
+ DEFINE_SORT_VARIANTS (data_compare_strict );
408
508
DEFINE_SORT_VARIANTS (data_compare_numeric );
409
509
DEFINE_SORT_VARIANTS (data_compare_string_case );
410
510
DEFINE_SORT_VARIANTS (data_compare_string );
@@ -527,6 +627,14 @@ static bucket_compare_func_t php_get_data_compare_func(zend_long sort_type, int
527
627
}
528
628
break ;
529
629
630
+ case PHP_SORT_STRICT :
631
+ if (reverse ) {
632
+ return php_array_reverse_data_compare_strict ;
633
+ } else {
634
+ return php_array_data_compare_strict ;
635
+ }
636
+ break ;
637
+
530
638
case PHP_SORT_REGULAR :
531
639
default :
532
640
if (reverse ) {
@@ -591,6 +699,14 @@ static bucket_compare_func_t php_get_data_compare_func_unstable(zend_long sort_t
591
699
}
592
700
break ;
593
701
702
+ case PHP_SORT_STRICT :
703
+ if (reverse ) {
704
+ return php_array_reverse_data_compare_strict_unstable ;
705
+ } else {
706
+ return php_array_data_compare_strict_unstable ;
707
+ }
708
+ break ;
709
+
594
710
case PHP_SORT_REGULAR :
595
711
default :
596
712
if (reverse ) {
0 commit comments