Skip to content

Commit 7f0b228

Browse files
authored
Fix pre-PHP 8.2 compatibility for php_mt_rand_range() with MT_RAND_PHP (#9839)
* Fix pre-PHP 8.2 compatibility for php_mt_rand_range() with MT_RAND_PHP As some left-over comments indicated: > Legacy mode deliberately not inside php_mt_rand_range() > to prevent other functions being affected The broken scaler was only used for `php_mt_rand_common()`, not `php_mt_rand_range()`. The former is only used for `mt_rand()`, whereas the latter is used for `array_rand()` and others. With the refactoring for the introduction of ext/random `php_mt_rand_common()` and `php_mt_rand_range()` were accidentally unified, thus introducing a behavioral change that was reported in FakerPHP/Faker#528. This commit moves the checks for `MT_RAND_PHP` from the general-purpose `range()` function back into `php_mt_rand_common()` and also into `Randomizer::getInt()` for drop-in compatibility with `mt_rand()`. * [ci skip] NEWS for `MT_RAND_PHP` compatibility
1 parent 0db2e66 commit 7f0b228

File tree

5 files changed

+68
-19
lines changed

5 files changed

+68
-19
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ PHP NEWS
1414
. Fixed bug GH-9818 (Initialize run time cache in PDO methods).
1515
(Florian Sowade)
1616

17+
- Random:
18+
. Fixed bug GH-9839 (Pre-PHP 8.2 output compatibility for non-mt_rand()
19+
functions for MT_RAND_PHP). (timwolla)
20+
1721
27 Oct 2022, PHP 8.2.0RC5
1822

1923
- CLI:

ext/random/engine_mt19937.c

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -162,23 +162,7 @@ static uint64_t generate(php_random_status *status)
162162

163163
static zend_long range(php_random_status *status, zend_long min, zend_long max)
164164
{
165-
php_random_status_state_mt19937 *s = status->state;
166-
167-
if (s->mode == MT_RAND_MT19937) {
168-
return php_random_range(&php_random_algo_mt19937, status, min, max);
169-
}
170-
171-
/* Legacy mode deliberately not inside php_mt_rand_range()
172-
* to prevent other functions being affected */
173-
174-
uint64_t r = php_random_algo_mt19937.generate(status) >> 1;
175-
176-
/* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
177-
* (max - min) > ZEND_LONG_MAX.
178-
*/
179-
zend_ulong offset = (double) ( (double) max - min + 1.0) * (r / (PHP_MT_RAND_MAX + 1.0));
180-
181-
return (zend_long) (offset + min);
165+
return php_random_range(&php_random_algo_mt19937, status, min, max);
182166
}
183167

184168
static bool serialize(php_random_status *status, HashTable *data)

ext/random/random.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,21 @@ PHPAPI zend_long php_mt_rand_range(zend_long min, zend_long max)
449449
* rand() allows min > max, mt_rand does not */
450450
PHPAPI zend_long php_mt_rand_common(zend_long min, zend_long max)
451451
{
452-
return php_mt_rand_range(min, max);
452+
php_random_status *status = php_random_default_status();
453+
php_random_status_state_mt19937 *s = status->state;
454+
455+
if (s->mode == MT_RAND_MT19937) {
456+
return php_mt_rand_range(min, max);
457+
}
458+
459+
uint64_t r = php_random_algo_mt19937.generate(php_random_default_status()) >> 1;
460+
461+
/* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
462+
* (max - min) > ZEND_LONG_MAX.
463+
*/
464+
zend_ulong offset = (double) ( (double) max - min + 1.0) * (r / (PHP_MT_RAND_MAX + 1.0));
465+
466+
return (zend_long) (offset + min);
453467
}
454468
/* }}} */
455469

ext/random/randomizer.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,22 @@ PHP_METHOD(Random_Randomizer, getInt)
126126
RETURN_THROWS();
127127
}
128128

129-
result = randomizer->algo->range(randomizer->status, min, max);
129+
if (UNEXPECTED(
130+
randomizer->algo->range == php_random_algo_mt19937.range
131+
&& ((php_random_status_state_mt19937 *) randomizer->status->state)->mode != MT_RAND_MT19937
132+
)) {
133+
uint64_t r = php_random_algo_mt19937.generate(randomizer->status) >> 1;
134+
135+
/* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
136+
* (max - min) > ZEND_LONG_MAX.
137+
*/
138+
zend_ulong offset = (double) ( (double) max - min + 1.0) * (r / (PHP_MT_RAND_MAX + 1.0));
139+
140+
result = (zend_long) (offset + min);
141+
} else {
142+
result = randomizer->algo->range(randomizer->status, min, max);
143+
}
144+
130145
if (EG(exception)) {
131146
RETURN_THROWS();
132147
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
array_rand() is not affected by MT_RAND_PHP as with PHP < 8.2
3+
--FILE--
4+
<?php
5+
6+
$array = [
7+
'foo',
8+
'bar',
9+
'baz',
10+
];
11+
12+
mt_srand(1, MT_RAND_PHP);
13+
function custom_array_rand() {
14+
global $array;
15+
$key = array_rand($array);
16+
var_dump('found key ' . $key);
17+
return $array[$key];
18+
}
19+
20+
var_dump(
21+
custom_array_rand(),
22+
custom_array_rand(),
23+
custom_array_rand(),
24+
);
25+
?>
26+
--EXPECTF--
27+
string(11) "found key 0"
28+
string(11) "found key 1"
29+
string(11) "found key 0"
30+
string(3) "foo"
31+
string(3) "bar"
32+
string(3) "foo"

0 commit comments

Comments
 (0)