diff options
| -rw-r--r-- | Zend/zend_execute.c | 6 | ||||
| -rwxr-xr-x | Zend/zend_interfaces.c | 30 | ||||
| -rwxr-xr-x | Zend/zend_interfaces.h | 1 | ||||
| -rw-r--r-- | Zend/zend_object_handlers.c | 72 | ||||
| -rw-r--r-- | Zend/zend_operators.c | 17 | ||||
| -rw-r--r-- | Zend/zend_operators.h | 1 | ||||
| -rw-r--r-- | tests/classes/array_access_001.phpt | 198 | ||||
| -rw-r--r-- | tests/classes/array_access_002.phpt | 198 | ||||
| -rw-r--r-- | tests/classes/array_access_003.phpt | 59 | ||||
| -rw-r--r-- | tests/classes/array_access_004.phpt | 58 |
10 files changed, 605 insertions, 35 deletions
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 21d80531a1..be3ca78e22 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -920,13 +920,17 @@ static void zend_fetch_dimension_address(znode *result, znode *op1, znode *op2, } break; case IS_OBJECT: - if (type == BP_VAR_R) { + if (type == BP_VAR_R || type == BP_VAR_RW) { if (!Z_OBJ_HT_P(container)->read_dimension) { zend_error(E_ERROR, "Cannot use object as array"); } else { zval *dim = get_zval_ptr(op2, Ts, &EG(free_op2), BP_VAR_R); zval *overloaded_result = Z_OBJ_HT_P(container)->read_dimension(container, dim TSRMLS_CC); + if (type == BP_VAR_RW && !overloaded_result->is_ref) { + zend_error(E_ERROR, "Objects used as arrays in post/pre increment/decrement must return values by reference"); + } + *retval = &overloaded_result; AI_USE_PTR(T(result->u.var).var); FREE_OP(Ts, op2, EG(free_op2)); diff --git a/Zend/zend_interfaces.c b/Zend/zend_interfaces.c index 77a8deb6cd..52961b2caa 100755 --- a/Zend/zend_interfaces.c +++ b/Zend/zend_interfaces.c @@ -25,6 +25,7 @@ zend_class_entry *zend_ce_traversable; zend_class_entry *zend_ce_aggregate; zend_class_entry *zend_ce_iterator; +zend_class_entry *zend_ce_arrayaccess; /* {{{ zend_call_method Only returns the returned zval if retval_ptr != NULL */ @@ -344,6 +345,13 @@ static int zend_implement_iterator(zend_class_entry *interface, zend_class_entry } /* }}} */ +/* {{{ zend_implement_arrayaccess */ +static int zend_implement_arrayaccess(zend_class_entry *interface, zend_class_entry *class_type TSRMLS_DC) +{ + return SUCCESS; +} +/* }}}*/ + /* {{{ function tables */ zend_function_entry zend_funcs_aggregate[] = { ZEND_ABSTRACT_ME(iterator, getIterator, NULL) @@ -360,6 +368,26 @@ zend_function_entry zend_funcs_iterator[] = { }; zend_function_entry *zend_funcs_traversable = NULL; + +static +ZEND_BEGIN_ARG_INFO(arginfo_arrayaccess_offset, 0) + ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO(); + +static +ZEND_BEGIN_ARG_INFO(arginfo_arrayaccess_offset_value, 0) + ZEND_ARG_INFO(0, offset) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO(); + +zend_function_entry zend_funcs_arrayaccess[] = { + ZEND_ABSTRACT_ME(arrayaccess, offsetExists, arginfo_arrayaccess_offset) + ZEND_ABSTRACT_ME(arrayaccess, offsetGet, arginfo_arrayaccess_offset) + ZEND_ABSTRACT_ME(arrayaccess, offsetSet, arginfo_arrayaccess_offset_value) + ZEND_ABSTRACT_ME(arrayaccess, offsetUnset, arginfo_arrayaccess_offset) + {NULL, NULL, NULL} +}; + /* }}} */ #define REGISTER_ITERATOR_INTERFACE(class_name, class_name_str) \ @@ -383,6 +411,8 @@ ZEND_API void zend_register_interfaces(TSRMLS_D) REGISTER_ITERATOR_INTERFACE(iterator, Iterator); REGISTER_ITERATOR_IMPLEMENT(iterator, traversable); + + REGISTER_ITERATOR_INTERFACE(arrayaccess, ArrayAccess); } /* }}} */ diff --git a/Zend/zend_interfaces.h b/Zend/zend_interfaces.h index b7c671b9f2..2f881a5835 100755 --- a/Zend/zend_interfaces.h +++ b/Zend/zend_interfaces.h @@ -24,6 +24,7 @@ ZEND_API zend_class_entry *zend_ce_traversable; ZEND_API zend_class_entry *zend_ce_aggregate; ZEND_API zend_class_entry *zend_ce_iterator; +ZEND_API zend_class_entry *zend_ce_arrayaccess; ZEND_API zval* zend_call_method(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2 TSRMLS_DC); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index e0f3e4e145..35c87eb225 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -26,6 +26,7 @@ #include "zend_objects.h" #include "zend_objects_API.h" #include "zend_object_handlers.h" +#include "zend_interfaces.h" #define DEBUG_OBJECT_HANDLERS 0 @@ -352,44 +353,49 @@ static void zend_std_write_property(zval *object, zval *member, zval *value TSRM zval *zend_std_read_dimension(zval *object, zval *offset TSRMLS_DC) { -#if 1 - zend_error(E_ERROR, "Cannot use object as array"); -#else - zend_printf("Fetching from object: "); - zend_print_zval(object, 0); - - zend_printf("\n the offset: "); - zend_print_zval(offset, 0); - - zend_printf("\n"); -#endif - return EG(uninitialized_zval_ptr); + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval; + + if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { + zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset); + if (retval->refcount > 0) { /* Should always be the case */ + retval->refcount--; + } + return retval; + } else { + zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); + return 0; + } } static void zend_std_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) { -#if 1 - zend_error(E_ERROR, "Cannot use object as array"); -#else - zend_printf("Assigning to object: "); - zend_print_zval(object, 0); - - zend_printf("\n with offset: "); - zend_print_zval(offset, 0); - - zend_printf("\n the value: "); - zend_print_zval(value, 0); - - zend_printf("\n"); -#endif + zend_class_entry *ce = Z_OBJCE_P(object); + + if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { + zend_call_method_with_2_params(&object, ce, NULL, "offsetset", NULL, offset, value); + } else { + zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); + } } static int zend_std_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC) { - zend_error(E_ERROR, "Cannot use object as array"); - return 0; + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval; + int result; + + if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { + zend_call_method_with_1_params(&object, ce, NULL, "offsetexists", &retval, offset); + result = i_zend_is_true(retval); + zval_ptr_dtor(&retval); + return result; + } else { + zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); + return 0; + } } @@ -463,7 +469,15 @@ static void zend_std_unset_property(zval *object, zval *member TSRMLS_DC) static void zend_std_unset_dimension(zval *object, zval *offset TSRMLS_DC) { - zend_error(E_ERROR, "Cannot use object as array"); + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval; + + if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { + zend_call_method_with_1_params(&object, ce, NULL, "offsetunset", &retval, offset); + zval_ptr_dtor(&retval); + } else { + zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); + } } diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 6b581138e3..8a6f78e645 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -1410,7 +1410,7 @@ ZEND_API int is_smaller_or_equal_function(zval *result, zval *op1, zval *op2 TSR } -ZEND_API zend_bool instanceof_function(zend_class_entry *instance_ce, zend_class_entry *ce TSRMLS_DC) +ZEND_API zend_bool instanceof_function_ex(zend_class_entry *instance_ce, zend_class_entry *ce, zend_bool interfaces_only TSRMLS_DC) { zend_uint i; @@ -1419,16 +1419,23 @@ ZEND_API zend_bool instanceof_function(zend_class_entry *instance_ce, zend_class return 1; } } - while (instance_ce) { - if (instance_ce == ce) { - return 1; + if (!interfaces_only) { + while (instance_ce) { + if (instance_ce == ce) { + return 1; + } + instance_ce = instance_ce->parent; } - instance_ce = instance_ce->parent; } return 0; } +ZEND_API zend_bool instanceof_function(zend_class_entry *instance_ce, zend_class_entry *ce TSRMLS_DC) +{ + return instanceof_function_ex(instance_ce, ce, 0 TSRMLS_CC); +} + #define LOWER_CASE 1 #define UPPER_CASE 2 #define NUMERIC 3 diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index bd2b39acdb..db0f956056 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -59,6 +59,7 @@ ZEND_API int is_not_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) ZEND_API int is_smaller_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); ZEND_API int is_smaller_or_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); +ZEND_API zend_bool instanceof_function_ex(zend_class_entry *instance_ce, zend_class_entry *ce, zend_bool interfaces_only TSRMLS_DC); ZEND_API zend_bool instanceof_function(zend_class_entry *instance_ce, zend_class_entry *ce TSRMLS_DC); static inline zend_bool is_numeric_string(char *str, int length, long *lval, double *dval, zend_bool allow_errors) diff --git a/tests/classes/array_access_001.phpt b/tests/classes/array_access_001.phpt new file mode 100644 index 0000000000..08b741e1c8 --- /dev/null +++ b/tests/classes/array_access_001.phpt @@ -0,0 +1,198 @@ +--TEST-- +ZE2 ArrayAccess +--SKIPIF-- +<?php + if (!class_exists('ArrayAccess')) die('skip ArrayAccess not present'); +?> +--FILE-- +<?php +class Object implements ArrayAccess { + + public $a = array('1st', 1, 2=>'3rd', '4th'=>4); + + function offsetExists($index) { + echo __METHOD__ . "($index)\n"; + return array_key_exists($index, $this->a); + } + function offsetGet($index) { + echo __METHOD__ . "($index)\n"; + return $this->a[$index]; + } + function offsetSet($index, $newval) { + echo __METHOD__ . "($index,$newval)\n"; + return $this->a[$index] = $newval; + } + function offsetUnset($index) { + echo __METHOD__ . "($index)\n"; + unset($this->a[$index]); + } +} + +$obj = new Object; + +var_dump($obj->a); + +echo "===EMPTY===\n"; +var_dump(empty($obj[0])); +var_dump(empty($obj[1])); +var_dump(empty($obj[2])); +var_dump(empty($obj['4th'])); +var_dump(empty($obj['5th'])); +var_dump(empty($obj[6])); + +echo "===isset===\n"; +var_dump(isset($obj[0])); +var_dump(isset($obj[1])); +var_dump(isset($obj[2])); +var_dump(isset($obj['4th'])); +var_dump(isset($obj['5th'])); +var_dump(isset($obj[6])); + +echo "===offsetGet===\n"; +var_dump($obj[0]); +var_dump($obj[1]); +var_dump($obj[2]); +var_dump($obj['4th']); +var_dump($obj['5th']); +var_dump($obj[6]); + +echo "===offsetSet===\n"; +echo "WRITE 1\n"; +$obj[1] = 'Changed 1'; +var_dump($obj[1]); +echo "WRITE 2\n"; +$obj['4th'] = 'Changed 4th'; +var_dump($obj['4th']); +echo "WRITE 3\n"; +$obj['5th'] = 'Added 5th'; +var_dump($obj['5th']); +echo "WRITE 4\n"; +$obj[6] = 'Added 6'; +var_dump($obj[6]); + +var_dump($obj[0]); +var_dump($obj[2]); + +$x = $obj[6] = 'changed 6'; +var_dump($obj[6]); +var_dump($x); + +echo "===unset===\n"; +var_dump($obj->a); +unset($obj[2]); +unset($obj['4th']); +unset($obj[7]); +unset($obj['8th']); +var_dump($obj->a); + +?> +===DONE=== +--EXPECTF-- +array(4) { + [0]=> + string(3) "1st" + [1]=> + int(1) + [2]=> + string(3) "3rd" + ["4th"]=> + int(4) +} +===EMPTY=== +object::offsetExists(0) +bool(false) +object::offsetExists(1) +bool(false) +object::offsetExists(2) +bool(false) +object::offsetExists(4th) +bool(false) +object::offsetExists(5th) +bool(true) +object::offsetExists(6) +bool(true) +===isset=== +object::offsetExists(0) +bool(true) +object::offsetExists(1) +bool(true) +object::offsetExists(2) +bool(true) +object::offsetExists(4th) +bool(true) +object::offsetExists(5th) +bool(false) +object::offsetExists(6) +bool(false) +===offsetGet=== +object::offsetGet(0) +string(3) "1st" +object::offsetGet(1) +int(1) +object::offsetGet(2) +string(3) "3rd" +object::offsetGet(4th) +int(4) +object::offsetGet(5th) + +Notice: Undefined index: 5th in %sarray_access_001.php on line %d +NULL +object::offsetGet(6) + +Notice: Undefined offset: 6 in %sarray_access_001.php on line %d +NULL +===offsetSet=== +WRITE 1 +object::offsetSet(1,Changed 1) +object::offsetGet(1) +string(9) "Changed 1" +WRITE 2 +object::offsetSet(4th,Changed 4th) +object::offsetGet(4th) +string(11) "Changed 4th" +WRITE 3 +object::offsetSet(5th,Added 5th) +object::offsetGet(5th) +string(9) "Added 5th" +WRITE 4 +object::offsetSet(6,Added 6) +object::offsetGet(6) +string(7) "Added 6" +object::offsetGet(0) +string(3) "1st" +object::offsetGet(2) +string(3) "3rd" +object::offsetSet(6,changed 6) +object::offsetGet(6) +string(9) "changed 6" +string(9) "changed 6" +===unset=== +array(6) { + [0]=> + string(3) "1st" + [1]=> + string(9) "Changed 1" + [2]=> + string(3) "3rd" + ["4th"]=> + string(11) "Changed 4th" + ["5th"]=> + string(9) "Added 5th" + [6]=> + string(9) "changed 6" +} +object::offsetUnset(2) +object::offsetUnset(4th) +object::offsetUnset(7) +object::offsetUnset(8th) +array(4) { + [0]=> + string(3) "1st" + [1]=> + string(9) "Changed 1" + ["5th"]=> + string(9) "Added 5th" + [6]=> + string(9) "changed 6" +} +===DONE=== diff --git a/tests/classes/array_access_002.phpt b/tests/classes/array_access_002.phpt new file mode 100644 index 0000000000..cb4416d7bd --- /dev/null +++ b/tests/classes/array_access_002.phpt @@ -0,0 +1,198 @@ +--TEST-- +ZE2 ArrayAccess::offsetSet without return +--SKIPIF-- +<?php + if (!class_exists('ArrayAccess')) die('skip ArrayAccess not present'); +?> +--FILE-- +<?php +class Object implements ArrayAccess { + + public $a = array('1st', 1, 2=>'3rd', '4th'=>4); + + function offsetExists($index) { + echo __METHOD__ . "($index)\n"; + return array_key_exists($index, $this->a); + } + function offsetGet($index) { + echo __METHOD__ . "($index)\n"; + return $this->a[$index]; + } + function offsetSet($index, $newval) { + echo __METHOD__ . "($index,$newval)\n"; + /*return*/ $this->a[$index] = $newval; + } + function offsetUnset($index) { + echo __METHOD__ . "($index)\n"; + unset($this->a[$index]); + } +} + +$obj = new Object; + +var_dump($obj->a); + +echo "===EMPTY===\n"; +var_dump(empty($obj[0])); +var_dump(empty($obj[1])); +var_dump(empty($obj[2])); +var_dump(empty($obj['4th'])); +var_dump(empty($obj['5th'])); +var_dump(empty($obj[6])); + +echo "===isset===\n"; +var_dump(isset($obj[0])); +var_dump(isset($obj[1])); +var_dump(isset($obj[2])); +var_dump(isset($obj['4th'])); +var_dump(isset($obj['5th'])); +var_dump(isset($obj[6])); + +echo "===offsetGet===\n"; +var_dump($obj[0]); +var_dump($obj[1]); +var_dump($obj[2]); +var_dump($obj['4th']); +var_dump($obj['5th']); +var_dump($obj[6]); + +echo "===offsetSet===\n"; +echo "WRITE 1\n"; +$obj[1] = 'Changed 1'; +var_dump($obj[1]); +echo "WRITE 2\n"; +$obj['4th'] = 'Changed 4th'; +var_dump($obj['4th']); +echo "WRITE 3\n"; +$obj['5th'] = 'Added 5th'; +var_dump($obj['5th']); +echo "WRITE 4\n"; +$obj[6] = 'Added 6'; +var_dump($obj[6]); + +var_dump($obj[0]); +var_dump($obj[2]); + +$x = $obj[6] = 'changed 6'; +var_dump($obj[6]); +var_dump($x); + +echo "===unset===\n"; +var_dump($obj->a); +unset($obj[2]); +unset($obj['4th']); +unset($obj[7]); +unset($obj['8th']); +var_dump($obj->a); + +?> +===DONE=== +--EXPECTF-- +array(4) { + [0]=> + string(3) "1st" + [1]=> + int(1) + [2]=> + string(3) "3rd" + ["4th"]=> + int(4) +} +===EMPTY=== +object::offsetExists(0) +bool(false) +object::offsetExists(1) +bool(false) +object::offsetExists(2) +bool(false) +object::offsetExists(4th) +bool(false) +object::offsetExists(5th) +bool(true) +object::offsetExists(6) +bool(true) +===isset=== +object::offsetExists(0) +bool(true) +object::offsetExists(1) +bool(true) +object::offsetExists(2) +bool(true) +object::offsetExists(4th) +bool(true) +object::offsetExists(5th) +bool(false) +object::offsetExists(6) +bool(false) +===offsetGet=== +object::offsetGet(0) +string(3) "1st" +object::offsetGet(1) +int(1) +object::offsetGet(2) +string(3) "3rd" +object::offsetGet(4th) +int(4) +object::offsetGet(5th) + +Notice: Undefined index: 5th in %sarray_access_002.php on line %d +NULL +object::offsetGet(6) + +Notice: Undefined offset: 6 in %sarray_access_002.php on line %d +NULL +===offsetSet=== +WRITE 1 +object::offsetSet(1,Changed 1) +object::offsetGet(1) +string(9) "Changed 1" +WRITE 2 +object::offsetSet(4th,Changed 4th) +object::offsetGet(4th) +string(11) "Changed 4th" +WRITE 3 +object::offsetSet(5th,Added 5th) +object::offsetGet(5th) +string(9) "Added 5th" +WRITE 4 +object::offsetSet(6,Added 6) +object::offsetGet(6) +string(7) "Added 6" +object::offsetGet(0) +string(3) "1st" +object::offsetGet(2) +string(3) "3rd" +object::offsetSet(6,changed 6) +object::offsetGet(6) +string(9) "changed 6" +string(9) "changed 6" +===unset=== +array(6) { + [0]=> + string(3) "1st" + [1]=> + string(9) "Changed 1" + [2]=> + string(3) "3rd" + ["4th"]=> + string(11) "Changed 4th" + ["5th"]=> + string(9) "Added 5th" + [6]=> + string(9) "changed 6" +} +object::offsetUnset(2) +object::offsetUnset(4th) +object::offsetUnset(7) +object::offsetUnset(8th) +array(4) { + [0]=> + string(3) "1st" + [1]=> + string(9) "Changed 1" + ["5th"]=> + string(9) "Added 5th" + [6]=> + string(9) "changed 6" +} +===DONE=== diff --git a/tests/classes/array_access_003.phpt b/tests/classes/array_access_003.phpt new file mode 100644 index 0000000000..f19e23d818 --- /dev/null +++ b/tests/classes/array_access_003.phpt @@ -0,0 +1,59 @@ +--TEST-- +ZE2 ArrayAccess::offsetGet ambiguties +--SKIPIF-- +<?php + if (!class_exists('ArrayAccess')) die('skip ArrayAccess not present'); +?> +--FILE-- +<?php +class Object implements ArrayAccess { + + public $a = array('1st', 1, 2=>'3rd', '4th'=>4); + + function offsetExists($index) { + echo __METHOD__ . "($index)\n"; + return array_key_exists($index, $this->a); + } + function &offsetGet($index) { + echo __METHOD__ . "($index)\n"; + switch($index) { + case 1: + $a = 'foo'; + return $a . 'Bar'; + case 2: + static $a=1; + return $a; + } + return $this->a[$index]; + } + function offsetSet($index, $newval) { + echo __METHOD__ . "($index,$newval)\n"; + if ($index==3) { + $this->cnt = $newval; + } + return $this->a[$index] = $newval; + } + function offsetUnset($index) { + echo __METHOD__ . "($index)\n"; + unset($this->a[$index]); + } +} + +$obj = new Object; + +var_dump($obj[1]); +var_dump($obj[2]); +$obj[2]++; +var_dump($obj[2]); + +?> +===DONE=== +--EXPECTF-- +object::offsetGet(1) +string(6) "fooBar" +object::offsetGet(2) +int(1) +object::offsetGet(2) +object::offsetGet(2) +int(2) +===DONE=== diff --git a/tests/classes/array_access_004.phpt b/tests/classes/array_access_004.phpt new file mode 100644 index 0000000000..8032d28642 --- /dev/null +++ b/tests/classes/array_access_004.phpt @@ -0,0 +1,58 @@ +--TEST-- +ZE2 ArrayAccess::offsetGet ambiguties +--SKIPIF-- +<?php + if (!class_exists('ArrayAccess')) die('skip ArrayAccess not present'); +?> +--FILE-- +<?php +class Object implements ArrayAccess { + + public $a = array('1st', 1, 2=>'3rd', '4th'=>4); + + function offsetExists($index) { + echo __METHOD__ . "($index)\n"; + return array_key_exists($index, $this->a); + } + function offsetGet($index) { + echo __METHOD__ . "($index)\n"; + switch($index) { + case 1: + $a = 'foo'; + return $a . 'Bar'; + case 2: + static $a=1; + return $a; + } + return $this->a[$index]; + } + function offsetSet($index, $newval) { + echo __METHOD__ . "($index,$newval)\n"; + if ($index==3) { + $this->cnt = $newval; + } + return $this->a[$index] = $newval; + } + function offsetUnset($index) { + echo __METHOD__ . "($index)\n"; + unset($this->a[$index]); + } +} + +$obj = new Object; + +var_dump($obj[1]); +var_dump($obj[2]); +$obj[2]++; +var_dump($obj[2]); + +?> +===DONE=== +--EXPECTF-- +object::offsetGet(1) +string(6) "fooBar" +object::offsetGet(2) +int(1) +object::offsetGet(2) + +Fatal error: Objects used as arrays in post/pre increment/decrement must return values by reference in %sarray_access_004.php on line %d |
