Skip to content
Closed
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
29 changes: 27 additions & 2 deletions Zend/zend_operators.c
Original file line number Diff line number Diff line change
Expand Up @@ -3424,9 +3424,18 @@ ZEND_API int ZEND_FASTCALL zendi_smart_strcmp(zend_string *s1, zend_string *s2)
int oflow1, oflow2;
zend_long lval1 = 0, lval2 = 0;
double dval1 = 0.0, dval2 = 0.0;
bool trailing1 = false, trailing2 = false;

if ((ret1 = is_numeric_string_ex(s1->val, s1->len, &lval1, &dval1, false, &oflow1, NULL)) &&
(ret2 = is_numeric_string_ex(s2->val, s2->len, &lval2, &dval2, false, &oflow2, NULL))) {
/* Use allow_errors=true to extract leading numeric values even with trailing data.
* If BOTH strings have numeric prefixes, do numeric comparison to maintain transitivity.
* This ensures the comparison function is transitive, which is required for
* stable sorting and correct behavior in array_unique() with SORT_REGULAR.
*/
ret1 = is_numeric_string_ex(s1->val, s1->len, &lval1, &dval1, true, &oflow1, &trailing1);
ret2 = is_numeric_string_ex(s2->val, s2->len, &lval2, &dval2, true, &oflow2, &trailing2);

/* If BOTH strings have numeric prefixes, do numeric comparison */
if (ret1 && ret2) {
#if ZEND_ULONG_MAX == 0xFFFFFFFF
if (oflow1 != 0 && oflow1 == oflow2 && dval1 - dval2 == 0. &&
((oflow1 == 1 && dval1 > 9007199254740991. /*0x1FFFFFFFFFFFFF*/)
Expand Down Expand Up @@ -3456,8 +3465,24 @@ ZEND_API int ZEND_FASTCALL zendi_smart_strcmp(zend_string *s1, zend_string *s2)
goto string_cmp;
}
dval1 = dval1 - dval2;
if (dval1 == 0.0) {
/* Numeric values are equal, check trailing data for tie-breaker */
if (!trailing1 && trailing2) {
return -1; /* No trailing data < has trailing data */
} else if (trailing1 && !trailing2) {
return 1; /* Has trailing data > no trailing data */
}
}
return ZEND_NORMALIZE_BOOL(dval1);
} else { /* they both have to be long's */
if (lval1 == lval2) {
/* Numeric values are equal, check trailing data for tie-breaker */
if (!trailing1 && trailing2) {
return -1; /* No trailing data < has trailing data */
} else if (trailing1 && !trailing2) {
return 1; /* Has trailing data > no trailing data */
}
}
return lval1 > lval2 ? 1 : (lval1 < lval2 ? -1 : 0);
}
} else {
Expand Down
90 changes: 90 additions & 0 deletions ext/standard/tests/array/gh20262.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
--TEST--
GH-20262: array_unique() with SORT_REGULAR fails to identify duplicates with mixed numeric/alphanumeric strings
--FILE--
<?php
// Original bug report: array_unique() with SORT_REGULAR doesn't properly
// identify duplicates when array contains mixed numeric and alphanumeric strings
$units = ['5', '10', '5', '3A', '5', '5'];
$unique = array_unique($units, SORT_REGULAR);

echo "Input array:\n";
var_dump($units);

echo "\nResult of array_unique():\n";
var_dump($unique);

echo "\nExpected: 3 unique values ('5', '10', '3A')\n";
echo "Actual count: " . count($unique) . "\n";

// Additional test: verify sort() groups equal values correctly
$arr = ['5', '10', '5', '3A', '5', '5'];
sort($arr);

echo "\nSorted array (equal values should be grouped):\n";
var_dump($arr);

// Verify '5' values are consecutive
$positions = [];
foreach ($arr as $idx => $val) {
if ($val === '5') {
$positions[] = $idx;
}
}

$consecutive = true;
for ($i = 0; $i < count($positions) - 1; $i++) {
if ($positions[$i] + 1 !== $positions[$i + 1]) {
$consecutive = false;
break;
}
}

echo "\nAll '5' values grouped together: " . ($consecutive ? "yes" : "no") . "\n";
?>
--EXPECT--
Input array:
array(6) {
[0]=>
string(1) "5"
[1]=>
string(2) "10"
[2]=>
string(1) "5"
[3]=>
string(2) "3A"
[4]=>
string(1) "5"
[5]=>
string(1) "5"
}

Result of array_unique():
array(3) {
[0]=>
string(1) "5"
[1]=>
string(2) "10"
[3]=>
string(2) "3A"
}

Expected: 3 unique values ('5', '10', '3A')
Actual count: 3

Sorted array (equal values should be grouped):
array(6) {
[0]=>
string(2) "3A"
[1]=>
string(1) "5"
[2]=>
string(1) "5"
[3]=>
string(1) "5"
[4]=>
string(1) "5"
[5]=>
string(2) "10"
}

All '5' values grouped together: yes
10 changes: 5 additions & 5 deletions ext/standard/tests/dir/readdir_variation4.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,21 @@ int(2)
-- Call to readdir() --
array(16) {
[0]=>
string(9) "-10.5.tmp"
[1]=>
string(9) "-2345.tmp"
[1]=>
string(9) "-10.5.tmp"
[2]=>
string(1) "."
[3]=>
string(2) ".."
[4]=>
string(4) ".tmp"
[5]=>
string(7) "0.5.tmp"
[6]=>
string(5) "0.tmp"
[7]=>
[6]=>
string(17) "1.23456789E-9.tmp"
[7]=>
string(7) "0.5.tmp"
[8]=>
string(5) "1.tmp"
[9]=>
Expand Down
Loading