Skip to content

Commit 3300fed

Browse files
authored
[9.x] Use secure randomness in Arr:random and Arr:shuffle (#46105)
* Use secure randomness in Arr:random and Arr:shuffle * Simplify random and shuffle algos * Fix function name * Prevent shuffle from preserving keys * Fix algo to better implementation (and fix broken test) * Fix increment style * Fix broken shuffle and make test useful * Keeping StyleCI happy
1 parent 3bbc6c5 commit 3300fed

File tree

2 files changed

+71
-16
lines changed

2 files changed

+71
-16
lines changed

src/Illuminate/Collections/Arr.php

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -635,28 +635,30 @@ public static function random($array, $number = null, $preserveKeys = false)
635635
}
636636

637637
if (is_null($number)) {
638-
return $array[array_rand($array)];
638+
return head(array_slice($array, random_int(0, $count - 1), 1));
639639
}
640640

641641
if ((int) $number === 0) {
642642
return [];
643643
}
644644

645-
$keys = array_rand($array, $number);
645+
$keys = array_keys($array);
646+
$count = count($keys);
647+
$selected = [];
646648

647-
$results = [];
649+
for ($i = $count - 1; $i >= $count - $number; $i--) {
650+
$j = random_int(0, $i);
648651

649-
if ($preserveKeys) {
650-
foreach ((array) $keys as $key) {
651-
$results[$key] = $array[$key];
652-
}
653-
} else {
654-
foreach ((array) $keys as $key) {
655-
$results[] = $array[$key];
652+
if ($preserveKeys) {
653+
$selected[$keys[$j]] = $array[$keys[$j]];
654+
} else {
655+
$selected[] = $array[$keys[$j]];
656656
}
657+
658+
$keys[$j] = $keys[$i];
657659
}
658660

659-
return $results;
661+
return $selected;
660662
}
661663

662664
/**
@@ -708,15 +710,25 @@ public static function set(&$array, $key, $value)
708710
*/
709711
public static function shuffle($array, $seed = null)
710712
{
711-
if (is_null($seed)) {
712-
shuffle($array);
713-
} else {
713+
if (! is_null($seed)) {
714714
mt_srand($seed);
715715
shuffle($array);
716716
mt_srand();
717+
718+
return $array;
717719
}
718720

719-
return $array;
721+
$keys = array_keys($array);
722+
723+
for ($i = count($keys) - 1; $i > 0; $i--) {
724+
$j = random_int(0, $i);
725+
$shuffled[] = $array[$keys[$j]];
726+
$keys[$j] = $keys[$i];
727+
}
728+
729+
$shuffled[] = $array[$keys[0]];
730+
731+
return $shuffled;
720732
}
721733

722734
/**

tests/Support/SupportArrTest.php

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,25 @@ public function testRandom()
731731
$this->assertCount(2, array_intersect_assoc(['one' => 'foo', 'two' => 'bar', 'three' => 'baz'], $random));
732732
}
733733

734+
public function testRandomIsActuallyRandom()
735+
{
736+
$values = [];
737+
738+
for ($i = 0; $i < 100; $i++) {
739+
$values[] = Arr::random(['foo', 'bar', 'baz']);
740+
}
741+
742+
$this->assertContains('foo', $values);
743+
$this->assertContains('bar', $values);
744+
$this->assertContains('baz', $values);
745+
}
746+
747+
public function testRandomNotIncrementingKeys()
748+
{
749+
$random = Arr::random(['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz']);
750+
$this->assertContains($random, ['foo', 'bar', 'baz']);
751+
}
752+
734753
public function testRandomOnEmptyArray()
735754
{
736755
$random = Arr::random([], 0);
@@ -811,10 +830,34 @@ public function testSet()
811830

812831
public function testShuffleWithSeed()
813832
{
814-
$this->assertEquals(
833+
$this->assertSame(
815834
Arr::shuffle(range(0, 100, 10), 1234),
816835
Arr::shuffle(range(0, 100, 10), 1234)
817836
);
837+
838+
$this->assertNotSame(
839+
range(0, 100, 10),
840+
Arr::shuffle(range(0, 100, 10), 1234)
841+
);
842+
}
843+
844+
public function testShuffle()
845+
{
846+
$source = range('a', 'z'); // alphabetic keys to ensure values are returned
847+
848+
$sameElements = true;
849+
$dontMatch = false;
850+
851+
// Attempt 5x times to prevent random failures
852+
for ($i = 0; $i < 5; $i++) {
853+
$shuffled = Arr::shuffle($source);
854+
855+
$dontMatch = $dontMatch || $source !== $shuffled;
856+
$sameElements = $sameElements && $source === array_values(Arr::sort($shuffled));
857+
}
858+
859+
$this->assertTrue($sameElements, 'Shuffled array should always have the same elements.');
860+
$this->assertTrue($dontMatch, 'Shuffled array should not have the same order.');
818861
}
819862

820863
public function testSort()

0 commit comments

Comments
 (0)