mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
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().
This commit is contained in:
parent
531eedf38f
commit
a0502b89a6
14 changed files with 183 additions and 46 deletions
2
NEWS
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
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
|
||||
|
|
Binary file not shown.
|
@ -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)
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -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
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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,12 @@ Array
|
|||
Array
|
||||
(
|
||||
[0] => ReflectionProperty Object
|
||||
(
|
||||
[name] => 0
|
||||
[class] => stdClass
|
||||
)
|
||||
|
||||
[1] => ReflectionProperty Object
|
||||
(
|
||||
[name] => oo
|
||||
[class] => stdClass
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ var_export($a);
|
|||
?>
|
||||
--EXPECT--
|
||||
stdClass::__set_state(array(
|
||||
0 => 1,
|
||||
1 => 3,
|
||||
'0' => 1,
|
||||
'1' => 3,
|
||||
'foo' => 'bar',
|
||||
))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue