php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #66173 Object/Array cast loses numeric property/element accessibility
Submitted: 2013-11-26 06:08 UTC Modified: 2016-11-14 18:31 UTC
Votes:11
Avg. Score:4.3 ± 0.7
Reproduced:11 of 11 (100.0%)
Same Version:9 (81.8%)
Same OS:8 (72.7%)
From: to dot yashin at gmail dot com Assigned: ajf (profile)
Status: Closed Package: Arrays related
PHP Version: 5.0 to master OS: *
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: to dot yashin at gmail dot com
New email:
PHP Version: OS:

 

 [2013-11-26 06:08 UTC] to dot yashin at gmail dot com
Description:
------------
Can not access an array element by index after casting

Test script:
---------------
<?php

$object = new StdClass();
$object->{'1'} = 'test';
/**
 * object(stdClass)#1 (1) {
 *  ["1"]=>
 *   string(4) "test"
 *  }
 */

$array = (array) $object;
/**
 * array(1) {
 *  ["1"]=>
 *   string(4) "test"
 *  }
 */

var_dump($array[1]);
// NULL
var_dump($array['1']);
// NULL

Expected result:
----------------
var_dump($array['1']);
// 'test'

Actual result:
--------------
var_dump($array['1']);
// NULL

Patches

Pull Requests

Pull requests:

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2013-11-29 01:52 UTC] phpmpan at mpan dot pl
It seems that:

`zend_hash_copy` blindly copies string key from the source object's hash table to the target array's hash table.

Numeric string keys are converted to numbers on array access and `zend_hash_index_find` is used, which chooses wrong hash, wrong `nIndex` is produced and finally wrong `arBucket` is choosen, which leads to the element not being found.
 [2013-12-05 11:31 UTC] inefedor at gmail dot com
This is pretty old bug as I know, I had nothing to do so I made a patch, but I doubt it will be merged because this use case is so rare...

diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c
index 96c3f3d..14381b9 100644
--- a/Zend/zend_hash.c
+++ b/Zend/zend_hash.c
@@ -810,13 +810,20 @@ ZEND_API void zend_hash_reverse_apply(HashTable *ht, apply_func_t apply_func TSR
 	HASH_UNPROTECT_RECURSION(ht);
 }
 
-
-ZEND_API void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size)
+ZEND_API void zend_hash_copy_ex(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size, int reindex_numeric_keys)
 {
 	Bucket *p;
 	void *new_entry;
 	zend_bool setTargetPointer;
+	long *longKey;
+	double *doubleKey;
+	int keyType;
 
+	if (reindex_numeric_keys) {
+		longKey = emalloc(sizeof(long));
+		doubleKey = emalloc(sizeof(double));
+	}
+	
 	IS_CONSISTENT(source);
 	IS_CONSISTENT(target);
 
@@ -826,11 +833,21 @@ ZEND_API void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_fun
 		if (setTargetPointer && source->pInternalPointer == p) {
 			target->pInternalPointer = NULL;
 		}
-		if (p->nKeyLength) {
+
+		if (p->nKeyLength && ( ! reindex_numeric_keys ||
+				! (keyType = is_numeric_string(p->arKey, p->nKeyLength, longKey, doubleKey, -1)))) {
+
 			zend_hash_quick_update(target, p->arKey, p->nKeyLength, p->h, p->pData, size, &new_entry);
-		} else {
+		} else if ( ! p->nKeyLength) {
 			zend_hash_index_update(target, p->h, p->pData, size, &new_entry);
+		} else {
+			if (keyType == IS_DOUBLE) {
+				*longKey = zend_dval_to_lval(*doubleKey);
+			}
+
+			zend_hash_index_update(target, *longKey, p->pData, size, &new_entry);
 		}
+
 		if (pCopyConstructor) {
 			pCopyConstructor(new_entry);
 		}
@@ -841,6 +858,10 @@ ZEND_API void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_fun
 	}
 }
 
+ZEND_API inline void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size)
+{
+	zend_hash_copy_ex(target, source, pCopyConstructor, tmp, size, 0);
+}
 
 ZEND_API void _zend_hash_merge(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size, int overwrite ZEND_FILE_LINE_DC)
 {
diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c
index 5c84deb2..55ab2c2 100644
--- a/Zend/zend_operators.c
+++ b/Zend/zend_operators.c
@@ -709,7 +709,7 @@ ZEND_API void convert_to_array(zval *op) /* {{{ */
 				} else if (Z_OBJ_HT_P(op)->get_properties) {
 					HashTable *obj_ht = Z_OBJ_HT_P(op)->get_properties(op TSRMLS_CC);
 					if (obj_ht) {
-						zend_hash_copy(ht, obj_ht, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));
+						zend_hash_copy_ex(ht, obj_ht, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *), 1);
 					}
 				} else {
 					convert_object_to_type(op, IS_ARRAY, convert_to_array);
 [2014-01-08 02:01 UTC] yohgaki@php.net
-Status: Open +Status: Analyzed
 [2014-01-08 02:01 UTC] yohgaki@php.net
Usually, object property name follows variable name rules.

http://www.php.net/manual/en/language.variables.basics.php

So we cannot have digits as first char of property name.
However, we may override this limitation by using {}, though.

[yohgaki@dev PHP-5.3]$ php -r '$obj = new StdClass; $obj->{12} = 234; ${1} = 567; var_dump($obj, ${1});'
object(stdClass)#1 (1) {
  ["12"]=>
  int(234)
}
int(567)


http://3v4l.org/vEE1Z


PHP is creating numeric property by $obj->{'1'}, but it seems odd to me.
Anyway, I've added this bug to RFC.

https://wiki.php.net/rfc/comparison_inconsistency#object_array_conversion_of_numeric_propertyindex
 [2014-01-08 02:31 UTC] phpmpan at mpan dot pl
Maybe it should be prohibited to pass illegal names to ->{} operator? Pros:
 - Such names can't be accessed with -> operator anyway, so they have marginal use cases.
 - This bug.
 - Potentially bugs can arise in user code that assumes objects can't contain invalid names.
 [2014-01-11 01:27 UTC] yohgaki@php.net
Variable/property that has numeric name can be accessed, but object to array conversion breaks accessibility. 

http://3v4l.org/DY9bZ

It's better to be fixed. IMO.
 [2014-01-11 01:44 UTC] yohgaki@php.net
Array to object conversion seems to have similar issue.

http://3v4l.org/vFuWT
 [2014-01-11 02:09 UTC] yohgaki@php.net
-Summary: Can not access an array element by index +Summary: Object/Array cast looses numeric property/element accessibility -Operating System: ubuntu 13.10 +Operating System: * -PHP Version: 5.5.6 +PHP Version: 5.0 to master
 [2015-12-23 23:04 UTC] ajf@php.net
-Assigned To: +Assigned To: ajf
 [2015-12-23 23:43 UTC] ajf@php.net
-Summary: Object/Array cast looses numeric property/element accessibility +Summary: Object/Array cast loses numeric property/element accessibility
 [2015-12-23 23:43 UTC] ajf@php.net
Corrected spelling error
 [2016-10-17 02:38 UTC] rene at pasing dot net
There is a backwards-imcompatible thingy going on in the 7.0-branch which is related to this bug and is not documented somewhere.
See this as an example: https://3v4l.org/tofaS
Can we somehow expect a bugfix and/or documented behaviour?
 [2016-10-17 07:45 UTC] ajf@php.net
That one was already reported as bug #72254
 [2016-11-14 18:31 UTC] ajf@php.net
-Status: Analyzed +Status: Closed
 [2016-11-14 18:31 UTC] ajf@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.

Fixed by: https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 11:01:29 2024 UTC