@@ -359,6 +359,225 @@ static int php_array_data_compare_string_locale_unstable_i(Bucket *f, Bucket *s)
359359}
360360/* }}} */
361361
362+ /* Returns the position of a PHP type in the total ordering used by SORT_STRICT. */
363+ static zend_always_inline int php_array_type_order (uint8_t type )
364+ {
365+ switch (type ) {
366+ case IS_NULL : return 0 ;
367+ case IS_FALSE : return 1 ;
368+ case IS_TRUE : return 2 ;
369+ case IS_LONG : return 3 ;
370+ case IS_DOUBLE : return 4 ;
371+ case IS_STRING : return 5 ;
372+ case IS_ARRAY : return 6 ;
373+ case IS_OBJECT : return 7 ;
374+ case IS_RESOURCE : return 8 ;
375+ EMPTY_SWITCH_DEFAULT_CASE ()
376+ }
377+ }
378+
379+ static zend_always_inline int php_array_compare_strings (zend_string * s1 , zend_string * s2 )
380+ {
381+ if (s1 == s2 ) {
382+ return 0 ;
383+ }
384+ size_t len1 = ZSTR_LEN (s1 );
385+ size_t len2 = ZSTR_LEN (s2 );
386+ size_t min_len = len1 < len2 ? len1 : len2 ;
387+ int cmp = memcmp (ZSTR_VAL (s1 ), ZSTR_VAL (s2 ), min_len );
388+ if (cmp != 0 ) {
389+ return cmp < 0 ? -1 : 1 ;
390+ }
391+ return ZEND_THREEWAY_COMPARE (len1 , len2 );
392+ }
393+
394+ static zend_always_inline int php_array_sort_compare_strict (zval * op1 , zval * op2 );
395+ static int php_array_sort_compare_objects_strict (zval * o1 , zval * o2 );
396+
397+ /* Wrapper for zend_hash_compare callback. php_array_sort_compare_strict is
398+ * zend_always_inline for performance, so we need this addressable wrapper. */
399+ static int php_array_data_compare_strict_callback (zval * op1 , zval * op2 )
400+ {
401+ return php_array_sort_compare_strict (op1 , op2 );
402+ }
403+
404+ static int php_array_sort_compare_symbol_tables_strict (HashTable * ht1 , HashTable * ht2 )
405+ {
406+ if (ht1 == ht2 ) {
407+ return 0 ;
408+ }
409+
410+ GC_TRY_ADDREF (ht1 );
411+ GC_TRY_ADDREF (ht2 );
412+
413+ int ret = zend_hash_compare (ht1 , ht2 , (compare_func_t )php_array_data_compare_strict_callback , 0 );
414+
415+ GC_TRY_DTOR_NO_REF (ht1 );
416+ GC_TRY_DTOR_NO_REF (ht2 );
417+
418+ return ret ;
419+ }
420+
421+ static int php_array_sort_compare_objects_strict (zval * o1 , zval * o2 )
422+ {
423+ ZEND_ASSERT (Z_TYPE_P (o1 ) == IS_OBJECT && Z_TYPE_P (o2 ) == IS_OBJECT );
424+
425+ zend_object * zobj1 = Z_OBJ_P (o1 );
426+ zend_object * zobj2 = Z_OBJ_P (o2 );
427+
428+ if (zobj1 -> ce != zobj2 -> ce ) {
429+ return zobj1 -> ce > zobj2 -> ce ? 1 : -1 ;
430+ }
431+
432+ if (zobj1 == zobj2 ) {
433+ return 0 ;
434+ }
435+
436+ if (zobj1 -> ce -> ce_flags & ZEND_ACC_ENUM ) {
437+ return zobj1 -> handle > zobj2 -> handle ? 1 : -1 ;
438+ }
439+
440+ if (Z_OBJ_HT_P (o1 )-> compare && Z_OBJ_HT_P (o1 )-> compare != zend_std_compare_objects ) {
441+ return Z_OBJ_HT_P (o1 )-> compare (o1 , o2 );
442+ }
443+
444+ /* Compare declared properties directly when no dynamic properties exist */
445+ if (!zobj1 -> properties && !zobj2 -> properties
446+ && !zend_object_is_lazy (zobj1 ) && !zend_object_is_lazy (zobj2 )) {
447+ zend_property_info * info ;
448+ int i , ret ;
449+
450+ if (!zobj1 -> ce -> default_properties_count ) {
451+ return 0 ;
452+ }
453+
454+ if (UNEXPECTED (Z_IS_RECURSIVE_P (o1 ))) {
455+ zend_throw_error (NULL , "Nesting level too deep - recursive dependency?" );
456+ return ZEND_UNCOMPARABLE ;
457+ }
458+ Z_PROTECT_RECURSION_P (o1 );
459+
460+ GC_ADDREF (zobj1 );
461+ GC_ADDREF (zobj2 );
462+
463+ ret = 0 ;
464+ for (i = 0 ; i < zobj1 -> ce -> default_properties_count ; i ++ ) {
465+ zval * p1 , * p2 ;
466+
467+ info = zobj1 -> ce -> properties_info_table [i ];
468+
469+ if (!info ) {
470+ continue ;
471+ }
472+
473+ p1 = OBJ_PROP (zobj1 , info -> offset );
474+ p2 = OBJ_PROP (zobj2 , info -> offset );
475+
476+ if (Z_TYPE_P (p1 ) != IS_UNDEF ) {
477+ if (Z_TYPE_P (p2 ) != IS_UNDEF ) {
478+ ret = php_array_sort_compare_strict (p1 , p2 );
479+ if (ret != 0 ) {
480+ break ;
481+ }
482+ } else {
483+ ret = 1 ;
484+ break ;
485+ }
486+ } else if (Z_TYPE_P (p2 ) != IS_UNDEF ) {
487+ ret = -1 ;
488+ break ;
489+ }
490+ }
491+
492+ Z_UNPROTECT_RECURSION_P (o1 );
493+ OBJ_RELEASE (zobj1 );
494+ OBJ_RELEASE (zobj2 );
495+ return ret ;
496+ }
497+
498+ /* Dynamic properties exist: compare via property hash tables */
499+ GC_ADDREF (zobj1 );
500+ GC_ADDREF (zobj2 );
501+
502+ int ret = php_array_sort_compare_symbol_tables_strict (
503+ zend_std_get_properties_ex (zobj1 ),
504+ zend_std_get_properties_ex (zobj2 )
505+ );
506+
507+ OBJ_RELEASE (zobj1 );
508+ OBJ_RELEASE (zobj2 );
509+
510+ return ret ;
511+ }
512+
513+ static zend_always_inline int php_array_sort_compare_strict (zval * op1 , zval * op2 )
514+ {
515+ ZVAL_DEREF (op1 );
516+ ZVAL_DEREF (op2 );
517+
518+ uint8_t t1 = Z_TYPE_P (op1 );
519+ uint8_t t2 = Z_TYPE_P (op2 );
520+
521+ if (t1 == t2 ) {
522+ switch (t1 ) {
523+ case IS_LONG :
524+ return ZEND_THREEWAY_COMPARE (Z_LVAL_P (op1 ), Z_LVAL_P (op2 ));
525+
526+ case IS_STRING :
527+ return php_array_compare_strings (Z_STR_P (op1 ), Z_STR_P (op2 ));
528+
529+ case IS_DOUBLE :
530+ /* Per IEEE 754 totalOrder, NaN sorts after all other values */
531+ if (UNEXPECTED (zend_isnan (Z_DVAL_P (op1 )))) {
532+ return zend_isnan (Z_DVAL_P (op2 )) ? 0 : 1 ;
533+ }
534+ if (UNEXPECTED (zend_isnan (Z_DVAL_P (op2 )))) {
535+ return -1 ;
536+ }
537+ return ZEND_THREEWAY_COMPARE (Z_DVAL_P (op1 ), Z_DVAL_P (op2 ));
538+
539+ case IS_ARRAY :
540+ return php_array_sort_compare_symbol_tables_strict (Z_ARRVAL_P (op1 ), Z_ARRVAL_P (op2 ));
541+
542+ case IS_OBJECT :
543+ return php_array_sort_compare_objects_strict (op1 , op2 );
544+
545+ case IS_NULL :
546+ case IS_FALSE :
547+ case IS_TRUE :
548+ return 0 ;
549+
550+ case IS_RESOURCE :
551+ return ZEND_THREEWAY_COMPARE (Z_RES_P (op1 )-> handle , Z_RES_P (op2 )-> handle );
552+
553+ EMPTY_SWITCH_DEFAULT_CASE ()
554+ }
555+ }
556+
557+ /* Types differ: order by type hierarchy */
558+ /* Special case: IS_FALSE (2) and IS_TRUE (3) are both bool, so compare by value */
559+ if ((t1 | 1 ) == IS_TRUE && (t2 | 1 ) == IS_TRUE ) {
560+ return t1 > t2 ? 1 : -1 ;
561+ }
562+ return php_array_type_order (t1 ) > php_array_type_order (t2 ) ? 1 : -1 ;
563+ }
564+
565+ static zend_always_inline int php_array_data_compare_strict_unstable_i (Bucket * f , Bucket * s )
566+ {
567+ return php_array_sort_compare_strict (& f -> val , & s -> val );
568+ }
569+
570+ static zend_always_inline int php_array_key_compare_strict_unstable_i (Bucket * f , Bucket * s )
571+ {
572+ if (f -> key == NULL && s -> key == NULL ) {
573+ return ZEND_THREEWAY_COMPARE ((zend_long )f -> h , (zend_long )s -> h );
574+ }
575+ if (f -> key && s -> key ) {
576+ return php_array_compare_strings (f -> key , s -> key );
577+ }
578+ return f -> key ? 1 : -1 ;
579+ }
580+
362581DEFINE_SORT_VARIANTS (key_compare );
363582DEFINE_SORT_VARIANTS (key_compare_numeric );
364583DEFINE_SORT_VARIANTS (key_compare_string_case );
@@ -371,6 +590,8 @@ DEFINE_SORT_VARIANTS(data_compare_string);
371590DEFINE_SORT_VARIANTS (data_compare_string_locale );
372591DEFINE_SORT_VARIANTS (natural_compare );
373592DEFINE_SORT_VARIANTS (natural_case_compare );
593+ DEFINE_SORT_VARIANTS (key_compare_strict );
594+ DEFINE_SORT_VARIANTS (data_compare_strict );
374595
375596static bucket_compare_func_t php_get_key_compare_func (zend_long sort_type )
376597{
@@ -395,6 +616,9 @@ static bucket_compare_func_t php_get_key_compare_func(zend_long sort_type)
395616 case PHP_SORT_LOCALE_STRING :
396617 return php_array_key_compare_string_locale ;
397618
619+ case PHP_SORT_STRICT :
620+ return php_array_key_compare_strict ;
621+
398622 case PHP_SORT_REGULAR :
399623 default :
400624 return php_array_key_compare ;
@@ -425,6 +649,9 @@ static bucket_compare_func_t php_get_key_reverse_compare_func(zend_long sort_typ
425649 case PHP_SORT_LOCALE_STRING :
426650 return php_array_reverse_key_compare_string_locale ;
427651
652+ case PHP_SORT_STRICT :
653+ return php_array_reverse_key_compare_strict ;
654+
428655 case PHP_SORT_REGULAR :
429656 default :
430657 return php_array_reverse_key_compare ;
@@ -455,6 +682,9 @@ static bucket_compare_func_t php_get_data_compare_func(zend_long sort_type) /* {
455682 case PHP_SORT_LOCALE_STRING :
456683 return php_array_data_compare_string_locale ;
457684
685+ case PHP_SORT_STRICT :
686+ return php_array_data_compare_strict ;
687+
458688 case PHP_SORT_REGULAR :
459689 default :
460690 return php_array_data_compare ;
@@ -485,6 +715,9 @@ static bucket_compare_func_t php_get_data_reverse_compare_func(zend_long sort_ty
485715 case PHP_SORT_LOCALE_STRING :
486716 return php_array_reverse_data_compare_string_locale ;
487717
718+ case PHP_SORT_STRICT :
719+ return php_array_reverse_data_compare_strict ;
720+
488721 case PHP_SORT_REGULAR :
489722 default :
490723 return php_array_reverse_data_compare ;
@@ -543,6 +776,14 @@ static bucket_compare_func_t php_get_data_compare_func_unstable(zend_long sort_t
543776 }
544777 break ;
545778
779+ case PHP_SORT_STRICT :
780+ if (reverse ) {
781+ return php_array_reverse_data_compare_strict_unstable ;
782+ } else {
783+ return php_array_data_compare_strict_unstable ;
784+ }
785+ break ;
786+
546787 case PHP_SORT_REGULAR :
547788 default :
548789 if (reverse ) {
@@ -6003,6 +6244,7 @@ PHP_FUNCTION(array_multisort)
60036244 case PHP_SORT_STRING :
60046245 case PHP_SORT_NATURAL :
60056246 case PHP_SORT_LOCALE_STRING :
6247+ case PHP_SORT_STRICT :
60066248 /* flag allowed here */
60076249 if (parse_state [MULTISORT_TYPE ] == 1 ) {
60086250 /* Save the flag and make sure then next arg is not the current flag. */
0 commit comments