]> granicus.if.org Git - php/commitdiff
Fixed bug #74361
authorNikita Popov <nikita.ppv@gmail.com>
Sun, 2 Apr 2017 11:19:32 +0000 (13:19 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Sun, 2 Apr 2017 11:19:32 +0000 (13:19 +0200)
NEWS
ext/standard/array.c
ext/standard/tests/array/bug74361.phpt [new file with mode: 0644]
ext/standard/tests/array/bug74361_2.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index a655dd36e04f456fc0085e379be5304142979aea..635a51b835e64881aeb1975cadab2c7f4c252a20 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,9 @@ PHP                                                                        NEWS
   . Fixed bug #74341 (openssl_x509_parse fails to parse ASN.1 UTCTime without
     seconds). (Moritz Fain)
 
+- Standard:
+  . Fixed bug #74361 (Compaction in array_rand() violates COW). (Nikita)
+
 13 Apr 2017, PHP 7.1.4
 
 - Core:
index 0dd0f3a98d7be87a01a9ac8b179b05631ff43f81..966e0a3af2cbf2917db6a71275ad55a6263d74f6 100644 (file)
@@ -5016,19 +5016,26 @@ PHP_FUNCTION(array_rand)
        if (num_req == 1) {
                HashTable *ht = Z_ARRVAL_P(input);
 
-               /* Compact the hashtable if less than 3/4 of elements are used */
-               if (num_avail < ht->nNumUsed - (ht->nNumUsed>>2)) {
-                       if (ht->u.flags & HASH_FLAG_PACKED) {
-                               zend_hash_packed_to_hash(ht);
-                       } else {
-                               zend_hash_rehash(ht);
-                       }
+               if (num_avail < ht->nNumUsed - (ht->nNumUsed>>1)) {
+                       /* If less than 1/2 of elements are used, don't sample. Instead search for a
+                        * specific offset using linear scan. */
+                       zend_long i = 0, randval = php_mt_rand_range(0, num_avail - 1);
+                       ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(input), num_key, string_key) {
+                               if (i == randval) {
+                                       if (string_key) {
+                                               RETURN_STR_COPY(string_key);
+                                       } else {
+                                               RETURN_LONG(num_key);
+                                       }
+                               }
+                               i++;
+                       } ZEND_HASH_FOREACH_END();
                }
 
                /* Sample random buckets until we hit one that is not empty.
-                * The worst case probability of hitting an empty element is 1-3/4. The worst case
-                * probability of hitting N empty elements in a row is (1-3/4)**N.
-                * For N=5 this becomes smaller than 0.1%. */
+                * The worst case probability of hitting an empty element is 1-1/2. The worst case
+                * probability of hitting N empty elements in a row is (1-1/2)**N.
+                * For N=10 this becomes smaller than 0.1%. */
                do {
                        zend_long randval = php_mt_rand_range(0, ht->nNumUsed - 1);
                        Bucket *bucket = &ht->arData[randval];
diff --git a/ext/standard/tests/array/bug74361.phpt b/ext/standard/tests/array/bug74361.phpt
new file mode 100644 (file)
index 0000000..6e74590
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Bug #74361: Compaction in array_rand() violates COW
+--FILE--
+<?php
+
+$array = [4 => 4];
+var_dump(array_rand($array));
+
+?>
+--EXPECT--
+int(4)
diff --git a/ext/standard/tests/array/bug74361_2.phpt b/ext/standard/tests/array/bug74361_2.phpt
new file mode 100644 (file)
index 0000000..4f4bdcf
--- /dev/null
@@ -0,0 +1,24 @@
+--TEST--
+Bug #74361: Compaction in array_rand() violates COW (variation)
+--FILE--
+<?php
+
+$array = range(0, 15);
+for ($i = 0; $i <= 8; $i++) {
+    unset($array[$i]);
+}
+
+foreach ($array as $x) {
+    var_dump($x);
+    array_rand($array, 1);
+}
+
+?>
+--EXPECT--
+int(9)
+int(10)
+int(11)
+int(12)
+int(13)
+int(14)
+int(15)