]> granicus.if.org Git - php/commitdiff
Convert numeric keys in object/array casts
authorAndrea Faulds <ajf@ajf.me>
Mon, 14 Nov 2016 18:20:45 +0000 (18:20 +0000)
committerAndrea Faulds <ajf@ajf.me>
Mon, 14 Nov 2016 18:20:45 +0000 (18:20 +0000)
RFC: https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts

This converts key types as appropriate in object to array and array to object
casts, as well as in get_object_vars().

14 files changed:
NEWS
UPGRADING
Zend/tests/cast_to_object.phpt
Zend/tests/object_array_cast.phpt
Zend/tests/settype_object.phpt
Zend/zend_builtin_functions.c
Zend/zend_hash.c
Zend/zend_hash.h
Zend/zend_operators.c
ext/reflection/tests/bug61388.phpt
ext/spl/tests/arrayObject___construct_basic7.phpt
ext/standard/tests/array/var_export.phpt
ext/standard/tests/general_functions/gettype_settype_basic.phpt
ext/standard/tests/general_functions/type.phpt

diff --git a/NEWS b/NEWS
index 62cc66c5b197a6cb40937cf58fed37cbbbb12f87..d92fe5175bd26e904105aeefd3804ab28703a348 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,8 @@ PHP                                                                        NEWS
     operation). (Dmitry)
   . Implemented FR #72768 (Add ENABLE_VIRTUAL_TERMINAL_PROCESSING flag for
     php.exe). (Michele Locati)
+  . Implemented "Convert numeric keys in object/array casts" RFC, fixes
+    bugs #53838, #61655, #66173, #70925, #72254, etc. (Andrea)
 
 - Date:
   . Fixed bug #69587 (DateInterval properties and isset). (jhdxr)
index 953866a9a1d91646a1f04ab16058d1401b529b83..138add6ae4c51b0cbdda4b5e2641ad2cb97754f4 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -25,6 +25,16 @@ PHP 7.2 UPGRADE NOTES
   . is_object() will now return true for objects of class
     __PHP_Incomplete_Class.
   . Support for Netware operating systems have been removed.
+  . Casting arrays to objects (with (object) or settype()) will now covert
+    integer keys to string property names. This fixes the behaviour of previous
+    versions, where integer keys would become inaccessible properties with
+    integer names.
+  . Casting objects to arrays (with (object) or settype()), and retrieving
+    object properties in an array with get_object_vars(), will now convert
+    numeric string property names (that is, property names of the format
+    /^(0|(-?[1-9][0-9]*))$/ where PHP_INT_MIN <= n <= PHP_INT_MAX) to integer
+    keys. This fixes the behaviour of previous versions, where numeric string
+    property names would become inaccessible string keys.
 
 ========================================
 2. New Features
index f8d4878475e44bde9f381693eef581bfe28f8979..04d68053e26fa2e6676ec6ce9f51711e60057d47 100644 (file)
Binary files a/Zend/tests/cast_to_object.phpt and b/Zend/tests/cast_to_object.phpt differ
index 1cf3dbbd9cf65159f61510540c0b8b7fe657eeff..b1c12b579d7e691ff9b635510641ad2b98c96e2b 100644 (file)
@@ -14,11 +14,11 @@ var_dump($obj);
 ?>
 --EXPECT--
 object(stdClass)#1 (3) {
-  [0]=>
+  ["0"]=>
   int(1)
-  [1]=>
+  ["1"]=>
   int(2)
-  [2]=>
+  ["2"]=>
   int(3)
 }
 array(3) {
@@ -38,10 +38,10 @@ array(3) {
   int(3)
 }
 object(stdClass)#1 (3) {
-  [0]=>
+  ["0"]=>
   int(1)
-  [1]=>
+  ["1"]=>
   int(2)
-  [2]=>
+  ["2"]=>
   int(3)
 }
index d619dce7e3a7d86e2d78bdd1a62f86ece92e6069..7f9e5bc64b1a4ee2abb68916b88218ebd3f56962 100644 (file)
Binary files a/Zend/tests/settype_object.phpt and b/Zend/tests/settype_object.phpt differ
index d99ebb292e07af1beae729a7294ecfc465a89912..51716a096ef581c21e94c88ab8035505e00050f0 100644 (file)
@@ -1251,12 +1251,9 @@ ZEND_FUNCTION(get_object_vars)
        if (!zobj->ce->default_properties_count && properties == zobj->properties && !ZEND_HASH_GET_APPLY_COUNT(properties)) {
                /* fast copy */
                if (EXPECTED(zobj->handlers == &std_object_handlers)) {
-                       if (EXPECTED(!(GC_FLAGS(properties) & IS_ARRAY_IMMUTABLE))) {
-                               GC_REFCOUNT(properties)++;
-                       }
-                       RETURN_ARR(properties);
+                       RETURN_ARR(zend_proptable_to_symtable(properties, 0));
                }
-               RETURN_ARR(zend_array_dup(properties));
+               RETURN_ARR(zend_proptable_to_symtable(properties, 1));
        } else {
                array_init_size(return_value, zend_hash_num_elements(properties));
 
@@ -1273,9 +1270,19 @@ ZEND_FUNCTION(get_object_vars)
                                                const char *prop_name, *class_name;
                                                size_t prop_len;
                                                zend_unmangle_property_name_ex(key, &class_name, &prop_name, &prop_len);
+                                               /* We assume here that a mangled property name is never
+                                                * numeric. This is probably a safe assumption, but
+                                                * theoretically someone might write an extension with
+                                                * private, numeric properties. Well, too bad.
+                                                */
                                                zend_hash_str_add_new(Z_ARRVAL_P(return_value), prop_name, prop_len, value);
                                        } else {
-                                               zend_hash_add_new(Z_ARRVAL_P(return_value), key, value);
+                                               zend_ulong num_key;
+                                               if (ZEND_HANDLE_NUMERIC(key, num_key)) {
+                                                       zend_hash_index_add_new(Z_ARRVAL_P(return_value), num_key, value);
+                                               } else {
+                                                       zend_hash_add_new(Z_ARRVAL_P(return_value), key, value);
+                                               }
                                        }
                                }
                        }
index c3cae2a831c957a31b8f8717c5e2f9f0ddac6b8d..836c5a5a44f11d85a89f4555c6427f0cccba09f5 100644 (file)
@@ -2477,6 +2477,122 @@ ZEND_API int ZEND_FASTCALL _zend_handle_numeric_str_ex(const char *key, size_t l
        }
 }
 
+/* Takes a "symtable" hashtable (contains integer and non-numeric string keys)
+ * and converts it to a "proptable" (contains only string keys).
+ * If the symtable didn't need duplicating, its refcount is incremented.
+ */
+ZEND_API HashTable* ZEND_FASTCALL zend_symtable_to_proptable(HashTable *ht)
+{
+       zend_ulong num_key;
+       zend_string *str_key;
+       zval *zv;
+
+       if (UNEXPECTED(HT_IS_PACKED(ht))) {
+               goto convert;
+       }
+
+       ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) {
+               if (!str_key) {
+                       goto convert;
+               }
+       } ZEND_HASH_FOREACH_END();
+
+       if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
+               GC_REFCOUNT(ht)++;
+       }
+
+       return ht;
+
+convert:
+       {
+               HashTable *new_ht = emalloc(sizeof(HashTable));
+
+               zend_hash_init(new_ht, zend_hash_num_elements(ht), NULL, ZVAL_PTR_DTOR, 0);
+
+               ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) {
+                       if (!str_key) {
+                               str_key = zend_long_to_str(num_key);
+                               zend_string_delref(str_key);
+                       }
+                       do {
+                               if (Z_OPT_REFCOUNTED_P(zv)) {
+                                       if (Z_ISREF_P(zv) && Z_REFCOUNT_P(zv) == 1) {
+                                               zv = Z_REFVAL_P(zv);
+                                               if (!Z_OPT_REFCOUNTED_P(zv)) {
+                                                       break;
+                                               }
+                                       }
+                                       Z_ADDREF_P(zv);
+                               }
+                       } while (0);
+                       zend_hash_update(new_ht, str_key, zv);
+               } ZEND_HASH_FOREACH_END();
+
+               return new_ht;
+       }
+}
+
+/* Takes a "proptable" hashtable (contains only string keys) and converts it to
+ * a "symtable" (contains integer and non-numeric string keys).
+ * If the proptable didn't need duplicating, its refcount is incremented.
+ */
+ZEND_API HashTable* ZEND_FASTCALL zend_proptable_to_symtable(HashTable *ht, zend_bool always_duplicate)
+{
+       zend_ulong num_key;
+       zend_string *str_key;
+       zval *zv;
+
+       ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) {
+               /* The `str_key &&` here might seem redundant: property tables should
+                * only have string keys. Unfortunately, this isn't true, at the very
+                * least because of ArrayObject, which stores a symtable where the
+                * property table should be.
+                */
+               if (str_key && ZEND_HANDLE_NUMERIC(str_key, num_key)) {
+                       goto convert;
+               }
+       } ZEND_HASH_FOREACH_END();
+
+       if (always_duplicate) {
+               return zend_array_dup(ht);
+       }
+
+       if (EXPECTED(!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE))) {
+               GC_REFCOUNT(ht)++;
+       }
+
+       return ht;
+
+convert:
+       {
+               HashTable *new_ht = emalloc(sizeof(HashTable));
+
+               zend_hash_init(new_ht, zend_hash_num_elements(ht), NULL, ZVAL_PTR_DTOR, 0);
+
+               ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) {
+                       do {
+                               if (Z_OPT_REFCOUNTED_P(zv)) {
+                                       if (Z_ISREF_P(zv) && Z_REFCOUNT_P(zv) == 1) {
+                                               zv = Z_REFVAL_P(zv);
+                                               if (!Z_OPT_REFCOUNTED_P(zv)) {
+                                                       break;
+                                               }
+                                       }
+                                       Z_ADDREF_P(zv);
+                               }
+                       } while (0);
+                       /* Again, thank ArrayObject for `!str_key ||`. */
+                       if (!str_key || ZEND_HANDLE_NUMERIC(str_key, num_key)) {
+                               zend_hash_index_update(new_ht, num_key, zv);
+                       } else {
+                               zend_hash_update(new_ht, str_key, zv);
+                       }
+               } ZEND_HASH_FOREACH_END();
+
+               return new_ht;
+       }
+}
+
 /*
  * Local variables:
  * tab-width: 4
index 190551d3f127e9768523406673d0c5ebb9748142..e8c01f5dbd37e17d064646d4a53dbbf5ac247bf7 100644 (file)
@@ -247,6 +247,8 @@ ZEND_API uint32_t zend_array_count(HashTable *ht);
 ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source);
 ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht);
 ZEND_API void ZEND_FASTCALL zend_symtable_clean(HashTable *ht);
+ZEND_API HashTable* ZEND_FASTCALL zend_symtable_to_proptable(HashTable *ht);
+ZEND_API HashTable* ZEND_FASTCALL zend_proptable_to_symtable(HashTable *ht, zend_bool always_duplicate);
 
 ZEND_API int ZEND_FASTCALL _zend_handle_numeric_str_ex(const char *key, size_t length, zend_ulong *idx);
 
index 9a90250934af4320ebf23a0462b2601cc1abeb2e..80055776944618c2f49fc2545674055a90b10e9c 100644 (file)
@@ -588,25 +588,17 @@ try_again:
                                        if (obj_ht) {
                                                zend_array *arr;
 
+                                               /* fast copy */
                                                if (!Z_OBJCE_P(op)->default_properties_count &&
                                                        obj_ht == Z_OBJ_P(op)->properties &&
-                                                       !ZEND_HASH_GET_APPLY_COUNT(Z_OBJ_P(op)->properties)) {
-                                                       /* fast copy */
-                                                       if (EXPECTED(Z_OBJ_P(op)->handlers == &std_object_handlers)) {
-                                                               arr = obj_ht;
-                                                               if (EXPECTED(!(GC_FLAGS(Z_OBJ_P(op)->properties) & IS_ARRAY_IMMUTABLE))) {
-                                                                       GC_REFCOUNT(Z_OBJ_P(op)->properties)++;
-                                                               }
-                                                       } else {
-                                                               arr = zend_array_dup(obj_ht);
-                                                       }
-                                                       zval_dtor(op);
-                                                       ZVAL_ARR(op, arr);
+                                                       !ZEND_HASH_GET_APPLY_COUNT(Z_OBJ_P(op)->properties) &&
+                                                       EXPECTED(Z_OBJ_P(op)->handlers == &std_object_handlers)) {
+                                                       arr = zend_proptable_to_symtable(obj_ht, 0);
                                                } else {
-                                                       arr = zend_array_dup(obj_ht);
-                                                       zval_dtor(op);
-                                                       ZVAL_ARR(op, arr);
+                                                       arr = zend_proptable_to_symtable(obj_ht, 1);
                                                }
+                                               zval_dtor(op);
+                                               ZVAL_ARR(op, arr);
                                                return;
                                        }
                                } else {
@@ -645,10 +637,12 @@ try_again:
                case IS_ARRAY:
                        {
                                HashTable *ht = Z_ARR_P(op);
-                               if (Z_IMMUTABLE_P(op)) {
+                               ht = zend_symtable_to_proptable(ht);
+                               if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) {
                                        /* TODO: try not to duplicate immutable arrays as well ??? */
                                        ht = zend_array_dup(ht);
                                }
+                               zval_dtor(op);
                                object_and_properties_init(op, zend_standard_class_def, ht);
                                break;
                        }
index 75c0300151c12acf4a2138fcc140773b7874a6ba..3d6dc83fa0de648a0c641341053787001bd787a7 100644 (file)
@@ -24,6 +24,12 @@ Array
 Array
 (
     [0] => ReflectionProperty Object
+        (
+            [name] => 0
+            [class] => stdClass
+        )
+
+    [1] => ReflectionProperty Object
         (
             [name] => oo
             [class] => stdClass
index 2474b38ce63b65316f76600d12c81040432e7b08..dd10b5fb6b996210e33b76f510e69c5c202b7037 100644 (file)
@@ -18,17 +18,17 @@ array(2) {
   int(1)
 }
 object(stdClass)#1 (2) {
-  [1]=>
+  ["1"]=>
   int(1)
-  [0]=>
+  ["0"]=>
   int(2)
 }
 object(ArrayObject)#2 (1) {
   ["storage":"ArrayObject":private]=>
   object(stdClass)#1 (2) {
-    [1]=>
+    ["1"]=>
     int(1)
-    [0]=>
+    ["0"]=>
     int(2)
   }
 }
index 8caf40789f3a9ff84569293f3bd839a5a5aa345b..0b1f76094db9ee81ff1104538f368cbb160124d6 100644 (file)
@@ -7,7 +7,7 @@ var_export($a);
 ?>
 --EXPECT--
 stdClass::__set_state(array(
-   0 => 1,
-   1 => 3,
+   '0' => 1,
+   '1' => 3,
    'foo' => 'bar',
 ))
index b2762fd83a31e6dbd5f0bb6a52beae7331ad810e..b082fbf5f5b5add23535bb2da8cfb87d55455d90 100644 (file)
@@ -740,11 +740,11 @@ string(5) "array"
 -- Iteration 1 --
 bool(true)
 object(stdClass)#2 (3) {
-  [0]=>
+  ["0"]=>
   int(1)
-  [1]=>
+  ["1"]=>
   int(2)
-  [2]=>
+  ["2"]=>
   int(3)
 }
 string(6) "object"
@@ -758,11 +758,11 @@ string(6) "object"
 -- Iteration 3 --
 bool(true)
 object(stdClass)#2 (3) {
-  [0]=>
+  ["0"]=>
   int(2)
-  [1]=>
+  ["1"]=>
   int(3)
-  [2]=>
+  ["2"]=>
   int(4)
 }
 string(6) "object"
index df2dbaf461ed9fe6595f34562331f8d42e98d17a..ac29a1bf38f8a9354987acb594307bb7f632d9a4 100644 (file)
@@ -265,11 +265,11 @@ array(0) {
 }
 bool(true)
 object(stdClass)#%d (3) {
-  [0]=>
+  ["0"]=>
   int(1)
-  [1]=>
+  ["1"]=>
   int(2)
-  [2]=>
+  ["2"]=>
   int(3)
 }
 bool(true)
@@ -279,11 +279,11 @@ object(stdClass)#%d (1) {
 }
 bool(true)
 object(stdClass)#%d (3) {
-  [0]=>
+  ["0"]=>
   int(2)
-  [1]=>
+  ["1"]=>
   int(3)
-  [2]=>
+  ["2"]=>
   int(4)
 }
 bool(true)