@@ -103,6 +103,7 @@ PHP_MINIT_FUNCTION(array) /* {{{ */
103103
104104 REGISTER_LONG_CONSTANT ("SORT_REGULAR" , PHP_SORT_REGULAR , CONST_CS | CONST_PERSISTENT );
105105 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 );
106107 REGISTER_LONG_CONSTANT ("SORT_STRING" , PHP_SORT_STRING , CONST_CS | CONST_PERSISTENT );
107108 REGISTER_LONG_CONSTANT ("SORT_LOCALE_STRING" , PHP_SORT_LOCALE_STRING , CONST_CS | CONST_PERSISTENT );
108109 REGISTER_LONG_CONSTANT ("SORT_NATURAL" , PHP_SORT_NATURAL , CONST_CS | CONST_PERSISTENT );
@@ -349,6 +350,102 @@ static zend_always_inline int php_array_data_compare_unstable_i(Bucket *f, Bucke
349350}
350351/* }}} */
351352
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+ // Below this point, the return value should always be non-zero.
374+ // Both types are the same *but the values are not identical*
375+ switch (t1 ) {
376+ case IS_LONG :
377+ return Z_LVAL_P (z1 ) > Z_LVAL_P (z2 ) ? 1 : -1 ;
378+
379+ case IS_DOUBLE :
380+ return Z_DVAL_P (z1 ) > Z_DVAL_P (z2 ) ? 1 : -1 ;
381+
382+ case IS_STRING :
383+ return zend_binary_strcmp (
384+ Z_STRVAL_P (z1 ), Z_STRLEN_P (z1 ),
385+ Z_STRVAL_P (z2 ), Z_STRLEN_P (z2 )
386+ );
387+
388+ case IS_ARRAY :
389+ // Do a recursive comparison. This is consistent with the test
390+ // for arrays in zend_is_identical, but it provides a meaningful
391+ // ordering in the case of non-identity as well.
392+ return zend_hash_compare (
393+ Z_ARRVAL_P (z1 ), Z_ARRVAL_P (z2 ),
394+ (compare_func_t ) hash_zval_strict_function , 1 /* ordered */
395+ );
396+
397+ case IS_OBJECT :
398+ {
399+ // Start with a recursive comparison like for arrays, for consistency.
400+ // (This is deliberately not using the user-defined `compare` handler,
401+ // nor is it using zend_std_compare_objects() because that uses
402+ // zend_compare when examining properties, not a strict comparison.)
403+ zend_object * zobj1 = Z_OBJ_P (z1 );
404+ zend_object * zobj2 = Z_OBJ_P (z2 );
405+ rebuild_object_properties (zobj1 );
406+ rebuild_object_properties (zobj2 );
407+ // zend_std_compare_objects() uses unordered comparison, but that
408+ // leads to a unpredictable sort: with unordered the properties are
409+ // compared in the order they appear in the *first* object so
410+ // `compare(a,b)` is not guaranteed to be the same as `-compare(b,a)`.
411+ int c = zend_hash_compare (
412+ zobj1 -> properties , zobj2 -> properties ,
413+ (compare_func_t ) hash_zval_strict_function , 1 /* ordered */
414+ );
415+ if (c != 0 ) {
416+ return (c > 0 ) ? 1 : -1 ;
417+ }
418+ // Fall back on spl_object_id() value, which will probably vary
419+ // non-deterministically between runs (alas).
420+ ZEND_ASSERT (zobj1 -> handle != zobj2 -> handle );
421+ return (zobj1 -> handle > zobj2 -> handle ) ? 1 : -1 ;
422+ }
423+
424+ case IS_RESOURCE :
425+ // This will also likely vary non-deterministically between runs.
426+ return Z_RES_HANDLE_P (z1 ) > Z_RES_HANDLE_P (z2 ) ? 1 : -1 ;
427+
428+ case IS_REFERENCE :
429+ ZEND_ASSERT (0 && "Should have been dereferenced above" );
430+
431+ case IS_UNDEF :
432+ case IS_NULL :
433+ case IS_FALSE :
434+ case IS_TRUE :
435+ default :
436+ ZEND_ASSERT (0 && "Values w/ same type should be identical" );
437+ return 0 ;
438+ }
439+ }
440+ /* }}} */
441+
442+
443+ static zend_always_inline int php_array_data_compare_strict_unstable_i (Bucket * f , Bucket * s ) /* {{{ */
444+ {
445+ return hash_zval_strict_function (& f -> val , & s -> val );
446+ }
447+ /* }}} */
448+
352449static zend_always_inline int php_array_data_compare_numeric_unstable_i (Bucket * f , Bucket * s ) /* {{{ */
353450{
354451 return numeric_compare_function (& f -> val , & s -> val );
@@ -405,6 +502,7 @@ DEFINE_SORT_VARIANTS(key_compare_string_case);
405502DEFINE_SORT_VARIANTS (key_compare_string );
406503DEFINE_SORT_VARIANTS (key_compare_string_locale );
407504DEFINE_SORT_VARIANTS (data_compare );
505+ DEFINE_SORT_VARIANTS (data_compare_strict );
408506DEFINE_SORT_VARIANTS (data_compare_numeric );
409507DEFINE_SORT_VARIANTS (data_compare_string_case );
410508DEFINE_SORT_VARIANTS (data_compare_string );
@@ -527,6 +625,14 @@ static bucket_compare_func_t php_get_data_compare_func(zend_long sort_type, int
527625 }
528626 break ;
529627
628+ case PHP_SORT_STRICT :
629+ if (reverse ) {
630+ return php_array_reverse_data_compare_strict ;
631+ } else {
632+ return php_array_data_compare_strict ;
633+ }
634+ break ;
635+
530636 case PHP_SORT_REGULAR :
531637 default :
532638 if (reverse ) {
@@ -591,6 +697,14 @@ static bucket_compare_func_t php_get_data_compare_func_unstable(zend_long sort_t
591697 }
592698 break ;
593699
700+ case PHP_SORT_STRICT :
701+ if (reverse ) {
702+ return php_array_reverse_data_compare_strict_unstable ;
703+ } else {
704+ return php_array_data_compare_strict_unstable ;
705+ }
706+ break ;
707+
594708 case PHP_SORT_REGULAR :
595709 default :
596710 if (reverse ) {
0 commit comments