Browse Source

Convert numeric keys in object/array casts

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().
pull/1248/merge
Andrea Faulds 9 years ago
parent
commit
a0502b89a6
  1. 2
      NEWS
  2. 10
      UPGRADING
  3. BIN
      Zend/tests/cast_to_object.phpt
  4. 12
      Zend/tests/object_array_cast.phpt
  5. BIN
      Zend/tests/settype_object.phpt
  6. 19
      Zend/zend_builtin_functions.c
  7. 116
      Zend/zend_hash.c
  8. 2
      Zend/zend_hash.h
  9. 26
      Zend/zend_operators.c
  10. 6
      ext/reflection/tests/bug61388.phpt
  11. 8
      ext/spl/tests/arrayObject___construct_basic7.phpt
  12. 4
      ext/standard/tests/array/var_export.phpt
  13. 12
      ext/standard/tests/general_functions/gettype_settype_basic.phpt
  14. 12
      ext/standard/tests/general_functions/type.phpt

2
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)

10
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

BIN
Zend/tests/cast_to_object.phpt

12
Zend/tests/object_array_cast.phpt

@ -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)
}

BIN
Zend/tests/settype_object.phpt

19
Zend/zend_builtin_functions.c

@ -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);
}
}
}
}

116
Zend/zend_hash.c

@ -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

2
Zend/zend_hash.h

@ -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);

26
Zend/zend_operators.c

@ -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;
}

6
ext/reflection/tests/bug61388.phpt

@ -24,6 +24,12 @@ Array
Array
(
[0] => ReflectionProperty Object
(
[name] => 0
[class] => stdClass
)
[1] => ReflectionProperty Object
(
[name] => oo
[class] => stdClass

8
ext/spl/tests/arrayObject___construct_basic7.phpt

@ -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)
}
}

4
ext/standard/tests/array/var_export.phpt

@ -7,7 +7,7 @@ var_export($a);
?>
--EXPECT--
stdClass::__set_state(array(
0 => 1,
1 => 3,
'0' => 1,
'1' => 3,
'foo' => 'bar',
))

12
ext/standard/tests/general_functions/gettype_settype_basic.phpt

@ -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"

12
ext/standard/tests/general_functions/type.phpt

@ -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)

Loading…
Cancel
Save