diff --git a/UPGRADING b/UPGRADING index 08f353a188ddc..13c44605a5256 100644 --- a/UPGRADING +++ b/UPGRADING @@ -29,6 +29,20 @@ PHP 7.4 UPGRADE NOTES supported and resulted in corrupted reflection objects. It has been explicitly prohibited now. +- SPL: + . Calling get_object_vars() on an ArrayObject instance will now always return + the properties of the ArrayObject itself (or a subclass). Previously it + returned the values of the wrapped array/object unless the STD_PROP_LIST + flag was specified. Other affected operations are: + * ReflectionObject::getProperties() + * array_key_exists(). Use isset() or offsetExists() instead. + * reset(), current(), etc. Use Iterator methods instead. + * Potentially others working on object properties as a list. + + (array) casts are *not* affected. They will continue to return either the + wrapped array, or the ArrayObject properties, depending on whether the + STD_PROP_LIST flag is used. + ======================================== 2. New Features ======================================== diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 96395a755421f..79cdcd197288b 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1687,6 +1687,21 @@ ZEND_API int zend_std_cast_object_tostring(zval *readobj, zval *writeobj, int ty } } return FAILURE; + case IS_ARRAY: + { + HashTable *obj_ht = Z_OBJ_HT_P(readobj)->get_properties(readobj); + if (obj_ht) { + /* fast copy */ + obj_ht = zend_proptable_to_symtable(obj_ht, + (Z_OBJCE_P(readobj)->default_properties_count || + Z_OBJ_P(readobj)->handlers != &std_object_handlers || + GC_IS_RECURSIVE(obj_ht))); + ZVAL_ARR(writeobj, obj_ht); + } else { + array_init(writeobj); + } + return SUCCESS; + } case _IS_BOOL: ZVAL_TRUE(writeobj); return SUCCESS; diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 71663ae31cd18..0e3ae6b6eae07 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -610,27 +610,13 @@ ZEND_API void ZEND_FASTCALL convert_to_array(zval *op) /* {{{ */ if (Z_OBJCE_P(op) == zend_ce_closure) { convert_scalar_to_array(op); } else { - if (Z_OBJ_HT_P(op)->get_properties) { - HashTable *obj_ht = Z_OBJ_HT_P(op)->get_properties(op); - if (obj_ht) { - /* fast copy */ - obj_ht = zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(op)->default_properties_count || - Z_OBJ_P(op)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht))); - zval_ptr_dtor(op); - ZVAL_ARR(op, obj_ht); - return; - } - } else { - zval dst; - convert_object_to_type(op, &dst, IS_ARRAY, convert_to_array); + zval dst; + convert_object_to_type(op, &dst, IS_ARRAY, convert_to_array); - if (Z_TYPE(dst) == IS_ARRAY) { - zval_ptr_dtor(op); - ZVAL_COPY_VALUE(op, &dst); - return; - } + if (Z_TYPE(dst) == IS_ARRAY) { + zval_ptr_dtor(op); + ZVAL_COPY_VALUE(op, &dst); + return; } zval_ptr_dtor(op); @@ -2094,6 +2080,10 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) ret = compare_function(result, op_free, op2); zend_free_obj_get_result(op_free); return ret; + } else if (Z_TYPE_P(op2) == IS_ARRAY) { + /* Treat array like null */ + ZVAL_LONG(result, 1); + return SUCCESS; } else if (Z_TYPE_P(op2) != IS_OBJECT && Z_OBJ_HT_P(op1)->cast_object) { ZVAL_UNDEF(&tmp_free); if (Z_OBJ_HT_P(op1)->cast_object(op1, &tmp_free, ((Z_TYPE_P(op2) == IS_FALSE || Z_TYPE_P(op2) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(op2))) == FAILURE) { @@ -2113,6 +2103,10 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) ret = compare_function(result, op1, op_free); zend_free_obj_get_result(op_free); return ret; + } else if (Z_TYPE_P(op1) == IS_ARRAY) { + /* Treat array like null */ + ZVAL_LONG(result, -1); + return SUCCESS; } else if (Z_TYPE_P(op1) != IS_OBJECT && Z_OBJ_HT_P(op2)->cast_object) { ZVAL_UNDEF(&tmp_free); if (Z_OBJ_HT_P(op2)->cast_object(op2, &tmp_free, ((Z_TYPE_P(op1) == IS_FALSE || Z_TYPE_P(op1) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(op1))) == FAILURE) { diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index cc006024e57e7..4251eb61f22ed 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5307,22 +5307,25 @@ ZEND_VM_COLD_CONST_HANDLER(21, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE) } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_HT_P(expr)->get_properties) { - HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr); - if (obj_ht) { - /* fast copy */ - obj_ht = zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht))); - ZVAL_ARR(result, obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } } else { - ZVAL_COPY_VALUE(result, expr); - Z_ADDREF_P(result); - convert_to_array(result); + ZVAL_UNDEF(result); + if (Z_OBJ_HT_P(expr)->cast_object) { + if (Z_OBJ_HT_P(expr)->cast_object(expr, result, IS_ARRAY) == FAILURE) { + zend_error(E_RECOVERABLE_ERROR, + "Object of class %s could not be converted to array", + ZSTR_VAL(Z_OBJCE_P(expr)->name)); + } + } else if (Z_OBJ_HT_P(expr)->get) { + zval *newop = Z_OBJ_HT_P(expr)->get(expr, result); + if (Z_TYPE_P(newop) != IS_OBJECT) { + /* for safety - avoid loop */ + ZVAL_COPY_VALUE(result, newop); + convert_to_array(result); + } + } + if (UNEXPECTED(Z_TYPE_P(result) != IS_ARRAY)) { + array_init(result); + } } } else { ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 5760612f003eb..30293984df4ac 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3121,22 +3121,25 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CONST_H } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_HT_P(expr)->get_properties) { - HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr); - if (obj_ht) { - /* fast copy */ - obj_ht = zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht))); - ZVAL_ARR(result, obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } } else { - ZVAL_COPY_VALUE(result, expr); - Z_ADDREF_P(result); - convert_to_array(result); + ZVAL_UNDEF(result); + if (Z_OBJ_HT_P(expr)->cast_object) { + if (Z_OBJ_HT_P(expr)->cast_object(expr, result, IS_ARRAY) == FAILURE) { + zend_error(E_RECOVERABLE_ERROR, + "Object of class %s could not be converted to array", + ZSTR_VAL(Z_OBJCE_P(expr)->name)); + } + } else if (Z_OBJ_HT_P(expr)->get) { + zval *newop = Z_OBJ_HT_P(expr)->get(expr, result); + if (Z_TYPE_P(newop) != IS_OBJECT) { + /* for safety - avoid loop */ + ZVAL_COPY_VALUE(result, newop); + convert_to_array(result); + } + } + if (UNEXPECTED(Z_TYPE_P(result) != IS_ARRAY)) { + array_init(result); + } } } else { ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); @@ -18092,22 +18095,25 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_TMP_HANDLER(ZEND_OPC } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_HT_P(expr)->get_properties) { - HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr); - if (obj_ht) { - /* fast copy */ - obj_ht = zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht))); - ZVAL_ARR(result, obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } } else { - ZVAL_COPY_VALUE(result, expr); - Z_ADDREF_P(result); - convert_to_array(result); + ZVAL_UNDEF(result); + if (Z_OBJ_HT_P(expr)->cast_object) { + if (Z_OBJ_HT_P(expr)->cast_object(expr, result, IS_ARRAY) == FAILURE) { + zend_error(E_RECOVERABLE_ERROR, + "Object of class %s could not be converted to array", + ZSTR_VAL(Z_OBJCE_P(expr)->name)); + } + } else if (Z_OBJ_HT_P(expr)->get) { + zval *newop = Z_OBJ_HT_P(expr)->get(expr, result); + if (Z_TYPE_P(newop) != IS_OBJECT) { + /* for safety - avoid loop */ + ZVAL_COPY_VALUE(result, newop); + convert_to_array(result); + } + } + if (UNEXPECTED(Z_TYPE_P(result) != IS_ARRAY)) { + array_init(result); + } } } else { ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); @@ -21100,22 +21106,25 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_VAR_HANDLER(ZEND_OPC } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_HT_P(expr)->get_properties) { - HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr); - if (obj_ht) { - /* fast copy */ - obj_ht = zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht))); - ZVAL_ARR(result, obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } } else { - ZVAL_COPY_VALUE(result, expr); - Z_ADDREF_P(result); - convert_to_array(result); + ZVAL_UNDEF(result); + if (Z_OBJ_HT_P(expr)->cast_object) { + if (Z_OBJ_HT_P(expr)->cast_object(expr, result, IS_ARRAY) == FAILURE) { + zend_error(E_RECOVERABLE_ERROR, + "Object of class %s could not be converted to array", + ZSTR_VAL(Z_OBJCE_P(expr)->name)); + } + } else if (Z_OBJ_HT_P(expr)->get) { + zval *newop = Z_OBJ_HT_P(expr)->get(expr, result); + if (Z_TYPE_P(newop) != IS_OBJECT) { + /* for safety - avoid loop */ + ZVAL_COPY_VALUE(result, newop); + convert_to_array(result); + } + } + if (UNEXPECTED(Z_TYPE_P(result) != IS_ARRAY)) { + array_init(result); + } } } else { ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); @@ -37467,22 +37476,25 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CV_HANDLER(ZEND_OPCO } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_HT_P(expr)->get_properties) { - HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr); - if (obj_ht) { - /* fast copy */ - obj_ht = zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht))); - ZVAL_ARR(result, obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } } else { - ZVAL_COPY_VALUE(result, expr); - Z_ADDREF_P(result); - convert_to_array(result); + ZVAL_UNDEF(result); + if (Z_OBJ_HT_P(expr)->cast_object) { + if (Z_OBJ_HT_P(expr)->cast_object(expr, result, IS_ARRAY) == FAILURE) { + zend_error(E_RECOVERABLE_ERROR, + "Object of class %s could not be converted to array", + ZSTR_VAL(Z_OBJCE_P(expr)->name)); + } + } else if (Z_OBJ_HT_P(expr)->get) { + zval *newop = Z_OBJ_HT_P(expr)->get(expr, result); + if (Z_TYPE_P(newop) != IS_OBJECT) { + /* for safety - avoid loop */ + ZVAL_COPY_VALUE(result, newop); + convert_to_array(result); + } + } + if (UNEXPECTED(Z_TYPE_P(result) != IS_ARRAY)) { + array_init(result); + } } } else { ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); diff --git a/ext/reflection/tests/bug61388.phpt b/ext/reflection/tests/bug61388.phpt index 3d6dc83fa0de6..b9fe6bfaaa91b 100644 --- a/ext/reflection/tests/bug61388.phpt +++ b/ext/reflection/tests/bug61388.phpt @@ -14,12 +14,6 @@ print_r($reflObj->getProperties(ReflectionProperty::IS_PUBLIC)); --EXPECT-- Array ( - [0] => ReflectionProperty Object - ( - [name] => test - [class] => ArrayObject - ) - ) Array ( diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index ab394b5c83b76..9ad636cf61515 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -1865,6 +1865,10 @@ static int sxe_object_cast_ex(zval *readobj, zval *writeobj, int type) sxe = Z_SXEOBJ_P(readobj); + if (type == IS_ARRAY) { + return zend_std_cast_object_tostring(readobj, writeobj, type); + } + if (type == _IS_BOOL) { node = php_sxe_get_first_node(sxe, NULL); if (node) { diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 63345e6e331d8..a1be4adc0e9f8 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -807,20 +807,6 @@ SPL_METHOD(Array, getArrayCopy) RETURN_ARR(zend_array_dup(spl_array_get_hash_table(intern))); } /* }}} */ -static HashTable *spl_array_get_properties(zval *object) /* {{{ */ -{ - spl_array_object *intern = Z_SPLARRAY_P(object); - - if (intern->ar_flags & SPL_ARRAY_STD_PROP_LIST) { - if (!intern->std.properties) { - rebuild_object_properties(&intern->std); - } - return intern->std.properties; - } - - return spl_array_get_hash_table(intern); -} /* }}} */ - static HashTable* spl_array_get_debug_info(zval *obj, int *is_temp) /* {{{ */ { zval *storage; @@ -949,6 +935,18 @@ static int spl_array_compare_objects(zval *o1, zval *o2) /* {{{ */ return result; } /* }}} */ +static int spl_array_cast_object(zval *obj, zval *result, int type) /* {{{ */ +{ + spl_array_object *intern = Z_SPLARRAY_P(obj); + + if (type != IS_ARRAY || (intern->ar_flags & SPL_ARRAY_STD_PROP_LIST)) { + return zend_std_cast_object_tostring(obj, result, type); + } + + ZVAL_ARR(result, zend_array_dup(spl_array_get_hash_table(intern))); + return SUCCESS; +} /* }}} */ + static int spl_array_skip_protected(spl_array_object *intern, HashTable *aht) /* {{{ */ { zend_string *string_key; @@ -2013,7 +2011,6 @@ PHP_MINIT_FUNCTION(spl_array) spl_handler_ArrayObject.has_dimension = spl_array_has_dimension; spl_handler_ArrayObject.count_elements = spl_array_object_count_elements; - spl_handler_ArrayObject.get_properties = spl_array_get_properties; spl_handler_ArrayObject.get_debug_info = spl_array_get_debug_info; spl_handler_ArrayObject.get_gc = spl_array_get_gc; spl_handler_ArrayObject.read_property = spl_array_read_property; @@ -2023,6 +2020,7 @@ PHP_MINIT_FUNCTION(spl_array) spl_handler_ArrayObject.unset_property = spl_array_unset_property; spl_handler_ArrayObject.compare_objects = spl_array_compare_objects; + spl_handler_ArrayObject.cast_object = spl_array_cast_object; spl_handler_ArrayObject.dtor_obj = zend_objects_destroy_object; spl_handler_ArrayObject.free_obj = spl_array_object_free_storage; diff --git a/ext/spl/tests/ArrayObject_get_object_vars.phpt b/ext/spl/tests/ArrayObject_get_object_vars.phpt new file mode 100644 index 0000000000000..a80add6b95ad7 --- /dev/null +++ b/ext/spl/tests/ArrayObject_get_object_vars.phpt @@ -0,0 +1,30 @@ +--TEST-- +get_object_vars() on ArrayObject works on the properties of the ArrayObject itself +--FILE-- +getObjectVars()); + +?> +--EXPECT-- +array(0) { +} +array(1) { + ["test"]=> + NULL +} diff --git a/ext/spl/tests/array_017.phpt b/ext/spl/tests/array_017.phpt index ed1286332cd95..0fde103627a0a 100644 --- a/ext/spl/tests/array_017.phpt +++ b/ext/spl/tests/array_017.phpt @@ -139,13 +139,17 @@ array(3) { ["Flags"]=> int(0) ["OVars"]=> - array(3) { - [0]=> - int(1) - ["a"]=> - int(25) + array(5) { ["pub1"]=> - int(42) + int(1) + ["pro1"]=> + int(2) + ["pri1"]=> + int(3) + ["imp1"]=> + int(4) + ["dyn1"]=> + int(5) } ["$this"]=> object(ArrayObjectEx)#%d (6) { @@ -178,13 +182,17 @@ array(3) { ["Flags"]=> int(0) ["OVars"]=> - array(3) { - [0]=> + array(5) { + ["pub2"]=> int(1) - ["a"]=> - int(25) - ["pub1"]=> - int(42) + ["pro2"]=> + int(2) + ["pri2"]=> + int(3) + ["imp2"]=> + int(4) + ["dyn2"]=> + int(5) } ["$this"]=> object(ArrayIteratorEx)#%d (6) { @@ -242,13 +250,17 @@ array(3) { ["Flags"]=> int(0) ["OVars"]=> - array(3) { - [0]=> + array(5) { + ["pub2"]=> int(1) - ["a"]=> - int(25) - ["pub1"]=> - int(42) + ["pro2"]=> + int(2) + ["pri2"]=> + int(3) + ["imp2"]=> + int(4) + ["dyn2"]=> + int(5) } ["$this"]=> object(ArrayIteratorEx)#%d (6) { @@ -541,14 +553,16 @@ array(3) { ["Flags"]=> int(0) ["OVars"]=> - array(4) { - ["pub1"]=> + array(5) { + ["pub2"]=> int(1) - ["pro1"]=> + ["pro2"]=> int(2) - ["imp1"]=> + ["pri2"]=> + int(3) + ["imp2"]=> int(4) - ["dyn1"]=> + ["dyn2"]=> int(5) } ["$this"]=> @@ -598,14 +612,16 @@ array(3) { ["Flags"]=> int(0) ["OVars"]=> - array(4) { - ["pub1"]=> + array(5) { + ["pub2"]=> int(1) - ["pro1"]=> + ["pro2"]=> int(2) - ["imp1"]=> + ["pri2"]=> + int(3) + ["imp2"]=> int(4) - ["dyn1"]=> + ["dyn2"]=> int(5) } ["$this"]=> diff --git a/ext/spl/tests/bug61347.phpt b/ext/spl/tests/bug61347.phpt index cb091858ae50d..720cf5b0f250f 100644 --- a/ext/spl/tests/bug61347.phpt +++ b/ext/spl/tests/bug61347.phpt @@ -12,7 +12,7 @@ var_dump(isset($b['no_exists'])); //false var_dump(empty($b['b'])); //true var_dump(empty($b[37])); //true -var_dump(array_key_exists('b', $b)); //true +var_dump(array_key_exists('b', $b)); //false var_dump($b['b']); $a = array('b' => '', 37 => false); @@ -31,7 +31,7 @@ bool(false) bool(false) bool(true) bool(true) -bool(true) +bool(false) NULL bool(true) bool(true) diff --git a/ext/standard/tests/general_functions/bug47027.phpt b/ext/standard/tests/general_functions/bug47027.phpt index e4f5aae9dd744..524764d0393d7 100644 --- a/ext/standard/tests/general_functions/bug47027.phpt +++ b/ext/standard/tests/general_functions/bug47027.phpt @@ -7,6 +7,4 @@ var_export ($ao); ?> --EXPECT-- ArrayObject::__set_state(array( - 2 => 'foo', - 'bar' => 'baz', )) diff --git a/tests/classes/arrayobject_001.phpt b/tests/classes/arrayobject_001.phpt deleted file mode 100644 index b75f8c7ab3cc7..0000000000000 --- a/tests/classes/arrayobject_001.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Ensure that ArrayObject acts like an array ---FILE-- - ---EXPECT-- -bar1bar