diff --git a/Zend/tests/bug31683.phpt b/Zend/tests/bug31683.phpt index be2dfc807cc28..32ca3d2fba725 100644 --- a/Zend/tests/bug31683.phpt +++ b/Zend/tests/bug31683.phpt @@ -77,6 +77,7 @@ string(2) "ok" string(2) "ok" string(2) "ok" string(2) "ok" +string(2) "ok" NULL string(2) "ok" string(2) "ok" @@ -90,6 +91,7 @@ string(2) "ok" string(2) "ok" string(2) "ok" string(2) "ok" +string(2) "ok" NULL string(2) "ok" string(2) "ok" diff --git a/Zend/tests/bug63217.phpt b/Zend/tests/bug63217.phpt index 9657b216429ae..1470af4221c93 100644 --- a/Zend/tests/bug63217.phpt +++ b/Zend/tests/bug63217.phpt @@ -91,7 +91,9 @@ var_dump($ao); ?> --EXPECT-- offsetExists given string(1) "0" +offsetGet given string(1) "0" offsetExists given string(3) "123" +offsetGet given string(3) "123" offsetUnset given string(1) "0" offsetUnset given string(3) "123" offsetSet given string(1) "0" @@ -99,7 +101,9 @@ offsetSet given string(3) "123" offsetGet given string(1) "0" offsetGet given string(3) "123" offsetExists given string(1) "0" +offsetGet given string(1) "0" offsetExists given string(3) "123" +offsetGet given string(3) "123" offsetUnset given string(1) "0" offsetUnset given string(3) "123" offsetSet given string(1) "0" diff --git a/Zend/tests/bug69955.phpt b/Zend/tests/bug69955.phpt index b066b25f62d62..41c7130853038 100644 --- a/Zend/tests/bug69955.phpt +++ b/Zend/tests/bug69955.phpt @@ -26,10 +26,12 @@ $c10 = new C10; var_dump($c10[] += 5); ?> ---EXPECT-- +--EXPECTF-- Inside C10::offsetGet NULL +Notice: Indirect modification of overloaded element of C10 has no effect in %s on line %d + Inside C10::offsetSet NULL int(105) diff --git a/Zend/tests/bug75420.10.phpt b/Zend/tests/bug75420.10.phpt index e577279c9544e..b6243e4951a0f 100644 --- a/Zend/tests/bug75420.10.phpt +++ b/Zend/tests/bug75420.10.phpt @@ -16,6 +16,6 @@ var_dump($obj[$name] ?? 12); var_dump($name); ?> --EXPECT-- -string(6) "foofoo" +int(24) int(42) int(24) diff --git a/Zend/tests/bug75420.11.phpt b/Zend/tests/bug75420.11.phpt index 861a151b6d546..87b66b26f4ce1 100644 --- a/Zend/tests/bug75420.11.phpt +++ b/Zend/tests/bug75420.11.phpt @@ -15,6 +15,6 @@ var_dump(empty($obj[$name])); var_dump($name); ?> --EXPECT-- -string(3) "foo" +int(24) bool(false) int(24) diff --git a/Zend/tests/bug75420.12.phpt b/Zend/tests/bug75420.12.phpt index 38a457fe78c83..836bed58af1b1 100644 --- a/Zend/tests/bug75420.12.phpt +++ b/Zend/tests/bug75420.12.phpt @@ -16,6 +16,6 @@ var_dump(empty($obj[$name])); var_dump($name); ?> --EXPECT-- -string(6) "foofoo" +int(24) bool(false) int(24) diff --git a/Zend/tests/bug75420.17.phpt b/Zend/tests/bug75420.17.phpt new file mode 100644 index 0000000000000..ee18711a31fa2 --- /dev/null +++ b/Zend/tests/bug75420.17.phpt @@ -0,0 +1,20 @@ +--TEST-- +Bug #75420.17 (Indirect modification of magic method argument) +--FILE-- + +--EXPECT-- +string(3) "foo" +bool(true) +int(24) diff --git a/Zend/tests/bug75420.9.phpt b/Zend/tests/bug75420.9.phpt index 50eebc4703105..4ac85f24fb4d5 100644 --- a/Zend/tests/bug75420.9.phpt +++ b/Zend/tests/bug75420.9.phpt @@ -15,6 +15,6 @@ var_dump($obj[$name] ?? 12); var_dump($name); ?> --EXPECT-- -string(3) "foo" +int(24) int(42) int(24) diff --git a/Zend/tests/new_without_parentheses/new_with_ctor_arguments_parentheses.phpt b/Zend/tests/new_without_parentheses/new_with_ctor_arguments_parentheses.phpt index 10f35957e00ed..2c07edaa9a973 100644 --- a/Zend/tests/new_without_parentheses/new_with_ctor_arguments_parentheses.phpt +++ b/Zend/tests/new_without_parentheses/new_with_ctor_arguments_parentheses.phpt @@ -107,5 +107,8 @@ offsetGet offsetGet offsetGet offsetExists +offsetGet offsetExists +offsetGet offsetExists +offsetGet diff --git a/Zend/tests/offsets/ArrayAccess_container_offset_behaviour.phpt b/Zend/tests/offsets/ArrayAccess_container_offset_behaviour.phpt index b45b373c7b640..dc3ea812e0517 100644 --- a/Zend/tests/offsets/ArrayAccess_container_offset_behaviour.phpt +++ b/Zend/tests/offsets/ArrayAccess_container_offset_behaviour.phpt @@ -27,6 +27,8 @@ int(25) isset(): string(15) "CLASS_NAME::offsetExists" VAR_DUMP_OF_OFFSET +string(12) "CLASS_NAME::offsetGet" +VAR_DUMP_OF_OFFSET bool(true) empty(): string(15) "CLASS_NAME::offsetExists" @@ -100,7 +102,7 @@ Cannot unset offset in a non-array variable OUTPUT; ob_start(); -foreach (['A', 'B'] as $class) { +foreach (['A'] as $class) { foreach ($offsets as $dimension) { $container = new $class(); $error = "(new $class())[" . zend_test_var_export($dimension) . '] has different outputs' . "\n"; diff --git a/Zend/tests/offsets/ArrayObject_container_offset_behaviour.phpt b/Zend/tests/offsets/ArrayObject_container_offset_behaviour.phpt index 7b3efbbb02de4..1e0053d81297d 100644 --- a/Zend/tests/offsets/ArrayObject_container_offset_behaviour.phpt +++ b/Zend/tests/offsets/ArrayObject_container_offset_behaviour.phpt @@ -41,6 +41,7 @@ bool(false) Nested null coalesce: int(30) Nested unset(): + OUTPUT; $EXPECTED_OUTPUT_VALID_OFFSETS_REGEX = '/^' . expectf_to_regex(EXPECTED_OUTPUT_VALID_OFFSETS) . '$/s'; @@ -58,9 +59,11 @@ Deprecated: Implicit conversion from float %F to int loses precision in %s on li Read: Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d -int(15) +int(5) Read-Write: +Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d + Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d isset(): @@ -68,17 +71,21 @@ Deprecated: Implicit conversion from float %F to int loses precision in %s on li bool(true) empty(): +Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d + Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d bool(false) null coalesce: Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d -int(35) + +Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d +int(25) Reference to dimension: Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d Value of reference: -int(35) +int(25) Value of container dimension after write to reference (should be int(100) if successful): Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d @@ -90,9 +97,9 @@ Nested read: Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d -Warning: Undefined array key 0 in %s on line %d +Warning: Undefined array key %s in %s on line %d -Warning: Trying to access array offset on null in %s on line %d +Warning: Trying to access array offset on null in %s on line 74 NULL Nested write: @@ -108,12 +115,16 @@ Nested isset(): Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d +Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d + Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d bool(true) Nested empty(): Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d +Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d + Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d bool(false) Nested null coalesce: @@ -121,7 +132,9 @@ Nested null coalesce: Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d -int(25) + +Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d +int(30) Nested unset(): Deprecated: Implicit conversion from float %F to int loses precision in %s on line %d @@ -164,57 +177,12 @@ Cannot access offset of type %s in isset or empty Nested null coalesce: Cannot access offset of type %s in isset or empty Nested unset(): - -Notice: Indirect modification of overloaded element of ArrayObject has no effect in %s on line %d -Cannot unset offset of type %s on ArrayObject +Cannot access offset of type %s on ArrayObject OUTPUT; $EXPECTED_OUTPUT_INVALID_OFFSETS_REGEX = '/^' . expectf_to_regex(EXPECTED_OUTPUT_INVALID_OFFSETS) . '$/s'; -const EXPECTED_OUTPUT_NULL_OFFSET = << +--EXPECT-- +Executed tests diff --git a/Zend/tests/offsets/DimensionFetchAppendable_container_offset_behaviour.phpt b/Zend/tests/offsets/DimensionFetchAppendable_container_offset_behaviour.phpt new file mode 100644 index 0000000000000..8a237851199dc --- /dev/null +++ b/Zend/tests/offsets/DimensionFetchAppendable_container_offset_behaviour.phpt @@ -0,0 +1,85 @@ +--TEST-- +\DimensionFetchAppendable containers behaviour with offsets +--FILE-- + +--EXPECT-- +Executed tests diff --git a/Zend/tests/offsets/DimensionFetchable_container_offset_behaviour.phpt b/Zend/tests/offsets/DimensionFetchable_container_offset_behaviour.phpt new file mode 100644 index 0000000000000..c42cfd236f09f --- /dev/null +++ b/Zend/tests/offsets/DimensionFetchable_container_offset_behaviour.phpt @@ -0,0 +1,136 @@ +--TEST-- +DimensionFetchable containers behaviour with offsets +--FILE-- + +--EXPECT-- +Executed tests diff --git a/Zend/tests/offsets/DimensionReadWrite_container_offset_behaviour.phpt b/Zend/tests/offsets/DimensionReadWrite_container_offset_behaviour.phpt new file mode 100644 index 0000000000000..2454244c2a1e0 --- /dev/null +++ b/Zend/tests/offsets/DimensionReadWrite_container_offset_behaviour.phpt @@ -0,0 +1,123 @@ +--TEST-- +\DimensionReadWriteable containers behaviour with offsets +--FILE-- + +--EXPECT-- +Executed tests diff --git a/Zend/tests/offsets/DimensionReadable_container_offset_behaviour.phpt b/Zend/tests/offsets/DimensionReadable_container_offset_behaviour.phpt new file mode 100644 index 0000000000000..408fb20131166 --- /dev/null +++ b/Zend/tests/offsets/DimensionReadable_container_offset_behaviour.phpt @@ -0,0 +1,117 @@ +--TEST-- +DimensionReadable containers behaviour with offsets +--FILE-- + +--EXPECT-- +Executed tests diff --git a/Zend/tests/offsets/DimensionUnsetable_container_offset_behaviour.phpt b/Zend/tests/offsets/DimensionUnsetable_container_offset_behaviour.phpt new file mode 100644 index 0000000000000..610023a705691 --- /dev/null +++ b/Zend/tests/offsets/DimensionUnsetable_container_offset_behaviour.phpt @@ -0,0 +1,86 @@ +--TEST-- +\DimensionUnsetable containers behaviour with offsets +--FILE-- + +--EXPECT-- +Executed tests diff --git a/Zend/tests/offsets/DimensionWriteable_container_offset_behaviour.phpt b/Zend/tests/offsets/DimensionWriteable_container_offset_behaviour.phpt new file mode 100644 index 0000000000000..b47b694f8d980 --- /dev/null +++ b/Zend/tests/offsets/DimensionWriteable_container_offset_behaviour.phpt @@ -0,0 +1,87 @@ +--TEST-- +DimensionWriteable containers behaviour with offsets +--FILE-- + +--EXPECT-- +Executed tests diff --git a/Zend/tests/offsets/appending_containers.phpt b/Zend/tests/offsets/appending_containers.phpt index 0372c28bf1841..b4c2b5c0f6666 100644 --- a/Zend/tests/offsets/appending_containers.phpt +++ b/Zend/tests/offsets/appending_containers.phpt @@ -65,8 +65,7 @@ string(5) "value" object(A)#3 (0) { } new B() container: -string(12) "B::offsetSet" -NULL +string(9) "B::append" string(5) "value" object(B)#4 (1) { ["storage":"ArrayObject":private]=> diff --git a/Zend/tests/offsets/appending_containers_in_fetch.phpt b/Zend/tests/offsets/appending_containers_in_fetch.phpt index e503b563fbce9..e3cf028a9e3cd 100644 --- a/Zend/tests/offsets/appending_containers_in_fetch.phpt +++ b/Zend/tests/offsets/appending_containers_in_fetch.phpt @@ -60,11 +60,14 @@ Error: Cannot use a scalar value as an array new stdClass() container: Error: Cannot use object of type stdClass as array new ArrayObject() container: - -Notice: Indirect modification of overloaded element of ArrayObject has no effect in %s on line %d object(ArrayObject)#2 (1) { ["storage":"ArrayObject":private]=> - array(0) { + array(1) { + [0]=> + array(1) { + [5]=> + string(5) "value" + } } } new A() container: @@ -74,6 +77,13 @@ NULL Notice: Indirect modification of overloaded element of A has no effect in %s on line %d Error: Cannot use a scalar value as an array new B() container: - -Notice: Indirect modification of overloaded element of B has no effect in %s on line %d -ArgumentCountError: B::offsetGet(): Argument #1 ($offset) not passed +object(B)#4 (1) { + ["storage":"ArrayObject":private]=> + array(1) { + [0]=> + array(1) { + [5]=> + string(5) "value" + } + } +} diff --git a/Zend/tests/offsets/appending_fetch_RW_containers.phpt b/Zend/tests/offsets/appending_fetch_RW_containers.phpt new file mode 100644 index 0000000000000..3f4ff95f6190d --- /dev/null +++ b/Zend/tests/offsets/appending_fetch_RW_containers.phpt @@ -0,0 +1,78 @@ +--TEST-- +Appending containers via a binary RW op $c[] .= $v; +--FILE-- +getMessage(), "\n"; + } +} + +?> +--EXPECTF-- +NULL container: +array(1) { + [0]=> + string(5) "value" +} +false container: + +Deprecated: Automatic conversion of false to array is deprecated in %s on line %d +array(1) { + [0]=> + string(5) "value" +} +true container: +Error: Cannot use a scalar value as an array +4 container: +Error: Cannot use a scalar value as an array +5.5 container: +Error: Cannot use a scalar value as an array +'10' container: +Error: [] operator not supported for strings +'25.5' container: +Error: [] operator not supported for strings +'string' container: +Error: [] operator not supported for strings +[] container: +array(1) { + [0]=> + string(5) "value" +} +STDERR container: +Error: Cannot use a scalar value as an array +new stdClass() container: +Error: Cannot use object of type stdClass as array +new ArrayObject() container: +object(ArrayObject)#2 (1) { + ["storage":"ArrayObject":private]=> + array(1) { + [0]=> + string(5) "value" + } +} +new A() container: +string(12) "A::offsetGet" +NULL + +Notice: Indirect modification of overloaded element of A has no effect in %s on line %d +string(12) "A::offsetSet" +NULL +string(6) "5value" +object(A)#3 (0) { +} +new B() container: +object(B)#4 (1) { + ["storage":"ArrayObject":private]=> + array(1) { + [0]=> + string(5) "value" + } +} diff --git a/Zend/tests/offsets/appending_fetch_RW_containers_with_objects.phpt b/Zend/tests/offsets/appending_fetch_RW_containers_with_objects.phpt new file mode 100644 index 0000000000000..74826fd0f6c63 --- /dev/null +++ b/Zend/tests/offsets/appending_fetch_RW_containers_with_objects.phpt @@ -0,0 +1,78 @@ +--TEST-- +Appending containers via a binary RW op $c[] .= $v; fetch is an object +--EXTENSIONS-- +gmp +--FILE-- +str = new FakeString(); + return $this->str; + } +} + +$container = new TestFetchAppendStringableObject(); +try { + $container[] .= 'value'; + var_dump($container); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +class TestFetchAppendStringableObjectTypedProperty implements FetchAppendable { + public object $str; + public function append(mixed $v): void {} + public function &fetchAppend(): object { + $this->str = new FakeString(); + return $this->str; + } +} + +$container = new TestFetchAppendStringableObjectTypedProperty(); +try { + $container[] .= 'value'; + var_dump($container); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +class TestFetchAppendObject implements FetchAppendable { + public object $n; + public function append(mixed $v): void {} + public function &fetchAppend(): object { + $this->n = gmp_init(20); + return $this->n; + } +} + +$container = new TestFetchAppendObject(); +try { + $container[] += 22; + var_dump($container); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +object(TestFetchAppendStringableObject)#1 (1) { + ["str"]=> + string(11) "Hello value" +} +TypeError: Cannot assign string to reference held by property TestFetchAppendStringableObjectTypedProperty::$str of type object +object(TestFetchAppendObject)#4 (1) { + ["n"]=> + object(GMP)#1 (1) { + ["num"]=> + string(2) "42" + } +} diff --git a/Zend/tests/offsets/internal_handlers.phpt b/Zend/tests/offsets/internal_handlers.phpt index a75e706ab8af1..ce8cfb3bfcbac 100644 --- a/Zend/tests/offsets/internal_handlers.phpt +++ b/Zend/tests/offsets/internal_handlers.phpt @@ -221,76 +221,48 @@ exportObject($o); ?> --EXPECTF-- read op -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_R, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' write op DimensionHandlersNoArrayAccess, read: false, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' read-write op -DimensionHandlersNoArrayAccess, read: true, write: true, has: false, unset: false, readType: BP_VAR_R, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' isset op -DimensionHandlersNoArrayAccess, read: false, write: false, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: 0, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: false, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' empty op -DimensionHandlersNoArrayAccess, read: false, write: false, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: 1, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: false, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' null coalescing op -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_IS, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: false, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' appending op -DimensionHandlersNoArrayAccess, read: false, write: true, has: false, unset: false, readType: uninitialized, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized +DimensionHandlersNoArrayAccess, read: false, write: false, has: false, unset: false, readType: uninitialized, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized unset op DimensionHandlersNoArrayAccess, read: false, write: false, has: false, unset: true, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' nested read Warning: Trying to access array offset on true in %s on line %d -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_R, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' nested write - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -Error: Cannot use a scalar value as an array -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_W, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: false, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar' nested write: appending then write - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -Error: Cannot use a scalar value as an array -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_W, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized +DimensionHandlersNoArrayAccess, read: false, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar' nested read-write - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -Error: Cannot use a scalar value as an array -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_RW, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar' nested read-write: appending then write - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -Error: Cannot use a scalar value as an array -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_RW, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized +DimensionHandlersNoArrayAccess, read: true, write: true, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar' nested isset -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_IS, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: false, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' nested empty -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_IS, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: false, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' nested null coalescing -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_IS, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: true, write: false, has: true, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' nested appending - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -Error: Cannot use a scalar value as an array -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_W, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: false, write: false, has: false, unset: false, readType: uninitialized, hasOffset: false, checkEmpty: uninitialized, offset: 'foo' nested appending: appending then append - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -Error: Cannot use a scalar value as an array -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_W, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized +DimensionHandlersNoArrayAccess, read: false, write: false, has: false, unset: false, readType: uninitialized, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized nested unset - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -Error: Cannot unset offset in a non-array variable -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_UNSET, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: false, write: false, has: false, unset: true, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar' reference fetching - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_W, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: false, write: false, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' nested reference fetching - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -Error: Cannot use a scalar value as an array -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_W, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +DimensionHandlersNoArrayAccess, read: false, write: false, has: false, unset: false, readType: uninitialized, hasOffset: true, checkEmpty: uninitialized, offset: 'bar' reference fetch-append - -Notice: Indirect modification of overloaded element of DimensionHandlersNoArrayAccess has no effect in %s on line %d -DimensionHandlersNoArrayAccess, read: true, write: false, has: false, unset: false, readType: BP_VAR_W, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized +DimensionHandlersNoArrayAccess, read: false, write: false, has: false, unset: false, readType: uninitialized, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized diff --git a/Zend/tests/offsets/internal_handlers_extended.phpt b/Zend/tests/offsets/internal_handlers_extended.phpt index 4877c2fc22a6f..4ac1c22d50552 100644 --- a/Zend/tests/offsets/internal_handlers_extended.phpt +++ b/Zend/tests/offsets/internal_handlers_extended.phpt @@ -34,13 +34,24 @@ class DoImplement extends DimensionHandlersNoArrayAccess implements ArrayAccess $no = new NoImplement(); $do = new DoImplement(); -$no['foo']; +try { + $no['foo']; +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} exportObject($no); -$do['foo']; +try { + $do['foo']; +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} exportObject($do); ?> --EXPECT-- -NoImplement, read: true, write: false, has: false, unset: false, readType: BP_VAR_R, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' -DoImplement, read: true, write: false, has: false, unset: false, readType: BP_VAR_R, hasOffset: true, checkEmpty: uninitialized, offset: 'foo' +Error: Cannot use object of type NoImplement as array +NoImplement, read: false, write: false, has: false, unset: false, readType: uninitialized, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized +string(22) "DoImplement::offsetGet" +string(3) "foo" +DoImplement, read: false, write: false, has: false, unset: false, readType: uninitialized, hasOffset: false, checkEmpty: uninitialized, offset: uninitialized diff --git a/Zend/tests/offsets/reference_containers_appended.phpt b/Zend/tests/offsets/reference_containers_appended.phpt index 982e9c61dbcd0..d14e03ec7cf34 100644 --- a/Zend/tests/offsets/reference_containers_appended.phpt +++ b/Zend/tests/offsets/reference_containers_appended.phpt @@ -54,11 +54,11 @@ Error: Cannot use a scalar value as an array new stdClass() container: Error: Cannot use object of type stdClass as array new ArrayObject() container: - -Notice: Indirect modification of overloaded element of ArrayObject has no effect in %s on line %d object(ArrayObject)#2 (1) { ["storage":"ArrayObject":private]=> - array(0) { + array(1) { + [0]=> + &NULL } } NULL @@ -71,6 +71,11 @@ object(A)#3 (0) { } int(5) new B() container: - -Notice: Indirect modification of overloaded element of B has no effect in %s on line %d -ArgumentCountError: B::offsetGet(): Argument #1 ($offset) not passed +object(B)#4 (1) { + ["storage":"ArrayObject":private]=> + array(1) { + [0]=> + &NULL + } +} +NULL diff --git a/Zend/tests/offsets/test_offset_helpers.inc b/Zend/tests/offsets/test_offset_helpers.inc index 8d4ccadef56ac..f34a7d0d0e1ec 100644 --- a/Zend/tests/offsets/test_offset_helpers.inc +++ b/Zend/tests/offsets/test_offset_helpers.inc @@ -139,6 +139,76 @@ class B extends ArrayObject { } } +class DimensionRead implements DimensionReadable { + public function offsetGet(mixed $offset): mixed { + var_dump(__METHOD__); + var_dump($offset); + return 5; + } + + public function offsetExists(mixed $offset): bool { + var_dump(__METHOD__); + var_dump($offset); + return true; + } +} + +class DimensionFetch extends DimensionRead implements DimensionFetchable { + public function &offsetFetch(mixed $offset): mixed { + var_dump(__METHOD__); + var_dump($offset); + return $this; + } +} + +class DimensionWrite implements DimensionWritable { + public function offsetSet(mixed $offset, mixed $value): void { + var_dump(__METHOD__); + var_dump($offset); + var_dump($value); + } +} + +class DimensionReadWrite implements DimensionReadable, DimensionWritable { + public function offsetGet(mixed $offset): mixed { + var_dump(__METHOD__); + var_dump($offset); + return 5; + } + + public function offsetExists(mixed $offset): bool { + var_dump(__METHOD__); + var_dump($offset); + return true; + } + public function offsetSet(mixed $offset, mixed $value): void { + var_dump(__METHOD__); + var_dump($offset); + var_dump($value); + } +} + +class DimensionAppend implements Appendable { + public function append(mixed $value): void { + var_dump(__METHOD__); + var_dump($value); + } +} + +class DimensionFetchAppend extends DimensionAppend implements FetchAppendable { + public function &fetchAppend(): mixed { + var_dump(__METHOD__); + return $this; + } +} + +class DimensionUnset implements DimensionUnsetable { + public function offsetUnset($offset): void { + var_dump(__METHOD__); + var_dump($offset); + } +} + $containers = [ null, false, @@ -156,6 +226,16 @@ $containers = [ new B(), ]; +$newApiContainers = [ + new DimensionRead(), + new DimensionFetch(), + new DimensionWrite(), + new DimensionReadWrite(), + new DimensionAppend(), + new DimensionFetchAppend(), + new DimensionUnset(), +]; + $offsets = [ null, false, diff --git a/Zend/tests/weakrefs/weakmap_basic_map_behavior.phpt b/Zend/tests/weakrefs/weakmap_basic_map_behavior.phpt index dd3c84518eac9..5e81e46c54345 100644 --- a/Zend/tests/weakrefs/weakmap_basic_map_behavior.phpt +++ b/Zend/tests/weakrefs/weakmap_basic_map_behavior.phpt @@ -63,6 +63,8 @@ var_dump($map->offsetExists($obj2)); var_dump($map->count()); var_dump($map->offsetUnset($obj2)); var_dump($map->count()); +// TODO +//var_dump($map->offsetFetch($obj2)); ?> --EXPECT-- @@ -150,7 +152,7 @@ object(WeakMap)#1 (2) { int(1) } ["value"]=> - array(1) { + &array(1) { [0]=> int(42) } @@ -163,7 +165,7 @@ object(WeakMap)#1 (2) { int(2) } ["value"]=> - int(42) + &int(42) } } diff --git a/Zend/tests/weakrefs/weakmap_error_conditions.phpt b/Zend/tests/weakrefs/weakmap_error_conditions.phpt index 2516c0443fd14..a9f0bf3f930d8 100644 --- a/Zend/tests/weakrefs/weakmap_error_conditions.phpt +++ b/Zend/tests/weakrefs/weakmap_error_conditions.phpt @@ -78,8 +78,8 @@ WeakMap key must be an object WeakMap key must be an object WeakMap key must be an object WeakMap key must be an object -Cannot append to WeakMap -Cannot append to WeakMap +Cannot append to object of type WeakMap +Cannot fetch append object of type WeakMap Object stdClass#2 not contained in WeakMap Warning: Undefined property: WeakMap::$prop in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index dd805e0e7638f..d568696f2028a 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -35,6 +35,7 @@ #include "zend_gc.h" #include "zend_variables.h" #include "zend_iterators.h" +#include "zend_dimension_handlers.h" #include "zend_stream.h" #include "zend_smart_str_public.h" #include "zend_smart_string_public.h" @@ -187,8 +188,6 @@ struct _zend_class_entry { /* allocated only if class implements Iterator or IteratorAggregate interface */ zend_class_iterator_funcs *iterator_funcs_ptr; - /* allocated only if class implements ArrayAccess interface */ - zend_class_arrayaccess_funcs *arrayaccess_funcs_ptr; /* handlers */ union { @@ -202,6 +201,9 @@ struct _zend_class_entry { int (*serialize)(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data); int (*unserialize)(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data); + /* dimension handler callbacks */ + zend_class_dimensions_functions *dimension_handlers; + uint32_t num_interfaces; uint32_t num_traits; diff --git a/Zend/zend_API.h b/Zend/zend_API.h index ab67dd5717e69..937ec2e12b677 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -316,7 +316,7 @@ typedef struct _zend_fcall_info_cache { class_container.interfaces = NULL; \ class_container.get_iterator = NULL; \ class_container.iterator_funcs_ptr = NULL; \ - class_container.arrayaccess_funcs_ptr = NULL; \ + class_container.dimension_handlers = NULL; \ class_container.info.internal.module = NULL; \ class_container.info.internal.builtin_functions = functions; \ } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 79a65d2d5aded..fc9a1f517f0af 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2040,7 +2040,6 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->create_object = NULL; ce->get_iterator = NULL; ce->iterator_funcs_ptr = NULL; - ce->arrayaccess_funcs_ptr = NULL; ce->get_static_method = NULL; ce->parent = NULL; ce->parent_name = NULL; @@ -2052,6 +2051,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->trait_precedences = NULL; ce->serialize = NULL; ce->unserialize = NULL; + ce->dimension_handlers = NULL; if (ce->type == ZEND_INTERNAL_CLASS) { ce->info.internal.module = NULL; ce->info.internal.builtin_functions = NULL; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 6a5626492ec73..4308a3e584926 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1000,6 +1000,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define BP_VAR_IS 3 #define BP_VAR_FUNC_ARG 4 #define BP_VAR_UNSET 5 +#define BP_VAR_FETCH 6 #define ZEND_INTERNAL_FUNCTION 1 #define ZEND_USER_FUNCTION 2 diff --git a/Zend/zend_dimension_handlers.h b/Zend/zend_dimension_handlers.h new file mode 100644 index 0000000000000..66ab0451573e7 --- /dev/null +++ b/Zend/zend_dimension_handlers.h @@ -0,0 +1,39 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Gina Peter Banyard | + +----------------------------------------------------------------------+ +*/ + +/* This really should not be needed... */ +#ifndef ZEND_DIMENSION_HANDLERS_H +#define ZEND_DIMENSION_HANDLERS_H + +//BEGIN_EXTERN_C() + +#include "zend_types.h" + +typedef struct _zend_class_dimensions_functions { + /* rv is a slot provided by the callee that is returned */ + zval *(*/* const */ read_dimension)(zend_object *object, zval *offset, zval *rv); + bool (*/* const */ has_dimension)(zend_object *object, zval *offset); + zval *(*/* const */ fetch_dimension)(zend_object *object, zval *offset, zval *rv); + void (*/* const */ write_dimension)(zend_object *object, zval *offset, zval *value); + /* rv is a slot provided by the callee that is returned */ + void (*/* const */ append)(zend_object *object, zval *value); + zval *(*/* const */ fetch_append)(zend_object *object, zval *rv); + void (*/* const */ unset_dimension)(zend_object *object, zval *offset); +} zend_class_dimensions_functions; + +#endif /* ZEND_DIMENSION_HANDLERS_H */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index b92c4c1174cdc..01182b1590de9 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -33,6 +33,7 @@ #include "zend_ini.h" #include "zend_exceptions.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "zend_closures.h" #include "zend_generators.h" #include "zend_vm.h" @@ -1503,6 +1504,83 @@ ZEND_API bool zend_never_inline zend_verify_class_constant_type(zend_class_const return 1; } +#ifdef ZEND_DEBUG +static zend_never_inline ZEND_COLD bool zend_check_dimension_interfaces_implemented(const zend_object *object, bool has_offset, int type) +{ + /* ext/ffi CData class is currently weird */ + if (zend_string_equals_literal_ci(object->ce->name, "FFI\\CData")) { + return true; + } + /* ext/com com class is currently weird */ + if (zend_string_equals_literal_ci(object->ce->name, "com")) { + return true; + } + /* ext/zend_test class is used for debugging and checking behaviour */ + if (zend_string_equals_literal_ci(object->ce->name, "DimensionHandlersNoArrayAccess")) { + return true; + } + if (instanceof_function(object->ce, zend_ce_arrayaccess)) { + return true; + } + switch (type) { + case BP_VAR_R: + case BP_VAR_IS: + return instanceof_function(object->ce, zend_ce_dimension_read); + case BP_VAR_W: + if (has_offset) { + return instanceof_function(object->ce, zend_ce_dimension_write); + } else { + return instanceof_function(object->ce, zend_ce_appendable); + } + case BP_VAR_RW: + return instanceof_function(object->ce, zend_ce_dimension_read) + && instanceof_function(object->ce, zend_ce_dimension_write); + case BP_VAR_UNSET: + return instanceof_function(object->ce, zend_ce_dimension_unset); + case BP_VAR_FETCH: + if (has_offset) { + return instanceof_function(object->ce, zend_ce_dimension_fetch); + } else { + return instanceof_function(object->ce, zend_ce_dimension_fetch_append); + } + EMPTY_SWITCH_DEFAULT_CASE(); + } + return false; +} +#endif + +static zend_never_inline ZEND_COLD void zend_invalid_use_of_object_as_array(const zend_object *object, bool has_offset, int type) +{ + switch (type) { + case BP_VAR_R: + case BP_VAR_IS: + zend_throw_error(NULL, "Cannot read offset of object of type %s", ZSTR_VAL(object->ce->name)); + break; + case BP_VAR_W: + if (has_offset) { + zend_throw_error(NULL, "Cannot write to offset of object of type %s", ZSTR_VAL(object->ce->name)); + } else { + zend_throw_error(NULL, "Cannot append to object of type %s", ZSTR_VAL(object->ce->name)); + } + break; + case BP_VAR_RW: + zend_throw_error(NULL, "Cannot read-write offset of object of type %s", ZSTR_VAL(object->ce->name)); + break; + case BP_VAR_UNSET: + // TODO: Cannot unset offset of object of type %s which does not implement INTERFACE_NAME + zend_throw_error(NULL, "Cannot unset offset of object of type %s", ZSTR_VAL(object->ce->name)); + break; + case BP_VAR_FETCH: + if (has_offset) { + zend_throw_error(NULL, "Cannot fetch offset of object of type %s", ZSTR_VAL(object->ce->name)); + } else { + zend_throw_error(NULL, "Cannot fetch append object of type %s", ZSTR_VAL(object->ce->name)); + } + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } +} + static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_use_object_as_array(const zend_object *object) { zend_throw_error(NULL, "Cannot use object of type %s as array", ZSTR_VAL(object->ce->name)); @@ -1528,10 +1606,41 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_illegal_string_offset zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_STRING), offset, type); } -static zend_never_inline void zend_assign_to_object_dim(zend_object *obj, zval *dim, zval *value OPLINE_DC EXECUTE_DATA_DC) +static zend_never_inline void zend_unset_object_dim(zend_object *obj, zval *offset) { - obj->handlers->write_dimension(obj, dim, value); + ZEND_ASSERT(offset && "Offset cannot be NULL for unset"); + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(obj->ce->dimension_handlers->unset_dimension)) { + ZVAL_DEREF(offset); + obj->ce->dimension_handlers->unset_dimension(obj, offset); + } else { + zend_invalid_use_of_object_as_array(obj, /* has_offset */ true, BP_VAR_UNSET); + } + } else { + zend_use_object_as_array(obj); + } +} +static zend_never_inline void zend_assign_to_object_dim(zend_object *obj, zval *offset, zval *value OPLINE_DC EXECUTE_DATA_DC) +{ + if (EXPECTED(obj->ce->dimension_handlers)) { + if ( + offset + && obj->ce->dimension_handlers->write_dimension + ) { + ZVAL_DEREF(offset); + obj->ce->dimension_handlers->write_dimension(obj, offset, value); + } else if ( + !offset + && obj->ce->dimension_handlers->append + ) { + obj->ce->dimension_handlers->append(obj, value); + } else { + zend_invalid_use_of_object_as_array(obj, /* has_offset */ offset, BP_VAR_W); + } + } else { + zend_use_object_as_array(obj); + } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -1607,35 +1716,151 @@ static zend_always_inline int zend_binary_op(zval *ret, zval *op1, zval *op2 OPL return zend_binary_ops[opcode - ZEND_ADD](ret, op1, op2); } -static zend_never_inline void zend_binary_assign_op_obj_dim(zend_object *obj, zval *property OPLINE_DC EXECUTE_DATA_DC) +static zend_never_inline void zend_fetch_object_dimension_address(zval *result, zend_object *obj, zval *offset, int offset_type, int type EXECUTE_DATA_DC); +static zend_never_inline void zend_binary_assign_op_typed_ref(zend_reference *ref, zval *value OPLINE_DC EXECUTE_DATA_DC); + +static zend_never_inline void zend_binary_assign_op_obj_dim(zend_object *obj, zval *dim OPLINE_DC EXECUTE_DATA_DC) { zval *value; - zval *z; - zval rv, res; GC_ADDREF(obj); - if (property && UNEXPECTED(Z_ISUNDEF_P(property))) { - property = ZVAL_UNDEFINED_OP2(); + if (UNEXPECTED(dim == NULL)) { + value = get_op_data_zval_ptr_r((opline+1)->op1_type, (opline+1)->op1); + if (EXPECTED(obj->ce->dimension_handlers)) { + if (obj->ce->dimension_handlers->fetch_append) { + ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ false, BP_VAR_FETCH)); + + zval zref; + zend_fetch_object_dimension_address(&zref, obj, NULL, 0, BP_VAR_W EXECUTE_DATA_CC); + + if (UNEXPECTED(Z_TYPE(zref) == IS_UNDEF)) { + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + goto clean_up; + } + + /* BC Layer for ArrayAccess */ + if (UNEXPECTED(!Z_ISREF(zref) && Z_TYPE(zref) != IS_INDIRECT)) { +#ifdef ZEND_DEBUG + ZEND_ASSERT(instanceof_function(obj->ce, zend_ce_arrayaccess)); +#endif + /* For array access that doesn't return a reference we need to do the old read write + * technique with an offset of type IS_NULL */ + zval res; + + if (zend_binary_op(&res, &zref, value OPLINE_CC) == SUCCESS) { + zval tmp; + ZVAL_NULL(&tmp); + + obj->ce->dimension_handlers->write_dimension(obj, &tmp, &res); + } + + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_COPY(EX_VAR(opline->result.var), &res); + } + zval_ptr_dtor(&res); + zval_ptr_dtor(&zref); + goto clean_up; + } + ZEND_ASSERT((Z_ISREF(zref) || Z_TYPE(zref) == IS_INDIRECT) && "zend_fetch_object_dimension_address did not return REF/INDIRECT"); + + zval *var_ptr; + if (Z_ISREF(zref)) { + zend_reference *ref = Z_REF(zref); + var_ptr = Z_REFVAL(zref); + if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(ref))) { + zend_binary_assign_op_typed_ref(ref, value OPLINE_CC EXECUTE_DATA_CC); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + Z_TRY_DELREF(zref); + goto clean_up; + } + Z_TRY_DELREF(zref); + zend_result status = zend_binary_op(var_ptr, var_ptr, value OPLINE_CC); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + if (UNEXPECTED(status == FAILURE)) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); + } + } + } else { + var_ptr = Z_INDIRECT_P(&zref); + zend_result status = zend_binary_op(var_ptr, var_ptr, value OPLINE_CC); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + if (UNEXPECTED(status == FAILURE)) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); + } + } + } + } else { + zend_invalid_use_of_object_as_array(obj, /* has_offset */ false, BP_VAR_FETCH); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + } + } else { + zend_use_object_as_array(obj); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + } + goto clean_up; + } + + ZEND_ASSERT(dim && "Offset is NULL for Read-Write operation"); + if (UNEXPECTED(Z_ISUNDEF_P(dim))) { + dim = ZVAL_UNDEFINED_OP2(); } value = get_op_data_zval_ptr_r((opline+1)->op1_type, (opline+1)->op1); - if ((z = obj->handlers->read_dimension(obj, property, BP_VAR_R, &rv)) != NULL) { + if (EXPECTED(obj->ce->dimension_handlers)) { + if ( + obj->ce->dimension_handlers->read_dimension + && obj->ce->dimension_handlers->write_dimension + ) { + ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ true, BP_VAR_RW)); + + ZVAL_DEREF(dim); + zval *z; + zval rv, res; + + z = obj->ce->dimension_handlers->read_dimension(obj, dim, &rv); + if (UNEXPECTED(z == NULL)) { + ZEND_ASSERT(EG(exception) && "returned NULL without exception"); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + goto clean_up; + } else { + if (zend_binary_op(&res, z, value OPLINE_CC) == SUCCESS) { + obj->ce->dimension_handlers->write_dimension(obj, dim, &res); + } + } - if (zend_binary_op(&res, z, value OPLINE_CC) == SUCCESS) { - obj->handlers->write_dimension(obj, property, &res); - } - if (z == &rv) { - zval_ptr_dtor(&rv); - } - if (UNEXPECTED(RETURN_VALUE_USED(opline))) { - ZVAL_COPY(EX_VAR(opline->result.var), &res); + if (z == &rv) { + zval_ptr_dtor(&rv); + } + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_COPY(EX_VAR(opline->result.var), &res); + } + zval_ptr_dtor(&res); + } else { + zend_invalid_use_of_object_as_array(obj, /* has_offset */ true, BP_VAR_RW); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } } - zval_ptr_dtor(&res); } else { zend_use_object_as_array(obj); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_NULL(EX_VAR(opline->result.var)); } } + clean_up: FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); if (UNEXPECTED(GC_DELREF(obj) == 0)) { zend_objects_store_del(obj); @@ -1682,9 +1907,14 @@ static zend_never_inline void zend_binary_assign_op_typed_prop(zend_property_inf } } -static zend_never_inline zend_long zend_check_string_offset(zval *dim, int type EXECUTE_DATA_DC) +/* Compared to the behaviour of array offsets, isset()/empty() did not throw + * TypeErrors for invalid offsets, or warn on type coercions. + * The coalesce operator did throw on invalid offset types but not for type coercions so we need to be aware of it. */ +static zend_never_inline zend_long zend_check_string_offset(zval *dim, int type, bool *is_type_valid, bool is_coalesce EXECUTE_DATA_DC) { zend_long offset; + /* isset()/empty() are more strict in what they allow/warn */ + bool allow_errors = (type != BP_VAR_IS) || is_coalesce; try_again: switch(Z_TYPE_P(dim)) { @@ -1693,15 +1923,22 @@ static zend_never_inline zend_long zend_check_string_offset(zval *dim, int type case IS_STRING: { bool trailing_data = false; - /* For BC reasons we allow errors so that we can warn on leading numeric string */ + /* For BC reasons we allow errors so that we can warn on leading numeric string, + * however, empty() and isset() never allowed errors */ if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL, - /* allow errors */ true, NULL, &trailing_data)) { + allow_errors, NULL, &trailing_data)) { if (UNEXPECTED(trailing_data) && type != BP_VAR_UNSET) { zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim)); } return offset; } - zend_illegal_string_offset(dim, type); + if (is_type_valid) { + *is_type_valid = false; + } + if (type != BP_VAR_IS) { + zend_illegal_string_offset(dim, type); + } + /* BP_VAR_IS emits no warning and throws no exception */ return 0; } case IS_UNDEF: @@ -1711,17 +1948,29 @@ static zend_never_inline zend_long zend_check_string_offset(zval *dim, int type case IS_NULL: case IS_FALSE: case IS_TRUE: - zend_error(E_WARNING, "String offset cast occurred"); + if (type != BP_VAR_IS) { + zend_error(E_WARNING, "String offset cast occurred"); + } break; case IS_REFERENCE: dim = Z_REFVAL_P(dim); goto try_again; default: - zend_illegal_string_offset(dim, type); + if (is_type_valid) { + *is_type_valid = false; + } + if (type != BP_VAR_IS) { + zend_illegal_string_offset(dim, type); + } else if (is_coalesce) { + /* is_coalesce used to have the same logic as BP_VAR_R */ + zend_illegal_string_offset(dim, BP_VAR_R); + } + /* isset()/empty() emits no warning and throws no exception */ return 0; } - return zval_get_long_func(dim, /* is_strict */ false); + /* Being strict here means that incompatible floats emit a deprecation notice */ + return zval_get_long_func(dim, /* is_strict */ !allow_errors); } ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void) @@ -1908,11 +2157,14 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, if (EXPECTED(Z_TYPE_P(dim) == IS_LONG)) { offset = Z_LVAL_P(dim); } else { + bool is_type_valid = true; /* The string may be destroyed while throwing the notice. * Temporarily increase the refcount to detect this situation. */ - GC_ADDREF(s); - offset = zend_check_string_offset(dim, BP_VAR_W EXECUTE_DATA_CC); - if (UNEXPECTED(GC_DELREF(s) == 0)) { + if (!(GC_FLAGS(s) & IS_STR_INTERNED)) { + GC_ADDREF(s); + } + offset = zend_check_string_offset(dim, BP_VAR_W, &is_type_valid, /* is_coalesce */ false EXECUTE_DATA_CC); + if (!(GC_FLAGS(s) & IS_STR_INTERNED) && UNEXPECTED(GC_DELREF(s) == 0)) { zend_string_efree(s); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_NULL(EX_VAR(opline->result.var)); @@ -1920,7 +2172,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, return; } /* Illegal offset assignment */ - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(!is_type_valid || EG(exception) != NULL)) { if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_UNDEF(EX_VAR(opline->result.var)); } @@ -2476,7 +2728,7 @@ static ZEND_COLD void zend_binary_assign_op_dim_slow(zval *container, zval *dim if (opline->op2_type == IS_UNUSED) { zend_use_new_element_for_string(); } else { - zend_check_string_offset(dim, BP_VAR_RW EXECUTE_DATA_CC); + zend_check_string_offset(dim, BP_VAR_RW, /* is_type_valid */ NULL, /* is_coalesce */ false EXECUTE_DATA_CC); zend_wrong_string_offset_error(); } } else { @@ -2728,6 +2980,84 @@ static zend_never_inline zval* ZEND_FASTCALL zend_fetch_dimension_address_inner_ return zend_fetch_dimension_address_inner(ht, dim, IS_CONST, BP_VAR_RW EXECUTE_DATA_CC); } +static zend_never_inline void zend_fetch_object_dimension_address(zval *result, zend_object *obj, zval *offset, int offset_type, int type EXECUTE_DATA_DC) +{ + zval *retval; + + GC_ADDREF(obj); + if (ZEND_CONST_COND(offset_type == IS_CV, offset != NULL) && UNEXPECTED(Z_TYPE_P(offset) == IS_UNDEF)) { + offset = ZVAL_UNDEFINED_OP2(); + } else if (offset_type == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { + offset++; + } + + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(offset && obj->ce->dimension_handlers->fetch_dimension)) { + ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ true, BP_VAR_FETCH)); + + ZVAL_DEREF(offset); + /* For null coalesce we first check if the offset exist, + * if it does we call the fetch_dimension() handler, + * otherwise just return an undef result */ + if (UNEXPECTED(type == BP_VAR_IS)) { + ZEND_ASSERT(offset && "BP_VAR_IS with append operation is compile time failure"); + ZEND_ASSERT(obj->ce->dimension_handlers->has_dimension); + /* The key does not exist */ + if (!obj->ce->dimension_handlers->has_dimension(obj, offset)) { + ZVAL_UNDEF(result); + goto clean_up; + } + } + retval = obj->ce->dimension_handlers->fetch_dimension(obj, offset, result); + } else if (!offset && obj->ce->dimension_handlers->fetch_append) { + ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ false, BP_VAR_FETCH)); + ZEND_ASSERT(type != BP_VAR_IS); + retval = obj->ce->dimension_handlers->fetch_append(obj, result); + } else { + zend_invalid_use_of_object_as_array(obj, /* has_offset */ offset, BP_VAR_FETCH); + ZVAL_UNDEF(result); + goto clean_up; + } + if (UNEXPECTED(!retval)) { + ZEND_ASSERT(EG(exception) && "fetch/append_dimension() returned NULL without exception"); + ZVAL_UNDEF(result); + goto clean_up; + } + + if ( + !Z_ISREF_P(retval) + /* Support indirect for: + * $ao[$i] = &$var; + * and + * $ao[] = &$var; + * cases */ + && Z_TYPE_P(retval) != IS_INDIRECT + && Z_TYPE_P(retval) != IS_OBJECT + ) { + zend_class_entry *ce = obj->ce; + + /* BC Layer for ArrayAccess where we do nothing */ + if (UNEXPECTED(instanceof_function(ce, zend_ce_arrayaccess))) { + goto clean_up; + } + zend_throw_error(NULL, "%s::%s() must return a reference type", + ZSTR_VAL(ce->name), offset ? "offsetFetch" : "fetchAppend"); + ZVAL_UNDEF(result); + goto clean_up; + } + if (result != retval) { + ZVAL_INDIRECT(result, retval); + } + } else { + zend_use_object_as_array(obj); + ZVAL_UNDEF(result); + } + clean_up: + if (UNEXPECTED(GC_DELREF(obj) == 0)) { + zend_objects_store_del(obj); + } +} + static zend_always_inline void zend_fetch_dimension_address(zval *result, zval *container, zval *dim, int dim_type, int type EXECUTE_DATA_DC) { zval *retval; @@ -2778,47 +3108,27 @@ static zend_always_inline void zend_fetch_dimension_address(zval *result, zval * if (dim == NULL) { zend_use_new_element_for_string(); } else { - zend_check_string_offset(dim, type EXECUTE_DATA_CC); + zend_check_string_offset(dim, type, /* is_type_valid */ NULL, /* is_coalesce */ false EXECUTE_DATA_CC); zend_wrong_string_offset_error(); } ZVAL_UNDEF(result); } else if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { - zend_object *obj = Z_OBJ_P(container); - GC_ADDREF(obj); - if (ZEND_CONST_COND(dim_type == IS_CV, dim != NULL) && UNEXPECTED(Z_TYPE_P(dim) == IS_UNDEF)) { - dim = ZVAL_UNDEFINED_OP2(); - } else if (dim_type == IS_CONST && Z_EXTRA_P(dim) == ZEND_EXTRA_VALUE) { - dim++; - } - retval = obj->handlers->read_dimension(obj, dim, type, result); - - if (UNEXPECTED(retval == &EG(uninitialized_zval))) { - zend_class_entry *ce = obj->ce; - - ZVAL_NULL(result); - zend_error(E_NOTICE, "Indirect modification of overloaded element of %s has no effect", ZSTR_VAL(ce->name)); - } else if (EXPECTED(retval && Z_TYPE_P(retval) != IS_UNDEF)) { - if (!Z_ISREF_P(retval)) { - if (result != retval) { - ZVAL_COPY(result, retval); - retval = result; - } - if (Z_TYPE_P(retval) != IS_OBJECT) { - zend_class_entry *ce = obj->ce; - zend_error(E_NOTICE, "Indirect modification of overloaded element of %s has no effect", ZSTR_VAL(ce->name)); - } - } else if (UNEXPECTED(Z_REFCOUNT_P(retval) == 1)) { - ZVAL_UNREF(retval); - } - if (result != retval) { - ZVAL_INDIRECT(result, retval); + zend_fetch_object_dimension_address(result, Z_OBJ_P(container), dim, dim_type, type EXECUTE_DATA_CC); + /* Needed to properly support: Zend/tests/bug70321.phpt */ + if (Z_ISREF_P(result)) { + if (Z_TYPE_P(Z_REFVAL_P(result)) == IS_OBJECT) { + /* We need to seperate objects returned by reference */ + SEPARATE_ZVAL(result); } - } else { - ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); - ZVAL_UNDEF(result); } - if (UNEXPECTED(GC_DELREF(obj) == 0)) { - zend_objects_store_del(obj); + //???Do we need to make a ref for ArrayAccess non-sense values? + //???else if (UNEXPECTED(Z_TYPE_P(result) != IS_INDIRECT && Z_TYPE_P(result) != IS_OBJECT)) { + //??? /* BC Layer for ArrayAccess */ + //??? /* "Create" a ref to the value even if it is not to have BC behaviour */ + //??? ZVAL_MAKE_REF(result); + //???} + if (UNEXPECTED(EG(exception))) { + ZVAL_UNDEF(result); } } else { if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { @@ -2880,7 +3190,59 @@ static zend_never_inline void ZEND_FASTCALL zend_fetch_dimension_address_UNSET(z zend_fetch_dimension_address(result, container_ptr, dim, dim_type, BP_VAR_UNSET EXECUTE_DATA_CC); } -static zend_always_inline void zend_fetch_dimension_address_read(zval *result, zval *container, zval *dim, int dim_type, int type, bool is_list, int slow EXECUTE_DATA_DC) +/* Handle Read operations and the null coalesce operation in which case is_bp_var_is is true */ +static zend_never_inline void zend_fetch_object_dimension_address_read(zval *result, zend_object *obj, zval *offset, int offset_type, bool is_bp_var_is EXECUTE_DATA_DC) +{ + zval *retval; + + ZEND_ASSERT(offset && "Read cannot have NULL offset"); + GC_ADDREF(obj); + if (ZEND_CONST_COND(offset_type == IS_CV, 1) && UNEXPECTED(Z_TYPE_P(offset) == IS_UNDEF)) { + offset = ZVAL_UNDEFINED_OP2(); + } + if (offset_type == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { + offset++; + } + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(obj->ce->dimension_handlers->read_dimension)) { + ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ true, BP_VAR_R)); + + ZVAL_DEREF(offset); + if (UNEXPECTED( + is_bp_var_is + && !obj->ce->dimension_handlers->has_dimension(obj, offset) + )) { + ZVAL_NULL(result); + goto end; + } + + retval = obj->ce->dimension_handlers->read_dimension(obj, offset, result); + + ZEND_ASSERT(result != NULL); + if (EXPECTED(retval)) { + if (result != retval) { + ZVAL_COPY_DEREF(result, retval); + } else if (UNEXPECTED(Z_ISREF_P(retval))) { + zend_unwrap_reference(result); + } + } else { + ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); + ZVAL_NULL(result); + } + } else { + zend_invalid_use_of_object_as_array(obj, /* has_offset */ true, is_bp_var_is ? BP_VAR_IS : BP_VAR_R); + } + } else { + zend_use_object_as_array(obj); + ZVAL_NULL(result); + } + end: + if (UNEXPECTED(GC_DELREF(obj) == 0)) { + zend_objects_store_del(obj); + } +} + +static zend_always_inline void zend_fetch_dimension_address_read(zval *result, zval *container, zval *dim, int dim_type, int type, bool is_list, bool slow EXECUTE_DATA_DC) { zval *retval; @@ -2901,73 +3263,27 @@ static zend_always_inline void zend_fetch_dimension_address_read(zval *result, z zend_string *str = Z_STR_P(container); zend_long offset; -try_string_offset: - if (UNEXPECTED(Z_TYPE_P(dim) != IS_LONG)) { - switch (Z_TYPE_P(dim)) { - case IS_STRING: - { - bool trailing_data = false; - /* For BC reasons we allow errors so that we can warn on leading numeric string */ - if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, - NULL, /* allow errors */ true, NULL, &trailing_data)) { - if (UNEXPECTED(trailing_data)) { - zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim)); - } - goto out; - } - if (type == BP_VAR_IS) { - ZVAL_NULL(result); - return; - } - zend_illegal_string_offset(dim, BP_VAR_R); - ZVAL_NULL(result); - return; - } - case IS_UNDEF: - /* The string may be destroyed while throwing the notice. - * Temporarily increase the refcount to detect this situation. */ - if (!(GC_FLAGS(str) & IS_STR_INTERNED)) { - GC_ADDREF(str); - } - ZVAL_UNDEFINED_OP2(); - if (!(GC_FLAGS(str) & IS_STR_INTERNED) && UNEXPECTED(GC_DELREF(str) == 0)) { - zend_string_efree(str); - ZVAL_NULL(result); - return; - } - ZEND_FALLTHROUGH; - case IS_DOUBLE: - case IS_NULL: - case IS_FALSE: - case IS_TRUE: - if (type != BP_VAR_IS) { - /* The string may be destroyed while throwing the notice. - * Temporarily increase the refcount to detect this situation. */ - if (!(GC_FLAGS(str) & IS_STR_INTERNED)) { - GC_ADDREF(str); - } - zend_error(E_WARNING, "String offset cast occurred"); - if (!(GC_FLAGS(str) & IS_STR_INTERNED) && UNEXPECTED(GC_DELREF(str) == 0)) { - zend_string_efree(str); - ZVAL_NULL(result); - return; - } - } - break; - case IS_REFERENCE: - dim = Z_REFVAL_P(dim); - goto try_string_offset; - default: - zend_illegal_string_offset(dim, BP_VAR_R); - ZVAL_NULL(result); - return; - } - - offset = zval_get_long_func(dim, /* is_strict */ false); - } else { + if (EXPECTED(Z_TYPE_P(dim) == IS_LONG)) { offset = Z_LVAL_P(dim); + } else { + bool is_type_valid = true; + /* The string may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(str) & IS_STR_INTERNED)) { + GC_ADDREF(str); + } + offset = zend_check_string_offset(dim, type, &is_type_valid, /* is_coalesce */ true EXECUTE_DATA_CC); + if (!(GC_FLAGS(str) & IS_STR_INTERNED) && UNEXPECTED(GC_DELREF(str) == 0)) { + zend_string_efree(str); + ZVAL_NULL(result); + return; + } + /* Illegal offset assignment */ + if (UNEXPECTED(!is_type_valid || EG(exception) != NULL)) { + ZVAL_NULL(result); + return; + } } - out: if (UNEXPECTED(ZSTR_LEN(str) < ((offset < 0) ? -(size_t)offset : ((size_t)offset + 1)))) { if (type != BP_VAR_IS) { @@ -2987,29 +3303,9 @@ static zend_always_inline void zend_fetch_dimension_address_read(zval *result, z ZVAL_CHAR(result, c); } } else if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { - zend_object *obj = Z_OBJ_P(container); - - GC_ADDREF(obj); - if (ZEND_CONST_COND(dim_type == IS_CV, 1) && UNEXPECTED(Z_TYPE_P(dim) == IS_UNDEF)) { - dim = ZVAL_UNDEFINED_OP2(); - } - if (dim_type == IS_CONST && Z_EXTRA_P(dim) == ZEND_EXTRA_VALUE) { - dim++; - } - retval = obj->handlers->read_dimension(obj, dim, type, result); - - ZEND_ASSERT(result != NULL); - if (retval) { - if (result != retval) { - ZVAL_COPY_DEREF(result, retval); - } else if (UNEXPECTED(Z_ISREF_P(retval))) { - zend_unwrap_reference(result); - } - } else { - ZVAL_NULL(result); - } - if (UNEXPECTED(GC_DELREF(obj) == 0)) { - zend_objects_store_del(obj); + zend_fetch_object_dimension_address_read(result, Z_OBJ_P(container), dim, dim_type, type == BP_VAR_IS EXECUTE_DATA_CC); + if (UNEXPECTED(EG(exception))) { + ZVAL_UNDEF(result); } } else { if (type != BP_VAR_IS && UNEXPECTED(Z_TYPE_P(container) == IS_UNDEF)) { @@ -3087,12 +3383,51 @@ static zend_never_inline zval* ZEND_FASTCALL zend_find_array_dim_slow(HashTable static zend_never_inline bool ZEND_FASTCALL zend_isset_dim_slow(zval *container, zval *offset EXECUTE_DATA_DC) { + ZEND_ASSERT(offset && "isset() cannot have NULL offset"); if (/*OP2_TYPE == IS_CV &&*/ UNEXPECTED(Z_TYPE_P(offset) == IS_UNDEF)) { offset = ZVAL_UNDEFINED_OP2(); } if (/*OP1_TYPE != IS_CONST &&*/ EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { - return Z_OBJ_HT_P(container)->has_dimension(Z_OBJ_P(container), offset, 0); + zend_object *obj = Z_OBJ_P(container); + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(obj->ce->dimension_handlers->has_dimension)) { + ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ true, BP_VAR_IS)); + + ZVAL_DEREF(offset); + + /* Object handler can modify the value of the object via globals; thus take a copy */ + zval copy; + ZVAL_COPY(©, container); + const zend_class_entry *ce = obj->ce; + bool exists = ce->dimension_handlers->has_dimension(Z_OBJ(copy), offset); + if (!exists) { + zval_ptr_dtor(©); + return false; + } + + zval *retval; + zval slot; + retval = ce->dimension_handlers->read_dimension(Z_OBJ(copy), offset, &slot); + + zval_ptr_dtor(©); + if (UNEXPECTED(!retval)) { + ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); + return true; + } + ZEND_ASSERT(Z_TYPE_P(retval) != IS_UNDEF); + /* Check if value is null, if it is we consider it to not be set */ + bool result = Z_TYPE_P(retval) != IS_NULL; + zval_ptr_dtor(retval); + return result; + } else { + zend_invalid_use_of_object_as_array(obj, /* has_offset */ true, BP_VAR_IS); + return false; + } + } else { + zend_use_object_as_array(obj); + return false; + } } else if (EXPECTED(Z_TYPE_P(container) == IS_STRING)) { /* string offsets */ zend_long lval; @@ -3108,16 +3443,12 @@ static zend_never_inline bool ZEND_FASTCALL zend_isset_dim_slow(zval *container, return 0; } } else { - /*if (OP2_TYPE & (IS_CV|IS_VAR)) {*/ - ZVAL_DEREF(offset); - /*}*/ - if (Z_TYPE_P(offset) < IS_STRING /* simple scalar types */ - || (Z_TYPE_P(offset) == IS_STRING /* or numeric string */ - && IS_LONG == is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), NULL, NULL, 0))) { - lval = zval_get_long_ex(offset, /* is_strict */ true); - goto str_offset; + bool is_type_valid = true; + lval = zend_check_string_offset(offset, BP_VAR_IS, &is_type_valid, /* is_coalesce */ false EXECUTE_DATA_CC); + if (!is_type_valid || UNEXPECTED(EG(exception) != NULL)) { + return false; } - return 0; + goto str_offset; } } else { return 0; @@ -3126,12 +3457,51 @@ static zend_never_inline bool ZEND_FASTCALL zend_isset_dim_slow(zval *container, static zend_never_inline bool ZEND_FASTCALL zend_isempty_dim_slow(zval *container, zval *offset EXECUTE_DATA_DC) { + ZEND_ASSERT(offset && "empty() cannot have NULL offset"); if (/*OP2_TYPE == IS_CV &&*/ UNEXPECTED(Z_TYPE_P(offset) == IS_UNDEF)) { offset = ZVAL_UNDEFINED_OP2(); } if (/*OP1_TYPE != IS_CONST &&*/ EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { - return !Z_OBJ_HT_P(container)->has_dimension(Z_OBJ_P(container), offset, 1); + zend_object *obj = Z_OBJ_P(container); + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(obj->ce->dimension_handlers->has_dimension)) { + ZEND_ASSERT(zend_check_dimension_interfaces_implemented(obj, /* has_offset */ true, BP_VAR_IS)); + + ZVAL_DEREF(offset); + + /* Object handler can modify the value of the object via globals; thus take a copy */ + zval copy; + ZVAL_COPY(©, container); + const zend_class_entry *ce = obj->ce; + bool exists = ce->dimension_handlers->has_dimension(Z_OBJ(copy), offset); + if (!exists) { + zval_ptr_dtor(©); + return true; + } + + zval *retval; + zval slot; + retval = ce->dimension_handlers->read_dimension(Z_OBJ(copy), offset, &slot); + + zval_ptr_dtor(©); + if (UNEXPECTED(!retval)) { + ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); + return true; + } + ZEND_ASSERT(Z_TYPE_P(retval) != IS_UNDEF); + /* Check if value is empty, which it is when it is falsy, i.e. not truthy */ + bool result = !i_zend_is_true(retval); + zval_ptr_dtor(retval); + return result; + } else { + zend_invalid_use_of_object_as_array(obj, /* has_offset */ true, BP_VAR_IS); + return true; + } + } else { + zend_use_object_as_array(obj); + return true; + } } else if (EXPECTED(Z_TYPE_P(container) == IS_STRING)) { /* string offsets */ zend_long lval; @@ -3147,16 +3517,12 @@ static zend_never_inline bool ZEND_FASTCALL zend_isempty_dim_slow(zval *containe return 1; } } else { - /*if (OP2_TYPE & (IS_CV|IS_VAR)) {*/ - ZVAL_DEREF(offset); - /*}*/ - if (Z_TYPE_P(offset) < IS_STRING /* simple scalar types */ - || (Z_TYPE_P(offset) == IS_STRING /* or numeric string */ - && IS_LONG == is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), NULL, NULL, 0))) { - lval = zval_get_long_ex(offset, /* is_strict */ true); - goto str_offset; + bool is_type_valid = true; + lval = zend_check_string_offset(offset, BP_VAR_IS, &is_type_valid, /* is_coalesce */ false EXECUTE_DATA_CC); + if (!is_type_valid || UNEXPECTED(EG(exception) != NULL)) { + return true; } - return 1; + goto str_offset; } } else { return 1; diff --git a/Zend/zend_interfaces.c b/Zend/zend_interfaces.c index ad593fe2b95dd..9c4e160532cc0 100644 --- a/Zend/zend_interfaces.c +++ b/Zend/zend_interfaces.c @@ -21,11 +21,11 @@ #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_interfaces_arginfo.h" +#include "zend_interfaces_dimension.h" 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 zend_class_entry *zend_ce_serializable; ZEND_API zend_class_entry *zend_ce_countable; ZEND_API zend_class_entry *zend_ce_stringable; @@ -375,28 +375,6 @@ 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) -{ - ZEND_ASSERT(!class_type->arrayaccess_funcs_ptr && "ArrayAccess funcs already set?"); - zend_class_arrayaccess_funcs *funcs_ptr = class_type->type == ZEND_INTERNAL_CLASS - ? pemalloc(sizeof(zend_class_arrayaccess_funcs), 1) - : zend_arena_alloc(&CG(arena), sizeof(zend_class_arrayaccess_funcs)); - class_type->arrayaccess_funcs_ptr = funcs_ptr; - - funcs_ptr->zf_offsetget = zend_hash_str_find_ptr( - &class_type->function_table, "offsetget", sizeof("offsetget") - 1); - funcs_ptr->zf_offsetexists = zend_hash_str_find_ptr( - &class_type->function_table, "offsetexists", sizeof("offsetexists") - 1); - funcs_ptr->zf_offsetset = zend_hash_str_find_ptr( - &class_type->function_table, "offsetset", sizeof("offsetset") - 1); - funcs_ptr->zf_offsetunset = zend_hash_str_find_ptr( - &class_type->function_table, "offsetunset", sizeof("offsetunset") - 1); - - return SUCCESS; -} -/* }}} */ - /* {{{ zend_user_serialize */ ZEND_API int zend_user_serialize(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data) { @@ -653,8 +631,7 @@ ZEND_API void zend_register_interfaces(void) zend_ce_serializable = register_class_Serializable(); zend_ce_serializable->interface_gets_implemented = zend_implement_serializable; - zend_ce_arrayaccess = register_class_ArrayAccess(); - zend_ce_arrayaccess->interface_gets_implemented = zend_implement_arrayaccess; + zend_register_dimension_interfaces(); zend_ce_countable = register_class_Countable(); diff --git a/Zend/zend_interfaces.h b/Zend/zend_interfaces.h index 883e482f510c4..8fb4fe514567e 100644 --- a/Zend/zend_interfaces.h +++ b/Zend/zend_interfaces.h @@ -27,7 +27,6 @@ BEGIN_EXTERN_C() extern ZEND_API zend_class_entry *zend_ce_traversable; extern ZEND_API zend_class_entry *zend_ce_aggregate; extern ZEND_API zend_class_entry *zend_ce_iterator; -extern ZEND_API zend_class_entry *zend_ce_arrayaccess; extern ZEND_API zend_class_entry *zend_ce_serializable; extern ZEND_API zend_class_entry *zend_ce_countable; extern ZEND_API zend_class_entry *zend_ce_stringable; diff --git a/Zend/zend_interfaces.stub.php b/Zend/zend_interfaces.stub.php index 2247205e46973..f4ce40a0b2954 100644 --- a/Zend/zend_interfaces.stub.php +++ b/Zend/zend_interfaces.stub.php @@ -28,24 +28,6 @@ public function valid(): bool; public function rewind(): void; } -interface ArrayAccess -{ - /** @tentative-return-type */ - public function offsetExists(mixed $offset): bool; - - /** - * Actually this should be return by ref but atm cannot be. - * @tentative-return-type - */ - public function offsetGet(mixed $offset): mixed; - - /** @tentative-return-type */ - public function offsetSet(mixed $offset, mixed $value): void; - - /** @tentative-return-type */ - public function offsetUnset(mixed $offset): void; -} - interface Serializable { /** @return string|null */ diff --git a/Zend/zend_interfaces_arginfo.h b/Zend/zend_interfaces_arginfo.h index bcdc8dd87c473..c5ec2dc020246 100644 --- a/Zend/zend_interfaces_arginfo.h +++ b/Zend/zend_interfaces_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a9c915c11e5989d8c7cf2d704ada09ca765670c3 */ + * Stub hash: c5085f77dab1779100b0f845a23806c71f0f1d74 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_IteratorAggregate_getIterator, 0, 0, Traversable, 0) ZEND_END_ARG_INFO() @@ -17,23 +17,6 @@ ZEND_END_ARG_INFO() #define arginfo_class_Iterator_rewind arginfo_class_Iterator_next -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ArrayAccess_offsetExists, 0, 1, _IS_BOOL, 0) - ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ArrayAccess_offsetGet, 0, 1, IS_MIXED, 0) - ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ArrayAccess_offsetSet, 0, 2, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) - ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ArrayAccess_offsetUnset, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) -ZEND_END_ARG_INFO() - ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Serializable_serialize, 0, 0, 0) ZEND_END_ARG_INFO() @@ -87,14 +70,6 @@ static const zend_function_entry class_Iterator_methods[] = { ZEND_FE_END }; -static const zend_function_entry class_ArrayAccess_methods[] = { - ZEND_RAW_FENTRY("offsetExists", NULL, arginfo_class_ArrayAccess_offsetExists, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL, NULL) - ZEND_RAW_FENTRY("offsetGet", NULL, arginfo_class_ArrayAccess_offsetGet, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL, NULL) - ZEND_RAW_FENTRY("offsetSet", NULL, arginfo_class_ArrayAccess_offsetSet, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL, NULL) - ZEND_RAW_FENTRY("offsetUnset", NULL, arginfo_class_ArrayAccess_offsetUnset, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL, NULL) - ZEND_FE_END -}; - static const zend_function_entry class_Serializable_methods[] = { ZEND_RAW_FENTRY("serialize", NULL, arginfo_class_Serializable_serialize, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL, NULL) ZEND_RAW_FENTRY("unserialize", NULL, arginfo_class_Serializable_unserialize, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL, NULL) @@ -153,16 +128,6 @@ static zend_class_entry *register_class_Iterator(zend_class_entry *class_entry_T return class_entry; } -static zend_class_entry *register_class_ArrayAccess(void) -{ - zend_class_entry ce, *class_entry; - - INIT_CLASS_ENTRY(ce, "ArrayAccess", class_ArrayAccess_methods); - class_entry = zend_register_internal_interface(&ce); - - return class_entry; -} - static zend_class_entry *register_class_Serializable(void) { zend_class_entry ce, *class_entry; diff --git a/Zend/zend_interfaces_dimension.c b/Zend/zend_interfaces_dimension.c new file mode 100644 index 0000000000000..1cb3e10f66115 --- /dev/null +++ b/Zend/zend_interfaces_dimension.c @@ -0,0 +1,392 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Gina Peter Banyard | + +----------------------------------------------------------------------+ +*/ + +#include "zend_interfaces_dimension.h" +#include "zend_API.h" /* For arginfos */ +#include "zend_interfaces_dimensions_arginfo.h" +#include "zend_types.h" +#include "zend_compile.h" +#include "zend_alloc.h" +// Assume already included via zend_class_entry definition +//#include "zend_dimension_handlers.h" + +ZEND_API zend_class_entry *zend_ce_arrayaccess; + +ZEND_API zend_class_entry *zend_ce_dimension_read; +ZEND_API zend_class_entry *zend_ce_dimension_fetch; +ZEND_API zend_class_entry *zend_ce_dimension_write; +ZEND_API zend_class_entry *zend_ce_dimension_unset; +ZEND_API zend_class_entry *zend_ce_appendable; +ZEND_API zend_class_entry *zend_ce_dimension_fetch_append; + +/* rv is a slot provided by the callee that is returned */ +static zval *zend_user_class_read_dimension(zend_object *object, zval *offset, zval *rv) +{ + zend_class_entry *ce = object->ce; + zval tmp_offset; + zend_function *zf = zend_hash_str_find_ptr(&ce->function_table, "offsetget", strlen("offsetget")); + + ZEND_ASSERT(zf); + ZEND_ASSERT(offset); + + ZVAL_COPY_DEREF(&tmp_offset, offset); + + GC_ADDREF(object); + zend_call_known_instance_method_with_1_params(zf, object, rv, &tmp_offset); + OBJ_RELEASE(object); + zval_ptr_dtor(&tmp_offset); + + if (UNEXPECTED(Z_TYPE_P(rv) == IS_UNDEF)) { + ZEND_ASSERT(EG(exception)); + return NULL; + } + return rv; +} + +/* rv is a slot provided by the callee that is returned */ +static zval *zend_user_class_fetch_dimension(zend_object *object, zval *offset, zval *rv) +{ + zend_class_entry *ce = object->ce; + zend_function *zf = zend_hash_str_find_ptr(&ce->function_table, "offsetfetch", strlen("offsetfetch")); + + ZEND_ASSERT(zf); + ZEND_ASSERT(offset); + + GC_ADDREF(object); + zend_call_known_instance_method_with_1_params(zf, object, rv, offset); + OBJ_RELEASE(object); + + if (UNEXPECTED(Z_TYPE_P(rv) == IS_UNDEF)) { + ZEND_ASSERT(EG(exception)); + return NULL; + } + + ZEND_ASSERT(Z_ISREF_P(rv)); + return rv; +} + +static zval *zend_legacy_ArrayAccess_fetch_dimension(zend_object *object, zval *offset, zval *rv) +{ + zend_class_entry *ce = object->ce; + zend_function *user_function = zend_hash_str_find_ptr(&ce->function_table, "offsetget", strlen("offsetget")); + + GC_ADDREF(object); + zend_call_known_function(user_function, object, object->ce, rv, 1, offset, NULL); + OBJ_RELEASE(object); + + if (UNEXPECTED(Z_TYPE_P(rv) == IS_UNDEF)) { + ZEND_ASSERT(EG(exception)); + return NULL; + } + + /* Warn about not returning by-ref */ + if (!Z_ISREF_P(rv)) { + if (Z_TYPE_P(rv) != IS_OBJECT) { + zend_class_entry *ce = object->ce; + // TODO Make this an E_WARNING + zend_error(E_NOTICE, "Indirect modification of overloaded element of %s has no effect", ZSTR_VAL(ce->name)); + if (UNEXPECTED(EG(exception))) { + zval_ptr_dtor(rv); + ZVAL_UNDEF(rv); + return NULL; + } + } + } + + return rv; +} + +static zval *zend_legacy_ArrayAccess_fetch_append(zend_object *object, zval *rv) +{ + zval tmp_offset; + + ZVAL_NULL(&tmp_offset); + zval *retval = zend_legacy_ArrayAccess_fetch_dimension(object, &tmp_offset, rv); + zval_ptr_dtor(&tmp_offset); + + return retval; +} + +static bool zend_user_class_has_dimension(zend_object *object, zval *offset) +{ + zend_class_entry *ce = object->ce; + zval tmp_offset; + zval isset_method_rv; + bool is_set = false; + + zend_function *zf_has = zend_hash_str_find_ptr(&ce->function_table, "offsetexists", strlen("offsetexists")); + ZEND_ASSERT(zf_has); + ZEND_ASSERT(offset); + + ZVAL_COPY_DEREF(&tmp_offset, offset); + GC_ADDREF(object); + zend_call_known_instance_method_with_1_params(zf_has, object, &isset_method_rv, &tmp_offset); + + if (UNEXPECTED(Z_TYPE(isset_method_rv) == IS_UNDEF)) { + ZEND_ASSERT(EG(exception)); + } else { + // Use i_zend_is_true(isset_method_rv)? + is_set = Z_TYPE(isset_method_rv) == IS_TRUE; + } + + zval_ptr_dtor(&isset_method_rv); + zval_ptr_dtor(&tmp_offset); + OBJ_RELEASE(object); + return is_set; +} + +static void zend_user_class_write_dimension(zend_object *object, zval *offset, zval *value) +{ + zend_class_entry *ce = object->ce; + zend_function *zf = zend_hash_str_find_ptr(&ce->function_table, "offsetset", strlen("offsetset")); + + ZEND_ASSERT(zf); + ZEND_ASSERT(offset); + ZEND_ASSERT(value); + + zval tmp_offset; + //Z_TRY_ADDREF_P(value); + ZVAL_COPY_DEREF(&tmp_offset, offset); + GC_ADDREF(object); + zend_call_known_instance_method_with_2_params(zf, object, /* retval */ NULL, &tmp_offset, value); + OBJ_RELEASE(object); + zval_ptr_dtor(&tmp_offset); +} + +static void zend_user_class_append(zend_object *object, zval *value) +{ + zend_class_entry *ce = object->ce; + zend_function *zf = zend_hash_str_find_ptr(&ce->function_table, "append", strlen("append")); + + ZEND_ASSERT(zf); + ZEND_ASSERT(value); + + //Z_TRY_ADDREF_P(value); + GC_ADDREF(object); + zend_call_known_instance_method_with_1_params(zf, object, /* retval */ NULL, value); + OBJ_RELEASE(object); +} + +static void zend_legacy_ArrayAccess_append(zend_object *object, zval *value) +{ + zval tmp_offset; + zend_class_entry *ce = object->ce; + zend_function *zf = zend_hash_str_find_ptr(&ce->function_table, "offsetset", strlen("offsetset")); + + ZEND_ASSERT(zf); + ZEND_ASSERT(value); + + ZVAL_NULL(&tmp_offset); + GC_ADDREF(object); + zend_call_known_instance_method_with_2_params(zf, object, /* retval */ NULL, &tmp_offset, value); + OBJ_RELEASE(object); + zval_ptr_dtor(&tmp_offset); +} + +static zval *zend_user_class_fetch_append(zend_object *object, zval *rv) +{ + zend_class_entry *ce = object->ce; + zend_function *zf = zend_hash_str_find_ptr(&ce->function_table, "fetchappend", strlen("fetchappend")); + + ZEND_ASSERT(zf); + + GC_ADDREF(object); + zend_call_known_instance_method_with_0_params(zf, object, rv); + OBJ_RELEASE(object); + + if (UNEXPECTED(Z_TYPE_P(rv) == IS_UNDEF)) { + ZEND_ASSERT(EG(exception)); + return NULL; + } + ZEND_ASSERT(Z_ISREF_P(rv)); + return rv; +} + +static void zend_user_class_unset_dimension(zend_object *object, zval *offset) +{ + zend_class_entry *ce = object->ce; + zval tmp_offset; + + zend_function *zf = zend_hash_str_find_ptr(&ce->function_table, "offsetunset", strlen("offsetunset")); + ZEND_ASSERT(zf); + ZEND_ASSERT(offset); + + ZVAL_COPY_DEREF(&tmp_offset, offset); + GC_ADDREF(object); + zend_call_known_instance_method_with_1_params(zf, object, /* retval */ NULL, &tmp_offset); + OBJ_RELEASE(object); + zval_ptr_dtor(&tmp_offset); +} + +/* Internal classes must define their own handlers if they overload the dimension access */ +#define ALLOC_HANDLERS_IF_MISSING(dimension_handlers_funcs_ptr, class_ce) \ + if (!class_ce->dimension_handlers) { \ + ZEND_ASSERT(class_ce->type == ZEND_USER_CLASS); \ + dimension_handlers_funcs_ptr = zend_arena_calloc(&CG(arena), 1, sizeof(zend_class_dimensions_functions)); \ + class_type->dimension_handlers = dimension_handlers_funcs_ptr; \ + } else { \ + dimension_handlers_funcs_ptr = class_ce->dimension_handlers; \ + } + +static /* const */ zend_class_dimensions_functions zend_default_array_access_dimensions_functions = { + .read_dimension = zend_user_class_read_dimension, + .has_dimension = zend_user_class_has_dimension, + .fetch_dimension = zend_legacy_ArrayAccess_fetch_dimension, + .write_dimension = zend_user_class_write_dimension, + .unset_dimension = zend_user_class_unset_dimension, + .append = zend_legacy_ArrayAccess_append, + .fetch_append = zend_legacy_ArrayAccess_fetch_append, +}; + +// TODO Have a nested/fetch handler? To handle nested/fetch dimension reads +static int zend_implement_arrayaccess(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->type == ZEND_INTERNAL_CLASS) { + class_type->dimension_handlers = &zend_default_array_access_dimensions_functions; + return SUCCESS; + } + + zend_class_dimensions_functions *funcs = NULL; + ALLOC_HANDLERS_IF_MISSING(funcs, class_type); + + funcs->read_dimension = zend_user_class_read_dimension; + funcs->has_dimension = zend_user_class_has_dimension; + funcs->fetch_dimension = zend_legacy_ArrayAccess_fetch_dimension; + funcs->write_dimension = zend_user_class_write_dimension; + funcs->unset_dimension = zend_user_class_unset_dimension; + funcs->append = zend_legacy_ArrayAccess_append; + funcs->fetch_append = zend_legacy_ArrayAccess_fetch_append; + + return SUCCESS; +} + +static int zend_implement_dimension_read(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->type == ZEND_INTERNAL_CLASS) { + return SUCCESS; + } + + zend_class_dimensions_functions *funcs = NULL; + ALLOC_HANDLERS_IF_MISSING(funcs, class_type); + + // TODO: Check if the class that interface implements this relies on the parent handler or not? + if (!funcs->read_dimension) { + funcs->read_dimension = zend_user_class_read_dimension; + funcs->has_dimension = zend_user_class_has_dimension; + } + return SUCCESS; +} + +static int zend_implement_dimension_write(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->type == ZEND_INTERNAL_CLASS) { + return SUCCESS; + } + + zend_class_dimensions_functions *funcs = NULL; + ALLOC_HANDLERS_IF_MISSING(funcs, class_type); + + if (!funcs->write_dimension) { + funcs->write_dimension = zend_user_class_write_dimension; + } + return SUCCESS; +} + +static int zend_implement_dimension_unset(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->type == ZEND_INTERNAL_CLASS) { + return SUCCESS; + } + + zend_class_dimensions_functions *funcs = NULL; + ALLOC_HANDLERS_IF_MISSING(funcs, class_type); + + if (!funcs->unset_dimension) { + funcs->unset_dimension = zend_user_class_unset_dimension; + } + return SUCCESS; +} + +static int zend_implement_appendable(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->type == ZEND_INTERNAL_CLASS) { + return SUCCESS; + } + + zend_class_dimensions_functions *funcs = NULL; + ALLOC_HANDLERS_IF_MISSING(funcs, class_type); + + if (!funcs->append) { + funcs->append = zend_user_class_append; + } + return SUCCESS; +} + +static int zend_implement_dimension_fetch(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->type == ZEND_INTERNAL_CLASS) { + return SUCCESS; + } + + zend_class_dimensions_functions *funcs = NULL; + ALLOC_HANDLERS_IF_MISSING(funcs, class_type); + + if (!funcs->fetch_dimension) { + funcs->fetch_dimension = zend_user_class_fetch_dimension; + } + return SUCCESS; +} + +static int zend_implement_dimension_fetch_append(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->type == ZEND_INTERNAL_CLASS) { + return SUCCESS; + } + + zend_class_dimensions_functions *funcs = NULL; + ALLOC_HANDLERS_IF_MISSING(funcs, class_type); + + if (!funcs->fetch_append) { + funcs->fetch_append = zend_user_class_fetch_append; + } + return SUCCESS; +} + +ZEND_API void zend_register_dimension_interfaces(void) +{ + zend_ce_arrayaccess = register_class_ArrayAccess(); + zend_ce_arrayaccess->interface_gets_implemented = zend_implement_arrayaccess; + + zend_ce_dimension_read = register_class_DimensionReadable(); + zend_ce_dimension_read->interface_gets_implemented = zend_implement_dimension_read; + + zend_ce_dimension_write = register_class_DimensionWritable(); + zend_ce_dimension_write->interface_gets_implemented = zend_implement_dimension_write; + + zend_ce_dimension_unset = register_class_DimensionUnsetable(); + zend_ce_dimension_unset->interface_gets_implemented = zend_implement_dimension_unset; + + zend_ce_appendable = register_class_Appendable(); + zend_ce_appendable->interface_gets_implemented = zend_implement_appendable; + + zend_ce_dimension_fetch = register_class_DimensionFetchable(zend_ce_dimension_read); + zend_ce_dimension_fetch->interface_gets_implemented = zend_implement_dimension_fetch; + + zend_ce_dimension_fetch_append = register_class_FetchAppendable(zend_ce_appendable); + zend_ce_dimension_fetch_append->interface_gets_implemented = zend_implement_dimension_fetch_append; +} diff --git a/Zend/zend_interfaces_dimension.h b/Zend/zend_interfaces_dimension.h new file mode 100644 index 0000000000000..c4c18261364a9 --- /dev/null +++ b/Zend/zend_interfaces_dimension.h @@ -0,0 +1,40 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Gina Peter Banyard | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_DIMENSION_INTERFACES_H +#define ZEND_DIMENSION_INTERFACES_H + +#include "zend_types.h" +#include "zend_compile.h" + +BEGIN_EXTERN_C() + +extern ZEND_API zend_class_entry *zend_ce_arrayaccess; + +extern ZEND_API zend_class_entry *zend_ce_dimension_read; +extern ZEND_API zend_class_entry *zend_ce_dimension_fetch; +extern ZEND_API zend_class_entry *zend_ce_dimension_write; +extern ZEND_API zend_class_entry *zend_ce_dimension_unset; +extern ZEND_API zend_class_entry *zend_ce_appendable; +extern ZEND_API zend_class_entry *zend_ce_dimension_fetch_append; + +ZEND_API void zend_register_dimension_interfaces(void); + +END_EXTERN_C() + +#endif /* ZEND_DIMENSION_INTERFACES_H */ diff --git a/Zend/zend_interfaces_dimensions.stub.php b/Zend/zend_interfaces_dimensions.stub.php new file mode 100644 index 0000000000000..65e98c62de54a --- /dev/null +++ b/Zend/zend_interfaces_dimensions.stub.php @@ -0,0 +1,53 @@ +name)); -} -/* }}} */ - -ZEND_API zval *zend_std_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ -{ - zend_class_entry *ce = object->ce; - zval tmp_offset; - - /* arrayaccess_funcs_ptr is set if (and only if) the class implements zend_ce_arrayaccess */ - zend_class_arrayaccess_funcs *funcs = ce->arrayaccess_funcs_ptr; - if (EXPECTED(funcs)) { - if (offset == NULL) { - /* [] construct */ - ZVAL_NULL(&tmp_offset); - } else { - ZVAL_COPY_DEREF(&tmp_offset, offset); - } - - GC_ADDREF(object); - if (type == BP_VAR_IS) { - zend_call_known_instance_method_with_1_params(funcs->zf_offsetexists, object, rv, &tmp_offset); - if (UNEXPECTED(Z_ISUNDEF_P(rv))) { - OBJ_RELEASE(object); - zval_ptr_dtor(&tmp_offset); - return NULL; - } - if (!i_zend_is_true(rv)) { - OBJ_RELEASE(object); - zval_ptr_dtor(&tmp_offset); - zval_ptr_dtor(rv); - return &EG(uninitialized_zval); - } - zval_ptr_dtor(rv); - } - - zend_call_known_instance_method_with_1_params(funcs->zf_offsetget, object, rv, &tmp_offset); - - OBJ_RELEASE(object); - zval_ptr_dtor(&tmp_offset); - - if (UNEXPECTED(Z_TYPE_P(rv) == IS_UNDEF)) { - if (UNEXPECTED(!EG(exception))) { - zend_throw_error(NULL, "Undefined offset for object of type %s used as array", ZSTR_VAL(ce->name)); - } - return NULL; - } - return rv; - } else { - zend_bad_array_access(ce); - return NULL; - } -} -/* }}} */ - -ZEND_API void zend_std_write_dimension(zend_object *object, zval *offset, zval *value) /* {{{ */ -{ - zend_class_entry *ce = object->ce; - zval tmp_offset; - - zend_class_arrayaccess_funcs *funcs = ce->arrayaccess_funcs_ptr; - if (EXPECTED(funcs)) { - if (!offset) { - ZVAL_NULL(&tmp_offset); - } else { - ZVAL_COPY_DEREF(&tmp_offset, offset); - } - GC_ADDREF(object); - zend_call_known_instance_method_with_2_params(funcs->zf_offsetset, object, NULL, &tmp_offset, value); - OBJ_RELEASE(object); - zval_ptr_dtor(&tmp_offset); - } else { - zend_bad_array_access(ce); - } -} -/* }}} */ - -// todo: make zend_std_has_dimension return bool as well -ZEND_API int zend_std_has_dimension(zend_object *object, zval *offset, int check_empty) /* {{{ */ -{ - zend_class_entry *ce = object->ce; - zval retval, tmp_offset; - bool result; - - zend_class_arrayaccess_funcs *funcs = ce->arrayaccess_funcs_ptr; - if (EXPECTED(funcs)) { - ZVAL_COPY_DEREF(&tmp_offset, offset); - GC_ADDREF(object); - zend_call_known_instance_method_with_1_params(funcs->zf_offsetexists, object, &retval, &tmp_offset); - result = i_zend_is_true(&retval); - zval_ptr_dtor(&retval); - if (check_empty && result && EXPECTED(!EG(exception))) { - zend_call_known_instance_method_with_1_params(funcs->zf_offsetget, object, &retval, &tmp_offset); - result = i_zend_is_true(&retval); - zval_ptr_dtor(&retval); - } - OBJ_RELEASE(object); - zval_ptr_dtor(&tmp_offset); - } else { - zend_bad_array_access(ce); - return 0; - } - - return result; -} -/* }}} */ - ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *name, int type, void **cache_slot) /* {{{ */ { zval *retval = NULL; @@ -1251,24 +1142,6 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void } /* }}} */ -ZEND_API void zend_std_unset_dimension(zend_object *object, zval *offset) /* {{{ */ -{ - zend_class_entry *ce = object->ce; - zval tmp_offset; - - zend_class_arrayaccess_funcs *funcs = ce->arrayaccess_funcs_ptr; - if (EXPECTED(funcs)) { - ZVAL_COPY_DEREF(&tmp_offset, offset); - GC_ADDREF(object); - zend_call_known_instance_method_with_1_params(funcs->zf_offsetunset, object, NULL, &tmp_offset); - OBJ_RELEASE(object); - zval_ptr_dtor(&tmp_offset); - } else { - zend_bad_array_access(ce); - } -} -/* }}} */ - static zend_never_inline zend_function *zend_get_parent_private_method(zend_class_entry *scope, zend_class_entry *ce, zend_string *function_name) /* {{{ */ { zval *func; @@ -2017,13 +1890,9 @@ ZEND_API const zend_object_handlers std_object_handlers = { zend_std_read_property, /* read_property */ zend_std_write_property, /* write_property */ - zend_std_read_dimension, /* read_dimension */ - zend_std_write_dimension, /* write_dimension */ zend_std_get_property_ptr_ptr, /* get_property_ptr_ptr */ zend_std_has_property, /* has_property */ zend_std_unset_property, /* unset_property */ - zend_std_has_dimension, /* has_dimension */ - zend_std_unset_dimension, /* unset_dimension */ zend_std_get_properties, /* get_properties */ zend_std_get_method, /* get_method */ zend_std_get_constructor, /* get_constructor */ diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 35498f34617c0..557a26480d988 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -43,19 +43,11 @@ struct _zend_property_info; /* Used to fetch property from the object, read-only */ typedef zval *(*zend_object_read_property_t)(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv); -/* Used to fetch dimension from the object, read-only */ -typedef zval *(*zend_object_read_dimension_t)(zend_object *object, zval *offset, int type, zval *rv); - - /* Used to set property of the object You must return the final value of the assigned property. */ typedef zval *(*zend_object_write_property_t)(zend_object *object, zend_string *member, zval *value, void **cache_slot); -/* Used to set dimension of the object */ -typedef void (*zend_object_write_dimension_t)(zend_object *object, zval *offset, zval *value); - - /* Used to create pointer to the property of the object, for future direct r/w access. * May return one of: * * A zval pointer, without incrementing the reference count. @@ -73,15 +65,9 @@ typedef zval *(*zend_object_get_property_ptr_ptr_t)(zend_object *object, zend_st */ typedef int (*zend_object_has_property_t)(zend_object *object, zend_string *member, int has_set_exists, void **cache_slot); -/* Used to check if a dimension of the object exists */ -typedef int (*zend_object_has_dimension_t)(zend_object *object, zval *member, int check_empty); - /* Used to remove a property of the object */ typedef void (*zend_object_unset_property_t)(zend_object *object, zend_string *member, void **cache_slot); -/* Used to remove a dimension of the object */ -typedef void (*zend_object_unset_dimension_t)(zend_object *object, zval *offset); - /* Used to get hash of the properties of the object, as hash of zval's */ typedef HashTable *(*zend_object_get_properties_t)(zend_object *object); @@ -169,13 +155,9 @@ struct _zend_object_handlers { zend_object_clone_obj_t clone_obj; /* optional */ zend_object_read_property_t read_property; /* required */ zend_object_write_property_t write_property; /* required */ - zend_object_read_dimension_t read_dimension; /* required */ - zend_object_write_dimension_t write_dimension; /* required */ zend_object_get_property_ptr_ptr_t get_property_ptr_ptr; /* required */ zend_object_has_property_t has_property; /* required */ zend_object_unset_property_t unset_property; /* required */ - zend_object_has_dimension_t has_dimension; /* required */ - zend_object_unset_dimension_t unset_dimension; /* required */ zend_object_get_properties_t get_properties; /* required */ zend_object_get_method_t get_method; /* required */ zend_object_get_constructor_t get_constructor; /* required */ diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 8828a03d7b618..53385251d849f 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -419,6 +419,7 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->num_interfaces > 0 && (ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)) { efree(ce->interfaces); } + /* Do not free ce->dimension_handlers as it is allocated on the arena */ if (ce->backed_enum_table) { zend_hash_release(ce->backed_enum_table); } @@ -507,9 +508,6 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->iterator_funcs_ptr) { free(ce->iterator_funcs_ptr); } - if (ce->arrayaccess_funcs_ptr) { - free(ce->arrayaccess_funcs_ptr); - } if (ce->num_interfaces > 0) { free(ce->interfaces); } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b710fa874af15..57dd221d02a0a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6681,7 +6681,7 @@ ZEND_VM_C_LABEL(num_index_dim): if (OP2_TYPE == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { offset++; } - Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + zend_unset_object_dim(Z_OBJ_P(container), offset); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 6cb6ff41ec8d9..04af33757c89a 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -25682,7 +25682,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_UNSET_DIM_SPEC_VAR_CONST_HANDL if (IS_CONST == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { offset++; } - Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + zend_unset_object_dim(Z_OBJ_P(container), offset); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -28132,7 +28132,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_UNSET_DIM_SPEC_VAR_TMPVAR_HAND if ((IS_TMP_VAR|IS_VAR) == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { offset++; } - Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + zend_unset_object_dim(Z_OBJ_P(container), offset); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -32520,7 +32520,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_UNSET_DIM_SPEC_VAR_CV_HANDLER( if (IS_CV == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { offset++; } - Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + zend_unset_object_dim(Z_OBJ_P(container), offset); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -44266,7 +44266,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_UNSET_DIM_SPEC_CV_CONST_HANDLE if (IS_CONST == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { offset++; } - Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + zend_unset_object_dim(Z_OBJ_P(container), offset); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -47906,7 +47906,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_UNSET_DIM_SPEC_CV_TMPVAR_HANDL if ((IS_TMP_VAR|IS_VAR) == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { offset++; } - Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + zend_unset_object_dim(Z_OBJ_P(container), offset); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -53392,7 +53392,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_UNSET_DIM_SPEC_CV_CV_HANDLER(Z if (IS_CV == IS_CONST && Z_EXTRA_P(offset) == ZEND_EXTRA_VALUE) { offset++; } - Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + zend_unset_object_dim(Z_OBJ_P(container), offset); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index aea46e493410e..0473236961e95 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -16,6 +16,7 @@ #include "zend.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "zend_objects_API.h" #include "zend_types.h" #include "zend_weakrefs.h" @@ -341,14 +342,28 @@ static void zend_weakmap_free_obj(zend_object *object) zend_object_std_dtor(&wm->std); } -static zval *zend_weakmap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +static zval *zend_weakmap_read_dimension(zend_object *object, /* const */ zval *offset, zval *rv) { - if (offset == NULL) { - zend_throw_error(NULL, "Cannot append to WeakMap"); + if (Z_TYPE_P(offset) != IS_OBJECT) { + zend_type_error("WeakMap key must be an object"); + return NULL; + } + + zend_weakmap *wm = zend_weakmap_from(object); + zend_object *obj_addr = Z_OBJ_P(offset); + zval *zv = zend_hash_index_find(&wm->ht, zend_object_to_weakref_key(obj_addr)); + if (zv == NULL) { + zend_throw_error(NULL, + "Object %s#%d not contained in WeakMap", ZSTR_VAL(obj_addr->ce->name), obj_addr->handle); return NULL; } - ZVAL_DEREF(offset); + ZVAL_COPY(rv, zv); + return rv; +} + +static zval *zend_weakmap_fetch_dimension(zend_object *object, /* const */ zval *offset, zval *rv) +{ if (Z_TYPE_P(offset) != IS_OBJECT) { zend_type_error("WeakMap key must be an object"); return NULL; @@ -358,28 +373,18 @@ static zval *zend_weakmap_read_dimension(zend_object *object, zval *offset, int zend_object *obj_addr = Z_OBJ_P(offset); zval *zv = zend_hash_index_find(&wm->ht, zend_object_to_weakref_key(obj_addr)); if (zv == NULL) { - if (type != BP_VAR_IS) { - zend_throw_error(NULL, - "Object %s#%d not contained in WeakMap", ZSTR_VAL(obj_addr->ce->name), obj_addr->handle); - return NULL; - } + zend_throw_error(NULL, + "Object %s#%d not contained in WeakMap", ZSTR_VAL(obj_addr->ce->name), obj_addr->handle); return NULL; } - if (type == BP_VAR_W || type == BP_VAR_RW) { - ZVAL_MAKE_REF(zv); - } - return zv; + ZVAL_MAKE_REF(zv); + ZVAL_COPY(rv, zv); + return rv; } -static void zend_weakmap_write_dimension(zend_object *object, zval *offset, zval *value) +static void zend_weakmap_write_dimension(zend_object *object, /* const */ zval *offset, zval *value) { - if (offset == NULL) { - zend_throw_error(NULL, "Cannot append to WeakMap"); - return; - } - - ZVAL_DEREF(offset); if (Z_TYPE_P(offset) != IS_OBJECT) { zend_type_error("WeakMap key must be an object"); return; @@ -405,11 +410,8 @@ static void zend_weakmap_write_dimension(zend_object *object, zval *offset, zval zend_hash_index_add_new(&wm->ht, obj_key, value); } -// todo: make zend_weakmap_has_dimension return bool as well -/* int return and check_empty due to Object Handler API */ -static int zend_weakmap_has_dimension(zend_object *object, zval *offset, int check_empty) +static bool zend_weakmap_has_dimension(zend_object *object, /* const */ zval *offset) { - ZVAL_DEREF(offset); if (Z_TYPE_P(offset) != IS_OBJECT) { zend_type_error("WeakMap key must be an object"); return 0; @@ -418,18 +420,14 @@ static int zend_weakmap_has_dimension(zend_object *object, zval *offset, int che zend_weakmap *wm = zend_weakmap_from(object); zval *zv = zend_hash_index_find(&wm->ht, zend_object_to_weakref_key(Z_OBJ_P(offset))); if (!zv) { - return 0; + return false; } - if (check_empty) { - return i_zend_is_true(zv); - } - return Z_TYPE_P(zv) != IS_NULL; + return true; } -static void zend_weakmap_unset_dimension(zend_object *object, zval *offset) +static void zend_weakmap_unset_dimension(zend_object *object, /* const */ zval *offset) { - ZVAL_DEREF(offset); if (Z_TYPE_P(offset) != IS_OBJECT) { zend_type_error("WeakMap key must be an object"); return; @@ -697,12 +695,24 @@ ZEND_METHOD(WeakMap, offsetGet) RETURN_THROWS(); } - zval *zv = zend_weakmap_read_dimension(Z_OBJ_P(ZEND_THIS), key, BP_VAR_R, NULL); + zval *zv = zend_weakmap_read_dimension(Z_OBJ_P(ZEND_THIS), key, return_value); if (!zv) { RETURN_THROWS(); } +} + +ZEND_METHOD(WeakMap, offsetFetch) +{ + zval *key; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &key) == FAILURE) { + RETURN_THROWS(); + } - ZVAL_COPY(return_value, zv); + zval *zv = zend_weakmap_fetch_dimension(Z_OBJ_P(ZEND_THIS), key, return_value); + if (!zv) { + RETURN_THROWS(); + } } ZEND_METHOD(WeakMap, offsetSet) @@ -724,7 +734,7 @@ ZEND_METHOD(WeakMap, offsetExists) RETURN_THROWS(); } - RETURN_BOOL(zend_weakmap_has_dimension(Z_OBJ_P(ZEND_THIS), key, /* check_empty */ 0)); + RETURN_BOOL(zend_weakmap_has_dimension(Z_OBJ_P(ZEND_THIS), key)); } ZEND_METHOD(WeakMap, offsetUnset) @@ -758,6 +768,14 @@ ZEND_METHOD(WeakMap, getIterator) zend_create_internal_iterator_zval(return_value, ZEND_THIS); } +static /* const */ zend_class_dimensions_functions zend_weakmap_dimensions_functions = { + .read_dimension = zend_weakmap_read_dimension, + .has_dimension = zend_weakmap_has_dimension, + .fetch_dimension = zend_weakmap_fetch_dimension, + .write_dimension = zend_weakmap_write_dimension, + .unset_dimension = zend_weakmap_unset_dimension +}; + void zend_register_weakref_ce(void) /* {{{ */ { zend_ce_weakref = register_class_WeakReference(); @@ -772,19 +790,22 @@ void zend_register_weakref_ce(void) /* {{{ */ zend_weakref_handlers.get_debug_info = zend_weakref_get_debug_info; zend_weakref_handlers.clone_obj = NULL; - zend_ce_weakmap = register_class_WeakMap(zend_ce_arrayaccess, zend_ce_countable, zend_ce_aggregate); + zend_ce_weakmap = register_class_WeakMap( + zend_ce_countable, + zend_ce_aggregate, + zend_ce_dimension_fetch, + zend_ce_dimension_write, + zend_ce_dimension_unset + ); zend_ce_weakmap->create_object = zend_weakmap_create_object; zend_ce_weakmap->get_iterator = zend_weakmap_get_iterator; zend_ce_weakmap->default_object_handlers = &zend_weakmap_handlers; + zend_ce_weakmap->dimension_handlers = &zend_weakmap_dimensions_functions; memcpy(&zend_weakmap_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); zend_weakmap_handlers.offset = XtOffsetOf(zend_weakmap, std); zend_weakmap_handlers.free_obj = zend_weakmap_free_obj; - zend_weakmap_handlers.read_dimension = zend_weakmap_read_dimension; - zend_weakmap_handlers.write_dimension = zend_weakmap_write_dimension; - zend_weakmap_handlers.has_dimension = zend_weakmap_has_dimension; - zend_weakmap_handlers.unset_dimension = zend_weakmap_unset_dimension; zend_weakmap_handlers.count_elements = zend_weakmap_count_elements; zend_weakmap_handlers.get_properties_for = zend_weakmap_get_properties_for; zend_weakmap_handlers.get_gc = zend_weakmap_get_gc; diff --git a/Zend/zend_weakrefs.stub.php b/Zend/zend_weakrefs.stub.php index 3c3d0a84603ed..446be04101c67 100644 --- a/Zend/zend_weakrefs.stub.php +++ b/Zend/zend_weakrefs.stub.php @@ -19,11 +19,14 @@ public function get(): ?object {} * @strict-properties * @not-serializable */ -final class WeakMap implements ArrayAccess, Countable, IteratorAggregate +final class WeakMap implements Countable, IteratorAggregate, DimensionFetchable, DimensionWritable, DimensionUnsetable { /** @param object $object */ public function offsetGet($object): mixed {} + /** @param object $object */ + public function &offsetFetch(mixed $offset): mixed; + /** @param object $object */ public function offsetSet($object, mixed $value): void {} diff --git a/Zend/zend_weakrefs_arginfo.h b/Zend/zend_weakrefs_arginfo.h index 25c137c9d76b6..e958db9503fa4 100644 --- a/Zend/zend_weakrefs_arginfo.h +++ b/Zend/zend_weakrefs_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d91889851d9732d41e43fffddb6235d033c67534 */ + * Stub hash: 0bab94f80eb791bcdb02d5fd76b5728bb88f466f */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_WeakReference___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -15,6 +15,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_WeakMap_offsetGet, 0, 1, I ZEND_ARG_INFO(0, object) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_WeakMap_offsetFetch, 1, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_WeakMap_offsetSet, 0, 2, IS_VOID, 0) ZEND_ARG_INFO(0, object) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -38,6 +42,7 @@ ZEND_METHOD(WeakReference, __construct); ZEND_METHOD(WeakReference, create); ZEND_METHOD(WeakReference, get); ZEND_METHOD(WeakMap, offsetGet); +ZEND_METHOD(WeakMap, offsetFetch); ZEND_METHOD(WeakMap, offsetSet); ZEND_METHOD(WeakMap, offsetExists); ZEND_METHOD(WeakMap, offsetUnset); @@ -53,6 +58,7 @@ static const zend_function_entry class_WeakReference_methods[] = { static const zend_function_entry class_WeakMap_methods[] = { ZEND_ME(WeakMap, offsetGet, arginfo_class_WeakMap_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(WeakMap, offsetFetch, arginfo_class_WeakMap_offsetFetch, ZEND_ACC_PUBLIC) ZEND_ME(WeakMap, offsetSet, arginfo_class_WeakMap_offsetSet, ZEND_ACC_PUBLIC) ZEND_ME(WeakMap, offsetExists, arginfo_class_WeakMap_offsetExists, ZEND_ACC_PUBLIC) ZEND_ME(WeakMap, offsetUnset, arginfo_class_WeakMap_offsetUnset, ZEND_ACC_PUBLIC) @@ -72,14 +78,14 @@ static zend_class_entry *register_class_WeakReference(void) return class_entry; } -static zend_class_entry *register_class_WeakMap(zend_class_entry *class_entry_ArrayAccess, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_IteratorAggregate) +static zend_class_entry *register_class_WeakMap(zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_DimensionFetchable, zend_class_entry *class_entry_DimensionWritable, zend_class_entry *class_entry_DimensionUnsetable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "WeakMap", class_WeakMap_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; - zend_class_implements(class_entry, 3, class_entry_ArrayAccess, class_entry_Countable, class_entry_IteratorAggregate); + zend_class_implements(class_entry, 5, class_entry_Countable, class_entry_IteratorAggregate, class_entry_DimensionFetchable, class_entry_DimensionWritable, class_entry_DimensionUnsetable); return class_entry; } diff --git a/configure.ac b/configure.ac index d09a9876e84ad..e033993e5b97d 100644 --- a/configure.ac +++ b/configure.ac @@ -1728,6 +1728,7 @@ PHP_ADD_SOURCES([Zend], [m4_normalize([ zend_ini_scanner.c zend_ini.c zend_interfaces.c + zend_interfaces_dimension.c zend_iterators.c zend_language_parser.c zend_language_scanner.c diff --git a/ext/com_dotnet/com_extension.c b/ext/com_dotnet/com_extension.c index bd9f981be13ec..78ee2296af151 100644 --- a/ext/com_dotnet/com_extension.c +++ b/ext/com_dotnet/com_extension.c @@ -185,22 +185,26 @@ PHP_MINIT_FUNCTION(com_dotnet) /* php_com_saproxy_class_entry->constructor->common.fn_flags |= ZEND_ACC_PROTECTED; */ php_com_saproxy_class_entry->default_object_handlers = &php_com_saproxy_handlers; php_com_saproxy_class_entry->get_iterator = php_com_saproxy_iter_get; + php_com_saproxy_class_entry->dimension_handlers = &php_com_saproxy_dimensions_functions; php_com_variant_class_entry = register_class_variant(); php_com_variant_class_entry->default_object_handlers = &php_com_object_handlers; php_com_variant_class_entry->create_object = php_com_object_new; php_com_variant_class_entry->get_iterator = php_com_iter_get; + php_com_variant_class_entry->dimension_handlers = &php_com_dimensions_functions; tmp = register_class_com(php_com_variant_class_entry); tmp->default_object_handlers = &php_com_object_handlers; tmp->create_object = php_com_object_new; tmp->get_iterator = php_com_iter_get; + tmp->dimension_handlers = &php_com_dimensions_functions; #if HAVE_MSCOREE_H tmp = register_class_dotnet(php_com_variant_class_entry); tmp->default_object_handlers = &php_com_object_handlers; tmp->create_object = php_com_object_new; tmp->get_iterator = php_com_iter_get; + tmp->dimension_handlers = &php_com_dimensions_functions; #endif REGISTER_INI_ENTRIES(); diff --git a/ext/com_dotnet/com_handlers.c b/ext/com_dotnet/com_handlers.c index a5c68bb7b9987..272dbd5afc3d6 100644 --- a/ext/com_dotnet/com_handlers.c +++ b/ext/com_dotnet/com_handlers.c @@ -24,6 +24,7 @@ #include "php_com_dotnet.h" #include "php_com_dotnet_internal.h" #include "Zend/zend_exceptions.h" +#include "Zend/zend_interfaces_dimension.h" static zval *com_property_read(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv) { @@ -77,7 +78,7 @@ static zval *com_property_write(zend_object *object, zend_string *member, zval * return value; } -static zval *com_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +static zval *com_read_dimension(zend_object *object, zval *offset, zval *rv) { php_com_dotnet_object *obj; VARIANT v; @@ -122,11 +123,6 @@ static void com_write_dimension(zend_object *object, zval *offset, zval *value) obj = (php_com_dotnet_object*) object; - if (offset == NULL) { - php_com_throw_exception(DISP_E_BADINDEX, "appending to variants is not supported"); - return; - } - if (V_VT(&obj->v) == VT_DISPATCH) { ZVAL_COPY_VALUE(&args[0], offset); ZVAL_COPY_VALUE(&args[1], value); @@ -201,23 +197,25 @@ static int com_property_exists(zend_object *object, zend_string *member, int che return 0; } -static int com_dimension_exists(zend_object *object, zval *member, int check_empty) +static bool com_dimension_exists(zend_object *object, zval *member) { /* TODO Add support */ zend_throw_error(NULL, "Cannot check dimension on a COM object"); - return 0; + return false; } +/* const */ zend_class_dimensions_functions php_com_dimensions_functions = { + .read_dimension = com_read_dimension, + .has_dimension = com_dimension_exists, + .fetch_dimension = com_read_dimension, + .write_dimension = com_write_dimension, +}; + static void com_property_delete(zend_object *object, zend_string *member, void **cache_slot) { zend_throw_error(NULL, "Cannot delete properties from a COM object"); } -static void com_dimension_delete(zend_object *object, zval *offset) -{ - zend_throw_error(NULL, "Cannot delete dimension from a COM object"); -} - static HashTable *com_properties_get(zend_object *object) { /* TODO: use type-info to get all the names and values ? @@ -517,13 +515,9 @@ zend_object_handlers php_com_object_handlers = { php_com_object_clone, com_property_read, com_property_write, - com_read_dimension, - com_write_dimension, com_get_property_ptr_ptr, com_property_exists, com_property_delete, - com_dimension_exists, - com_dimension_delete, com_properties_get, com_method_get, zend_std_get_constructor, diff --git a/ext/com_dotnet/com_saproxy.c b/ext/com_dotnet/com_saproxy.c index c68001b1b311f..dec5896138438 100644 --- a/ext/com_dotnet/com_saproxy.c +++ b/ext/com_dotnet/com_saproxy.c @@ -81,7 +81,7 @@ static zval *saproxy_property_write(zend_object *object, zend_string *member, zv return value; } -static zval *saproxy_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +static zval *saproxy_read_dimension(zend_object *object, zval *offset, zval *rv) { php_com_saproxy *proxy = (php_com_saproxy*) object; UINT dims, i; @@ -280,16 +280,16 @@ static void saproxy_write_dimension(zend_object *object, zval *offset, zval *val } } -static int saproxy_property_exists(zend_object *object, zend_string *member, int check_empty, void **cache_slot) +static bool saproxy_dimension_exists(zend_object *object, zval *member) { - /* no properties */ - return 0; + /* TODO Add support */ + zend_throw_error(NULL, "Cannot check dimension on a COM object"); + return false; } -static int saproxy_dimension_exists(zend_object *object, zval *member, int check_empty) +static int saproxy_property_exists(zend_object *object, zend_string *member, int check_empty, void **cache_slot) { - /* TODO Add support */ - zend_throw_error(NULL, "Cannot check dimension on a COM object"); + /* no properties */ return 0; } @@ -394,25 +394,28 @@ zend_object_handlers php_com_saproxy_handlers = { saproxy_clone, saproxy_property_read, saproxy_property_write, - saproxy_read_dimension, - saproxy_write_dimension, NULL, saproxy_property_exists, saproxy_property_delete, - saproxy_dimension_exists, - saproxy_dimension_delete, saproxy_properties_get, saproxy_method_get, saproxy_constructor_get, saproxy_class_name_get, saproxy_object_cast, saproxy_count_elements, - NULL, /* get_debug_info */ - NULL, /* get_closure */ - NULL, /* get_gc */ - NULL, /* do_operation */ - saproxy_objects_compare, /* compare */ - NULL, /* get_properties_for */ + NULL, /* get_debug_info */ + NULL, /* get_closure */ + NULL, /* get_gc */ + NULL, /* do_operation */ + saproxy_objects_compare, /* compare */ + NULL, /* get_properties_for */ +}; + +/* const */ zend_class_dimensions_functions php_com_saproxy_dimensions_functions = { + .read_dimension = saproxy_read_dimension, + .has_dimension = saproxy_dimension_exists, + .fetch_dimension = saproxy_read_dimension, + .write_dimension = saproxy_write_dimension, }; void php_com_saproxy_create(zend_object *com_object, zval *proxy_out, zval *index) diff --git a/ext/com_dotnet/php_com_dotnet_internal.h b/ext/com_dotnet/php_com_dotnet_internal.h index 18982e56ccfa0..1915355c80600 100644 --- a/ext/com_dotnet/php_com_dotnet_internal.h +++ b/ext/com_dotnet/php_com_dotnet_internal.h @@ -24,6 +24,7 @@ #include #include #include "win32/winutil.h" +#include "Zend/zend_interfaces_dimension.h" typedef struct _php_com_dotnet_object { zend_object zo; @@ -73,12 +74,14 @@ zend_object* php_com_object_new(zend_class_entry *ce); zend_object* php_com_object_clone(zend_object *object); void php_com_object_free_storage(zend_object *object); extern zend_object_handlers php_com_object_handlers; +extern /* const */ zend_class_dimensions_functions php_com_dimensions_functions; void php_com_object_enable_event_sink(php_com_dotnet_object *obj, bool enable); /* com_saproxy.c */ zend_object_iterator *php_com_saproxy_iter_get(zend_class_entry *ce, zval *object, int by_ref); void php_com_saproxy_create(zend_object *com_object, zval *proxy_out, zval *index); extern zend_object_handlers php_com_saproxy_handlers; +extern /* const */ zend_class_dimensions_functions php_com_saproxy_dimensions_functions; /* com_olechar.c */ PHP_COM_DOTNET_API zend_string *php_com_olestring_to_string(OLECHAR *olestring, int codepage); diff --git a/ext/com_dotnet/tests/bug78694.phpt b/ext/com_dotnet/tests/bug78694.phpt index f44b00a4f4fda..bbb67cb78b58e 100644 --- a/ext/com_dotnet/tests/bug78694.phpt +++ b/ext/com_dotnet/tests/bug78694.phpt @@ -7,11 +7,11 @@ com_dotnet foreach ([new com('WScript.Shell'), new variant([])] as $var) { try { $var[] = 42; - } catch (com_exception $ex) { - var_dump($ex->getMessage()); + } catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } } ?> --EXPECT-- -string(38) "appending to variants is not supported" -string(38) "appending to variants is not supported" +Error: Cannot append to object of type com +Error: Cannot append to object of type variant diff --git a/ext/dom/html_collection.c b/ext/dom/html_collection.c index 4bc5713d37cdb..86ffe7967b248 100644 --- a/ext/dom/html_collection.c +++ b/ext/dom/html_collection.c @@ -98,16 +98,11 @@ PHP_METHOD(Dom_HTMLCollection, namedItem) dom_html_collection_named_item_into_zval(return_value, key, Z_OBJ_P(ZEND_THIS)); } -zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, zval *rv) { - if (UNEXPECTED(!offset)) { - zend_throw_error(NULL, "Cannot append to %s", ZSTR_VAL(object->ce->name)); - return NULL; - } - dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(offset); if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL)) { - zend_illegal_container_offset(object->ce->name, offset, type); + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_R); return NULL; } @@ -121,14 +116,11 @@ zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, int return rv; } -int dom_html_collection_has_dimension(zend_object *object, zval *member, int check_empty) +bool dom_html_collection_has_dimension(zend_object *object, zval *offset) { - /* If it exists, it cannot be empty because nodes aren't empty. */ - ZEND_IGNORE_VALUE(check_empty); - - dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(member); + dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(offset); if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL)) { - zend_illegal_container_offset(object->ce->name, member, BP_VAR_IS); + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_IS); return 0; } diff --git a/ext/dom/html_collection.h b/ext/dom/html_collection.h index a94daa1aae805..e746576539aeb 100644 --- a/ext/dom/html_collection.h +++ b/ext/dom/html_collection.h @@ -17,7 +17,7 @@ #ifndef PHP_HTML_COLLECTION_H #define PHP_HTML_COLLECTION_H -zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, int type, zval *rv); -int dom_html_collection_has_dimension(zend_object *object, zval *member, int check_empty); +zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, zval *rv); +bool dom_html_collection_has_dimension(zend_object *object, zval *member); #endif diff --git a/ext/dom/namednodemap.c b/ext/dom/namednodemap.c index bc867aba43840..3a50f58c7aa26 100644 --- a/ext/dom/namednodemap.c +++ b/ext/dom/namednodemap.c @@ -241,6 +241,90 @@ PHP_METHOD(DOMNamedNodeMap, getNamedItemNS) } /* }}} end dom_namednodemap_get_named_item_ns */ +PHP_METHOD(DOMNamedNodeMap, offsetGet) +{ + zend_string *key = NULL; + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(key, index) + ZEND_PARSE_PARAMETERS_END(); + + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + dom_nnodemap_object *object_map = intern->ptr; + + if (key) { + // TODO Handle numeric string? + php_dom_named_node_map_get_named_item_into_zval(object_map, key, return_value); + } else { + if (index < 0 || ZEND_LONG_INT_OVFL(index)) { + zend_argument_value_error(1, "must be between 0 and %d", INT_MAX); + RETURN_THROWS(); + } + + php_dom_named_node_map_get_item_into_zval(object_map, index, return_value); + } +} + +PHP_METHOD(DOMNamedNodeMap, offsetFetch) +{ + zend_string *key = NULL; + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(key, index) + ZEND_PARSE_PARAMETERS_END(); + + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + dom_nnodemap_object *object_map = intern->ptr; + + if (key) { + // TODO Handle numeric string? + php_dom_named_node_map_get_named_item_into_zval(object_map, key, return_value); + if (Z_TYPE_P(return_value) == IS_NULL) { + ZVAL_MAKE_REF(return_value); + } + } else { + if (index < 0 || ZEND_LONG_INT_OVFL(index)) { + zend_argument_value_error(1, "must be between 0 and %d", INT_MAX); + RETURN_THROWS(); + } + + php_dom_named_node_map_get_item_into_zval(object_map, index, return_value); + if (Z_TYPE_P(return_value) == IS_NULL) { + ZVAL_MAKE_REF(return_value); + } + } +} + + +PHP_METHOD(DOMNamedNodeMap, offsetExists) +{ + zend_string *key = NULL; + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(key, index) + ZEND_PARSE_PARAMETERS_END(); + + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + + if (key) { + dom_nnodemap_object *object_map = intern->ptr; + // TODO Handle numeric string? + RETURN_BOOL(php_dom_named_node_map_get_named_item(object_map, key, false) != NULL); + } else { + // TODO Still check for valid offset greater than or equal to 0? + if (index < 0) { + // TODO Check standard wording + zend_argument_value_error(1, "must be greater or equal than 0"); + RETURN_THROWS(); + } + + RETURN_BOOL(index < php_dom_get_namednodemap_length(intern) ); + } +} + /* {{{ */ PHP_METHOD(DOMNamedNodeMap, count) { diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c index 9e6212de53bcd..0ac5051aaf651 100644 --- a/ext/dom/nodelist.c +++ b/ext/dom/nodelist.c @@ -278,7 +278,7 @@ dom_nodelist_dimension_index dom_modern_nodelist_get_index(const zval *offset) return ret; } -zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, zval *rv) { if (UNEXPECTED(!offset)) { zend_throw_error(NULL, "Cannot append to %s", ZSTR_VAL(object->ce->name)); @@ -287,7 +287,7 @@ zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(offset); if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL || index.type == DOM_NODELIST_DIM_STRING)) { - zend_illegal_container_offset(object->ce->name, offset, type); + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_R); return NULL; } @@ -295,11 +295,8 @@ zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int return rv; } -int dom_modern_nodelist_has_dimension(zend_object *object, zval *member, int check_empty) +bool dom_modern_nodelist_has_dimension(zend_object *object, zval *member) { - /* If it exists, it cannot be empty because nodes aren't empty. */ - ZEND_IGNORE_VALUE(check_empty); - dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(member); if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL || index.type == DOM_NODELIST_DIM_STRING)) { zend_illegal_container_offset(object->ce->name, member, BP_VAR_IS); @@ -309,4 +306,64 @@ int dom_modern_nodelist_has_dimension(zend_object *object, zval *member, int che return index.lval >= 0 && index.lval < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); } +PHP_METHOD(DOMNodeList, offsetGet) +{ + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + if (index < 0) { + // TODO Check standard wording + zend_argument_value_error(1, "must be greater or equal than 0"); + RETURN_THROWS(); + } + + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + dom_nnodemap_object *objmap = intern->ptr; + php_dom_nodelist_get_item_into_zval(objmap, index, return_value); +} + +PHP_METHOD(DOMNodeList, offsetFetch) +{ + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + if (index < 0) { + // TODO Check standard wording + zend_argument_value_error(1, "must be greater or equal than 0"); + RETURN_THROWS(); + } + + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + dom_nnodemap_object *objmap = intern->ptr; + php_dom_nodelist_get_item_into_zval(objmap, index, return_value); + if (Z_TYPE_P(return_value) == IS_NULL) { + ZVAL_MAKE_REF(return_value); + } +} + +PHP_METHOD(DOMNodeList, offsetExists) +{ + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + // TODO Still check for valid offset greater than or equal to 0? + if (index < 0) { + // TODO Check standard wording + zend_argument_value_error(1, "must be greater or equal than 0"); + RETURN_THROWS(); + } + + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + RETURN_BOOL(index < php_dom_get_nodelist_length(intern) ); +} + #endif diff --git a/ext/dom/nodelist.h b/ext/dom/nodelist.h index 5ac3de1e46c44..5218fd1652aee 100644 --- a/ext/dom/nodelist.h +++ b/ext/dom/nodelist.h @@ -34,7 +34,7 @@ typedef struct dom_nodelist_dimension_index { void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); zend_long php_dom_get_nodelist_length(dom_object *obj); dom_nodelist_dimension_index dom_modern_nodelist_get_index(const zval *offset); -zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv); -int dom_modern_nodelist_has_dimension(zend_object *object, zval *member, int check_empty); +zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, zval *rv); +bool dom_modern_nodelist_has_dimension(zend_object *object, zval *member); #endif diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index e6e7a23600d94..1aa24d6246767 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -32,6 +32,7 @@ #include "dom_properties.h" #include "token_list.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "lexbor/lexbor/core/types.h" #include "lexbor/lexbor/core/lexbor.h" @@ -676,12 +677,14 @@ ZEND_GET_MODULE(dom) #endif void dom_nnodemap_objects_free_storage(zend_object *object); -static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv); -static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty); -static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv); -static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty); -static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv); -static int dom_modern_nodemap_has_dimension(zend_object *object, zval *member, int check_empty); +static zval *dom_node_list_read_dimension(zend_object *object, zval *offset, zval *rv); +static bool dom_node_list_has_dimension(zend_object *object, zval *member); +static zval *dom_node_named_map_read_dimension(zend_object *object, zval *offset, zval *rv); +static bool dom_node_named_map_has_dimension(zend_object *object, zval *member); +static zend_object *dom_objects_store_clone_obj(zend_object *zobject); + +static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset, zval *rv); +static bool dom_modern_nodemap_has_dimension(zend_object *object, zval *member); #ifdef LIBXML_XPATH_ENABLED void dom_xpath_objects_free_storage(zend_object *object); @@ -704,7 +707,48 @@ static void dom_free(void *ptr) { efree(ptr); } -/* {{{ PHP_MINIT_FUNCTION(dom) */ +static /* const */ zend_class_dimensions_functions dom_legacy_nodelist_dimensions_functions = { + .read_dimension = dom_node_list_read_dimension, + .has_dimension = dom_node_list_has_dimension, + /* As we return objects the fetch and read dimension implementation is identical */ + .fetch_dimension = dom_node_list_read_dimension, +}; + +static /* const */ zend_class_dimensions_functions dom_modern_nodelist_dimensions_functions = { + .read_dimension = dom_modern_nodelist_read_dimension, + .has_dimension = dom_modern_nodelist_has_dimension, + /* As we return objects the fetch and read dimension implementation is identical */ + .fetch_dimension = dom_modern_nodelist_read_dimension, +}; + +static /* const */ zend_class_dimensions_functions dom_legacy_named_nodemap_dimensions_functions = { + .read_dimension = dom_node_named_map_read_dimension, + .has_dimension = dom_node_named_map_has_dimension, + /* As we return objects the fetch and read dimension implementation is identical */ + .fetch_dimension = dom_node_named_map_read_dimension, +}; + +static /* const */ zend_class_dimensions_functions dom_modern_nodemap_dimensions_functions = { + .read_dimension = dom_modern_nodemap_read_dimension, + .has_dimension = dom_modern_nodemap_has_dimension, + /* As we return objects the fetch and read dimension implementation is identical */ + .fetch_dimension = dom_modern_nodemap_read_dimension, +}; + +static /* const */ zend_class_dimensions_functions dom_html_collection_dimensions_functions = { + .read_dimension = dom_html_collection_read_dimension, + .has_dimension = dom_html_collection_has_dimension, + /* As we return objects the fetch and read dimension implementation is identical */ + .fetch_dimension = dom_html_collection_read_dimension, +}; + +static /* const */ zend_class_dimensions_functions dom_token_list_dimensions_functions = { + .read_dimension = dom_token_list_read_dimension, + .has_dimension = dom_token_list_has_dimension, + /* As we return objects the fetch and read dimension implementation is identical */ + .fetch_dimension = dom_token_list_read_dimension, +}; + PHP_MINIT_FUNCTION(dom) { memcpy(&dom_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); @@ -724,24 +768,12 @@ PHP_MINIT_FUNCTION(dom) memcpy(&dom_nnodemap_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_nnodemap_object_handlers.free_obj = dom_nnodemap_objects_free_storage; - dom_nnodemap_object_handlers.read_dimension = dom_nodemap_read_dimension; - dom_nnodemap_object_handlers.has_dimension = dom_nodemap_has_dimension; + // TODO Those are redundant now with dom_nnodemap_object_handlers memcpy(&dom_nodelist_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers)); - dom_nodelist_object_handlers.read_dimension = dom_nodelist_read_dimension; - dom_nodelist_object_handlers.has_dimension = dom_nodelist_has_dimension; - memcpy(&dom_modern_nnodemap_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers)); - dom_modern_nnodemap_object_handlers.read_dimension = dom_modern_nodemap_read_dimension; - dom_modern_nnodemap_object_handlers.has_dimension = dom_modern_nodemap_has_dimension; - memcpy(&dom_modern_nodelist_object_handlers, &dom_nodelist_object_handlers, sizeof(zend_object_handlers)); - dom_modern_nodelist_object_handlers.read_dimension = dom_modern_nodelist_read_dimension; - dom_modern_nodelist_object_handlers.has_dimension = dom_modern_nodelist_has_dimension; - memcpy(&dom_html_collection_object_handlers, &dom_modern_nodelist_object_handlers, sizeof(zend_object_handlers)); - dom_html_collection_object_handlers.read_dimension = dom_html_collection_read_dimension; - dom_html_collection_object_handlers.has_dimension = dom_html_collection_has_dimension; memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std); @@ -755,8 +787,6 @@ PHP_MINIT_FUNCTION(dom) * for this object, which is incompatible with cloning because it imposes that there is only one instance * per parent object. */ dom_token_list_object_handlers.clone_obj = NULL; - dom_token_list_object_handlers.read_dimension = dom_token_list_read_dimension; - dom_token_list_object_handlers.has_dimension = dom_token_list_has_dimension; zend_hash_init(&classes, 0, NULL, NULL, true); @@ -931,49 +961,57 @@ PHP_MINIT_FUNCTION(dom) zend_hash_merge(&dom_xml_document_prop_handlers, &dom_abstract_base_document_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_xml_document_class_entry->name, &dom_xml_document_prop_handlers); - dom_nodelist_class_entry = register_class_DOMNodeList(zend_ce_aggregate, zend_ce_countable); + /* Set DomNodeList handlers and Dimension CE handlers */ + dom_nodelist_class_entry = register_class_DOMNodeList(zend_ce_aggregate, zend_ce_countable, zend_ce_dimension_fetch); dom_nodelist_class_entry->create_object = dom_nnodemap_objects_new; dom_nodelist_class_entry->default_object_handlers = &dom_nodelist_object_handlers; dom_nodelist_class_entry->get_iterator = php_dom_get_iterator; + dom_nodelist_class_entry->dimension_handlers = &dom_legacy_nodelist_dimensions_functions; zend_hash_init(&dom_nodelist_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_nodelist_prop_handlers, "length", dom_nodelist_length_read, NULL); zend_hash_add_new_ptr(&classes, dom_nodelist_class_entry->name, &dom_nodelist_prop_handlers); - dom_modern_nodelist_class_entry = register_class_Dom_NodeList(zend_ce_aggregate, zend_ce_countable); + dom_modern_nodelist_class_entry = register_class_Dom_NodeList(zend_ce_aggregate, zend_ce_countable, zend_ce_dimension_fetch); dom_modern_nodelist_class_entry->create_object = dom_nnodemap_objects_new; dom_modern_nodelist_class_entry->default_object_handlers = &dom_modern_nodelist_object_handlers; dom_modern_nodelist_class_entry->get_iterator = php_dom_get_iterator; + dom_modern_nodelist_class_entry->dimension_handlers = &dom_modern_nodelist_dimensions_functions; zend_hash_add_new_ptr(&classes, dom_modern_nodelist_class_entry->name, &dom_nodelist_prop_handlers); - dom_namednodemap_class_entry = register_class_DOMNamedNodeMap(zend_ce_aggregate, zend_ce_countable); + dom_namednodemap_class_entry = register_class_DOMNamedNodeMap(zend_ce_aggregate, zend_ce_countable, zend_ce_dimension_fetch); + dom_namednodemap_class_entry->create_object = dom_nnodemap_objects_new; dom_namednodemap_class_entry->default_object_handlers = &dom_nnodemap_object_handlers; dom_namednodemap_class_entry->get_iterator = php_dom_get_iterator; + dom_namednodemap_class_entry->dimension_handlers = &dom_legacy_named_nodemap_dimensions_functions; zend_hash_init(&dom_namednodemap_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_namednodemap_prop_handlers, "length", dom_namednodemap_length_read, NULL); zend_hash_add_new_ptr(&classes, dom_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers); - dom_modern_namednodemap_class_entry = register_class_Dom_NamedNodeMap(zend_ce_aggregate, zend_ce_countable); + dom_modern_namednodemap_class_entry = register_class_Dom_NamedNodeMap(zend_ce_aggregate, zend_ce_countable, zend_ce_dimension_fetch); dom_modern_namednodemap_class_entry->create_object = dom_nnodemap_objects_new; dom_modern_namednodemap_class_entry->default_object_handlers = &dom_modern_nnodemap_object_handlers; dom_modern_namednodemap_class_entry->get_iterator = php_dom_get_iterator; + dom_modern_namednodemap_class_entry->dimension_handlers = &dom_modern_nodemap_dimensions_functions; zend_hash_add_new_ptr(&classes, dom_modern_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers); - dom_modern_dtd_namednodemap_class_entry = register_class_Dom_DtdNamedNodeMap(zend_ce_aggregate, zend_ce_countable); + dom_modern_dtd_namednodemap_class_entry = register_class_Dom_DtdNamedNodeMap(zend_ce_aggregate, zend_ce_countable, zend_ce_dimension_fetch); dom_modern_dtd_namednodemap_class_entry->create_object = dom_nnodemap_objects_new; dom_modern_dtd_namednodemap_class_entry->default_object_handlers = &dom_modern_nnodemap_object_handlers; dom_modern_dtd_namednodemap_class_entry->get_iterator = php_dom_get_iterator; + dom_modern_dtd_namednodemap_class_entry->dimension_handlers = &dom_modern_nodemap_dimensions_functions; zend_hash_add_new_ptr(&classes, dom_modern_dtd_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers); - dom_html_collection_class_entry = register_class_Dom_HTMLCollection(zend_ce_aggregate, zend_ce_countable); + dom_html_collection_class_entry = register_class_Dom_HTMLCollection(zend_ce_aggregate, zend_ce_countable, zend_ce_dimension_fetch); dom_html_collection_class_entry->create_object = dom_nnodemap_objects_new; dom_html_collection_class_entry->default_object_handlers = &dom_html_collection_object_handlers; dom_html_collection_class_entry->get_iterator = php_dom_get_iterator; + dom_html_collection_class_entry->dimension_handlers = &dom_html_collection_dimensions_functions; zend_hash_add_new_ptr(&classes, dom_html_collection_class_entry->name, &dom_nodelist_prop_handlers); @@ -1257,10 +1295,11 @@ PHP_MINIT_FUNCTION(dom) zend_hash_add_new_ptr(&classes, dom_modern_xpath_class_entry->name, &dom_xpath_prop_handlers); #endif - dom_token_list_class_entry = register_class_Dom_TokenList(zend_ce_aggregate, zend_ce_countable); + dom_token_list_class_entry = register_class_Dom_TokenList(zend_ce_aggregate, zend_ce_countable, zend_ce_dimension_fetch); dom_token_list_class_entry->create_object = dom_token_list_new; dom_token_list_class_entry->default_object_handlers = &dom_token_list_object_handlers; dom_token_list_class_entry->get_iterator = dom_token_list_get_iterator; + dom_token_list_class_entry->dimension_handlers = &dom_token_list_dimensions_functions; zend_hash_init(&dom_token_list_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_token_list_prop_handlers, "length", dom_token_list_length_read, NULL); @@ -2244,14 +2283,13 @@ static bool dom_nodemap_or_nodelist_process_offset_as_named(zval *offset, zend_l return false; } -static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +static zval *dom_node_list_read_dimension(zend_object *object, zval *offset, zval *rv) /* {{{ */ { if (UNEXPECTED(!offset)) { zend_throw_error(NULL, "Cannot access %s without offset", ZSTR_VAL(object->ce->name)); return NULL; } - - ZVAL_DEREF(offset); + ZEND_ASSERT(offset); zend_long lval; if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { @@ -2264,21 +2302,16 @@ static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int return rv; } -static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty) +static bool dom_node_list_has_dimension(zend_object *object, zval *offset) { - ZVAL_DEREF(member); - - /* If it exists, it cannot be empty because nodes aren't empty. */ - ZEND_IGNORE_VALUE(check_empty); - - zend_long offset; - if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) { + zend_long lval; + if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { /* does not support named lookup */ - return 0; + return false; } - return offset >= 0 && offset < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); -} + return lval >= 0 && lval < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); +} /* }}} end dom_nodelist_has_dimension */ void dom_remove_all_children(xmlNodePtr nodep) { @@ -2336,14 +2369,9 @@ void php_dom_get_content_into_zval(const xmlNode *nodep, zval *return_value, boo } } -static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +static zval *dom_node_named_map_read_dimension(zend_object *object, zval *offset, zval *rv) /* {{{ */ { - if (UNEXPECTED(!offset)) { - zend_throw_error(NULL, "Cannot access %s without offset", ZSTR_VAL(object->ce->name)); - return NULL; - } - - ZVAL_DEREF(offset); + ZEND_ASSERT(offset); zend_long lval; if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { @@ -2362,32 +2390,23 @@ static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int t return rv; } -static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty) +static bool dom_node_named_map_has_dimension(zend_object *object, zval *offset) { - ZVAL_DEREF(member); - - /* If it exists, it cannot be empty because nodes aren't empty. */ - ZEND_IGNORE_VALUE(check_empty); - - zend_long offset; - if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) { + zend_long lval; + if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { /* exceptional case, switch to named lookup */ - return php_dom_named_node_map_get_named_item(php_dom_obj_from_obj(object)->ptr, Z_STR_P(member), false) != NULL; + return php_dom_named_node_map_get_named_item(php_dom_obj_from_obj(object)->ptr, Z_STR_P(offset), false) != NULL; } - return offset >= 0 && offset < php_dom_get_namednodemap_length(php_dom_obj_from_obj(object)); + return lval >= 0 && lval < php_dom_get_namednodemap_length(php_dom_obj_from_obj(object)); } -static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset, zval *rv) { - if (UNEXPECTED(!offset)) { - zend_throw_error(NULL, "Cannot append to %s", ZSTR_VAL(object->ce->name)); - return NULL; - } + ZEND_ASSERT(offset); dom_nnodemap_object *map = php_dom_obj_from_obj(object)->ptr; - ZVAL_DEREF(offset); if (Z_TYPE_P(offset) == IS_STRING) { zend_ulong lval; if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), lval)) { @@ -2400,22 +2419,19 @@ static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset } else if (Z_TYPE_P(offset) == IS_DOUBLE) { php_dom_named_node_map_get_item_into_zval(map, zend_dval_to_lval_safe(Z_DVAL_P(offset)), rv); } else { - zend_illegal_container_offset(object->ce->name, offset, type); + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_R); return NULL; } return rv; } -static int dom_modern_nodemap_has_dimension(zend_object *object, zval *member, int check_empty) +// TODO Rename member to offset? +static bool dom_modern_nodemap_has_dimension(zend_object *object, zval *member) { - /* If it exists, it cannot be empty because nodes aren't empty. */ - ZEND_IGNORE_VALUE(check_empty); - dom_object *obj = php_dom_obj_from_obj(object); dom_nnodemap_object *map = obj->ptr; - ZVAL_DEREF(member); if (Z_TYPE_P(member) == IS_STRING) { zend_ulong lval; if (ZEND_HANDLE_NUMERIC(Z_STR_P(member), lval)) { diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 15d639fb27e94..34ac31c33aee6 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -501,7 +501,7 @@ public function prepend(...$nodes): void {} public function replaceChildren(...$nodes): void {} } - class DOMNodeList implements IteratorAggregate, Countable + class DOMNodeList implements IteratorAggregate, Countable, DimensionFetchable { /** @readonly */ public int $length; @@ -513,6 +513,13 @@ public function getIterator(): Iterator {} /** @return DOMElement|DOMNode|DOMNameSpaceNode|null */ public function item(int $index) {} + + /** @return DOMElement|DOMNode|DOMNameSpaceNode */ + public function offsetGet(mixed $offset): mixed {} + + /** @return DOMElement|DOMNode|DOMNameSpaceNode */ + public function &offsetFetch(mixed $offset): mixed {} + public function offsetExists(mixed $offset): bool {} } class DOMCharacterData extends DOMNode implements DOMChildNode @@ -911,7 +918,7 @@ public function isElementContentWhitespace(): bool {} public function splitText(int $offset) {} } - class DOMNamedNodeMap implements IteratorAggregate, Countable + class DOMNamedNodeMap implements IteratorAggregate, Countable, DimensionFetchable { /** @readonly */ public int $length; @@ -929,6 +936,13 @@ public function item(int $index): ?DOMNode {} public function count(): int {} public function getIterator(): Iterator {} + + /** @return DOMAttr */ + public function offsetGet(mixed $offset): mixed {} + + /** @return DOMAttr */ + public function &offsetFetch(mixed $offset): mixed {} + public function offsetExists(mixed $offset): bool {} } class DOMEntity extends DOMNode @@ -1213,7 +1227,7 @@ public function __sleep(): array {} public function __wakeup(): void {} } - class NodeList implements \IteratorAggregate, \Countable + class NodeList implements \IteratorAggregate, \Countable, \DimensionFetchable { /** @readonly */ public int $length; @@ -1226,9 +1240,17 @@ public function getIterator(): \Iterator {} /** @implementation-alias DOMNodeList::item */ public function item(int $index): ?Node {} + + /** @implementation-alias DOMNodeList::offsetGet */ + public function offsetGet(mixed $offset): ?Attr {} + + /** @implementation-alias DOMNodeList::offsetFetch */ + public function &offsetFetch(mixed $offset): ?Attr {} + /** @implementation-alias DOMNodeList::offsetExists */ + public function offsetExists(mixed $offset): bool {} } - class NamedNodeMap implements \IteratorAggregate, \Countable + class NamedNodeMap implements \IteratorAggregate, \Countable, \DimensionFetchable { /** @readonly */ public int $length; @@ -1245,9 +1267,17 @@ public function count(): int {} /** @implementation-alias DOMNamedNodeMap::getIterator */ public function getIterator(): \Iterator {} + + /** @implementation-alias DOMNamedNodeMap::offsetGet */ + public function offsetGet(mixed $offset): ?Attr {} + + /** @implementation-alias DOMNamedNodeMap::offsetFetch */ + public function &offsetFetch(mixed $offset): ?Attr {} + /** @implementation-alias DOMNamedNodeMap::offsetExists */ + public function offsetExists(mixed $offset): bool {} } - class DtdNamedNodeMap implements \IteratorAggregate, \Countable + class DtdNamedNodeMap implements \IteratorAggregate, \Countable, \DimensionFetchable { /** @readonly */ public int $length; @@ -1264,9 +1294,17 @@ public function count(): int {} /** @implementation-alias DOMNamedNodeMap::getIterator */ public function getIterator(): \Iterator {} + + /** @implementation-alias DOMNamedNodeMap::offsetGet */ + public function offsetGet(mixed $offset): ?Attr {} + + /** @implementation-alias DOMNamedNodeMap::offsetFetch */ + public function &offsetFetch(mixed $offset): ?Attr {} + /** @implementation-alias DOMNamedNodeMap::offsetExists */ + public function offsetExists(mixed $offset): bool {} } - class HTMLCollection implements \IteratorAggregate, \Countable + class HTMLCollection implements \IteratorAggregate, \Countable, \DimensionFetchable { /** @readonly */ public int $length; @@ -1281,6 +1319,14 @@ public function count(): int {} /** @implementation-alias DOMNodeList::getIterator */ public function getIterator(): \Iterator {} + + /** @implementation-alias DOMNodeList::offsetGet */ + public function offsetGet(mixed $offset): ?Attr {} + + /** @implementation-alias DOMNodeList::offsetFetch */ + public function &offsetFetch(mixed $offset): ?Attr {} + /** @implementation-alias DOMNodeList::offsetExists */ + public function offsetExists(mixed $offset): bool {} } enum AdjacentPosition : string @@ -1680,7 +1726,7 @@ public function saveXmlFile(string $filename, int $options = 0): int|false {} * @not-serializable * @strict-properties */ - final class TokenList implements IteratorAggregate, Countable + final class TokenList implements IteratorAggregate, Countable, DimensionFetchable { /** @implementation-alias Dom\Node::__construct */ private function __construct() {} @@ -1699,6 +1745,13 @@ public function supports(string $token): bool {} public function count(): int {} public function getIterator(): \Iterator {} + + /** @return string */ + public function offsetGet(mixed $offset): mixed {} + + /** @return string */ + public function &offsetFetch(mixed $offset): mixed {} + public function offsetExists(mixed $offset): bool {} } /** diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index fe42290b5f01a..0f1f4cd69ab6d 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1af73c3b63ebeb5e59948990892dcf6b627a1671 */ + * Stub hash: 63f903cf0d03008a0c85d161b5c80e32e4e894a5 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) @@ -172,6 +172,18 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOMNodeList_item, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMNodeList_offsetGet, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMNodeList_offsetFetch, 1, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMNodeList_offsetExists, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMCharacterData_appendData, 0, 1, IS_TRUE, 0) ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -509,6 +521,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_DOMNamedNodeMap_getIterator arginfo_class_DOMNodeList_getIterator +#define arginfo_class_DOMNamedNodeMap_offsetGet arginfo_class_DOMNodeList_offsetGet + +#define arginfo_class_DOMNamedNodeMap_offsetFetch arginfo_class_DOMNodeList_offsetFetch + +#define arginfo_class_DOMNamedNodeMap_offsetExists arginfo_class_DOMNodeList_offsetExists + #define arginfo_class_DOMEntityReference___construct arginfo_class_DOMDocument_createEntityReference #define arginfo_class_DOMProcessingInstruction___construct arginfo_class_DOMAttr___construct @@ -691,6 +709,16 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_NodeList_item, 0, 1, Do ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_NodeList_offsetGet, 0, 1, Dom\\Attr, 1) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_NodeList_offsetFetch, 1, 1, Dom\\Attr, 1) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Dom_NodeList_offsetExists arginfo_class_DOMNodeList_offsetExists + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_NamedNodeMap_item, 0, 1, Dom\\Attr, 1) ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -708,6 +736,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_Dom_NamedNodeMap_getIterator arginfo_class_DOMNodeList_getIterator +#define arginfo_class_Dom_NamedNodeMap_offsetGet arginfo_class_Dom_NodeList_offsetGet + +#define arginfo_class_Dom_NamedNodeMap_offsetFetch arginfo_class_Dom_NodeList_offsetFetch + +#define arginfo_class_Dom_NamedNodeMap_offsetExists arginfo_class_DOMNodeList_offsetExists + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Dom_DtdNamedNodeMap_item, 0, 1, Dom\\Entity|Dom\\\116otation, MAY_BE_NULL) ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -725,6 +759,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_Dom_DtdNamedNodeMap_getIterator arginfo_class_DOMNodeList_getIterator +#define arginfo_class_Dom_DtdNamedNodeMap_offsetGet arginfo_class_Dom_NodeList_offsetGet + +#define arginfo_class_Dom_DtdNamedNodeMap_offsetFetch arginfo_class_Dom_NodeList_offsetFetch + +#define arginfo_class_Dom_DtdNamedNodeMap_offsetExists arginfo_class_DOMNodeList_offsetExists + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_HTMLCollection_item, 0, 1, Dom\\Element, 1) ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -737,6 +777,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_Dom_HTMLCollection_getIterator arginfo_class_DOMNodeList_getIterator +#define arginfo_class_Dom_HTMLCollection_offsetGet arginfo_class_Dom_NodeList_offsetGet + +#define arginfo_class_Dom_HTMLCollection_offsetFetch arginfo_class_Dom_NodeList_offsetFetch + +#define arginfo_class_Dom_HTMLCollection_offsetExists arginfo_class_DOMNodeList_offsetExists + #define arginfo_class_Dom_Element_hasAttributes arginfo_class_Dom_Node_hasChildNodes #define arginfo_class_Dom_Element_getAttributeNames arginfo_class_DOMNode___sleep @@ -1122,6 +1168,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_Dom_TokenList_getIterator arginfo_class_DOMNodeList_getIterator +#define arginfo_class_Dom_TokenList_offsetGet arginfo_class_DOMNodeList_offsetGet + +#define arginfo_class_Dom_TokenList_offsetFetch arginfo_class_DOMNodeList_offsetFetch + +#define arginfo_class_Dom_TokenList_offsetExists arginfo_class_DOMNodeList_offsetExists + #define arginfo_class_Dom_NamespaceInfo___construct arginfo_class_DOMDocumentFragment___construct #if defined(LIBXML_XPATH_ENABLED) @@ -1207,6 +1259,9 @@ ZEND_METHOD(DOMDocument, replaceChildren); ZEND_METHOD(DOMNodeList, count); ZEND_METHOD(DOMNodeList, getIterator); ZEND_METHOD(DOMNodeList, item); +ZEND_METHOD(DOMNodeList, offsetGet); +ZEND_METHOD(DOMNodeList, offsetFetch); +ZEND_METHOD(DOMNodeList, offsetExists); ZEND_METHOD(DOMCharacterData, appendData); ZEND_METHOD(DOMCharacterData, substringData); ZEND_METHOD(DOMCharacterData, insertData); @@ -1296,6 +1351,9 @@ ZEND_METHOD(DOMNamedNodeMap, getNamedItemNS); ZEND_METHOD(DOMNamedNodeMap, item); ZEND_METHOD(DOMNamedNodeMap, count); ZEND_METHOD(DOMNamedNodeMap, getIterator); +ZEND_METHOD(DOMNamedNodeMap, offsetGet); +ZEND_METHOD(DOMNamedNodeMap, offsetFetch); +ZEND_METHOD(DOMNamedNodeMap, offsetExists); ZEND_METHOD(DOMEntityReference, __construct); ZEND_METHOD(DOMProcessingInstruction, __construct); #if defined(LIBXML_XPATH_ENABLED) @@ -1380,6 +1438,9 @@ ZEND_METHOD(Dom_TokenList, replace); ZEND_METHOD(Dom_TokenList, supports); ZEND_METHOD(Dom_TokenList, count); ZEND_METHOD(Dom_TokenList, getIterator); +ZEND_METHOD(Dom_TokenList, offsetGet); +ZEND_METHOD(Dom_TokenList, offsetFetch); +ZEND_METHOD(Dom_TokenList, offsetExists); #if defined(LIBXML_XPATH_ENABLED) ZEND_METHOD(Dom_XPath, __construct); #endif @@ -1479,6 +1540,9 @@ static const zend_function_entry class_DOMNodeList_methods[] = { ZEND_ME(DOMNodeList, count, arginfo_class_DOMNodeList_count, ZEND_ACC_PUBLIC) ZEND_ME(DOMNodeList, getIterator, arginfo_class_DOMNodeList_getIterator, ZEND_ACC_PUBLIC) ZEND_ME(DOMNodeList, item, arginfo_class_DOMNodeList_item, ZEND_ACC_PUBLIC) + ZEND_ME(DOMNodeList, offsetGet, arginfo_class_DOMNodeList_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(DOMNodeList, offsetFetch, arginfo_class_DOMNodeList_offsetFetch, ZEND_ACC_PUBLIC) + ZEND_ME(DOMNodeList, offsetExists, arginfo_class_DOMNodeList_offsetExists, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -1608,6 +1672,9 @@ static const zend_function_entry class_DOMNamedNodeMap_methods[] = { ZEND_ME(DOMNamedNodeMap, item, arginfo_class_DOMNamedNodeMap_item, ZEND_ACC_PUBLIC) ZEND_ME(DOMNamedNodeMap, count, arginfo_class_DOMNamedNodeMap_count, ZEND_ACC_PUBLIC) ZEND_ME(DOMNamedNodeMap, getIterator, arginfo_class_DOMNamedNodeMap_getIterator, ZEND_ACC_PUBLIC) + ZEND_ME(DOMNamedNodeMap, offsetGet, arginfo_class_DOMNamedNodeMap_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(DOMNamedNodeMap, offsetFetch, arginfo_class_DOMNamedNodeMap_offsetFetch, ZEND_ACC_PUBLIC) + ZEND_ME(DOMNamedNodeMap, offsetExists, arginfo_class_DOMNamedNodeMap_offsetExists, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -1696,6 +1763,9 @@ static const zend_function_entry class_Dom_NodeList_methods[] = { ZEND_RAW_FENTRY("count", zim_DOMNodeList_count, arginfo_class_Dom_NodeList_count, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getIterator", zim_DOMNodeList_getIterator, arginfo_class_Dom_NodeList_getIterator, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("item", zim_DOMNodeList_item, arginfo_class_Dom_NodeList_item, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetGet", zim_DOMNodeList_offsetGet, arginfo_class_Dom_NodeList_offsetGet, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetFetch", zim_DOMNodeList_offsetFetch, arginfo_class_Dom_NodeList_offsetFetch, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetExists", zim_DOMNodeList_offsetExists, arginfo_class_Dom_NodeList_offsetExists, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_FE_END }; @@ -1705,6 +1775,9 @@ static const zend_function_entry class_Dom_NamedNodeMap_methods[] = { ZEND_RAW_FENTRY("getNamedItemNS", zim_DOMNamedNodeMap_getNamedItemNS, arginfo_class_Dom_NamedNodeMap_getNamedItemNS, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("count", zim_DOMNamedNodeMap_count, arginfo_class_Dom_NamedNodeMap_count, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getIterator", zim_DOMNamedNodeMap_getIterator, arginfo_class_Dom_NamedNodeMap_getIterator, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetGet", zim_DOMNamedNodeMap_offsetGet, arginfo_class_Dom_NamedNodeMap_offsetGet, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetFetch", zim_DOMNamedNodeMap_offsetFetch, arginfo_class_Dom_NamedNodeMap_offsetFetch, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetExists", zim_DOMNamedNodeMap_offsetExists, arginfo_class_Dom_NamedNodeMap_offsetExists, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_FE_END }; @@ -1714,6 +1787,9 @@ static const zend_function_entry class_Dom_DtdNamedNodeMap_methods[] = { ZEND_RAW_FENTRY("getNamedItemNS", zim_DOMNamedNodeMap_getNamedItemNS, arginfo_class_Dom_DtdNamedNodeMap_getNamedItemNS, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("count", zim_DOMNamedNodeMap_count, arginfo_class_Dom_DtdNamedNodeMap_count, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getIterator", zim_DOMNamedNodeMap_getIterator, arginfo_class_Dom_DtdNamedNodeMap_getIterator, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetGet", zim_DOMNamedNodeMap_offsetGet, arginfo_class_Dom_DtdNamedNodeMap_offsetGet, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetFetch", zim_DOMNamedNodeMap_offsetFetch, arginfo_class_Dom_DtdNamedNodeMap_offsetFetch, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetExists", zim_DOMNamedNodeMap_offsetExists, arginfo_class_Dom_DtdNamedNodeMap_offsetExists, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_FE_END }; @@ -1722,6 +1798,9 @@ static const zend_function_entry class_Dom_HTMLCollection_methods[] = { ZEND_ME(Dom_HTMLCollection, namedItem, arginfo_class_Dom_HTMLCollection_namedItem, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("count", zim_DOMNodeList_count, arginfo_class_Dom_HTMLCollection_count, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getIterator", zim_DOMNodeList_getIterator, arginfo_class_Dom_HTMLCollection_getIterator, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetGet", zim_DOMNodeList_offsetGet, arginfo_class_Dom_HTMLCollection_offsetGet, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetFetch", zim_DOMNodeList_offsetFetch, arginfo_class_Dom_HTMLCollection_offsetFetch, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetExists", zim_DOMNodeList_offsetExists, arginfo_class_Dom_HTMLCollection_offsetExists, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_FE_END }; @@ -1911,6 +1990,9 @@ static const zend_function_entry class_Dom_TokenList_methods[] = { ZEND_ME(Dom_TokenList, supports, arginfo_class_Dom_TokenList_supports, ZEND_ACC_PUBLIC) ZEND_ME(Dom_TokenList, count, arginfo_class_Dom_TokenList_count, ZEND_ACC_PUBLIC) ZEND_ME(Dom_TokenList, getIterator, arginfo_class_Dom_TokenList_getIterator, ZEND_ACC_PUBLIC) + ZEND_ME(Dom_TokenList, offsetGet, arginfo_class_Dom_TokenList_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(Dom_TokenList, offsetFetch, arginfo_class_Dom_TokenList_offsetFetch, ZEND_ACC_PUBLIC) + ZEND_ME(Dom_TokenList, offsetExists, arginfo_class_Dom_TokenList_offsetExists, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -2362,13 +2444,13 @@ static zend_class_entry *register_class_DOMDocumentFragment(zend_class_entry *cl return class_entry; } -static zend_class_entry *register_class_DOMNodeList(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_DOMNodeList(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_DimensionFetchable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "DOMNodeList", class_DOMNodeList_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); + zend_class_implements(class_entry, 3, class_entry_IteratorAggregate, class_entry_Countable, class_entry_DimensionFetchable); zval property_length_default_value; ZVAL_UNDEF(&property_length_default_value); @@ -2708,13 +2790,13 @@ static zend_class_entry *register_class_DOMText(zend_class_entry *class_entry_DO return class_entry; } -static zend_class_entry *register_class_DOMNamedNodeMap(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_DOMNamedNodeMap(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_DimensionFetchable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "DOMNamedNodeMap", class_DOMNamedNodeMap_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); + zend_class_implements(class_entry, 3, class_entry_IteratorAggregate, class_entry_Countable, class_entry_DimensionFetchable); zval property_length_default_value; ZVAL_UNDEF(&property_length_default_value); @@ -3021,13 +3103,13 @@ static zend_class_entry *register_class_Dom_Node(void) return class_entry; } -static zend_class_entry *register_class_Dom_NodeList(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_Dom_NodeList(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_DimensionFetchable) { zend_class_entry ce, *class_entry; INIT_NS_CLASS_ENTRY(ce, "Dom", "NodeList", class_Dom_NodeList_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); + zend_class_implements(class_entry, 3, class_entry_IteratorAggregate, class_entry_Countable, class_entry_DimensionFetchable); zval property_length_default_value; ZVAL_UNDEF(&property_length_default_value); @@ -3038,13 +3120,13 @@ static zend_class_entry *register_class_Dom_NodeList(zend_class_entry *class_ent return class_entry; } -static zend_class_entry *register_class_Dom_NamedNodeMap(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_Dom_NamedNodeMap(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_DimensionFetchable) { zend_class_entry ce, *class_entry; INIT_NS_CLASS_ENTRY(ce, "Dom", "NamedNodeMap", class_Dom_NamedNodeMap_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); + zend_class_implements(class_entry, 3, class_entry_IteratorAggregate, class_entry_Countable, class_entry_DimensionFetchable); zval property_length_default_value; ZVAL_UNDEF(&property_length_default_value); @@ -3055,13 +3137,13 @@ static zend_class_entry *register_class_Dom_NamedNodeMap(zend_class_entry *class return class_entry; } -static zend_class_entry *register_class_Dom_DtdNamedNodeMap(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_Dom_DtdNamedNodeMap(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_DimensionFetchable) { zend_class_entry ce, *class_entry; INIT_NS_CLASS_ENTRY(ce, "Dom", "DtdNamedNodeMap", class_Dom_DtdNamedNodeMap_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); + zend_class_implements(class_entry, 3, class_entry_IteratorAggregate, class_entry_Countable, class_entry_DimensionFetchable); zval property_length_default_value; ZVAL_UNDEF(&property_length_default_value); @@ -3072,13 +3154,13 @@ static zend_class_entry *register_class_Dom_DtdNamedNodeMap(zend_class_entry *cl return class_entry; } -static zend_class_entry *register_class_Dom_HTMLCollection(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_Dom_HTMLCollection(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_DimensionFetchable) { zend_class_entry ce, *class_entry; INIT_NS_CLASS_ENTRY(ce, "Dom", "HTMLCollection", class_Dom_HTMLCollection_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); + zend_class_implements(class_entry, 3, class_entry_IteratorAggregate, class_entry_Countable, class_entry_DimensionFetchable); zval property_length_default_value; ZVAL_UNDEF(&property_length_default_value); @@ -3664,14 +3746,14 @@ static zend_class_entry *register_class_Dom_XMLDocument(zend_class_entry *class_ return class_entry; } -static zend_class_entry *register_class_Dom_TokenList(zend_class_entry *class_entry_Dom_IteratorAggregate, zend_class_entry *class_entry_Dom_Countable) +static zend_class_entry *register_class_Dom_TokenList(zend_class_entry *class_entry_Dom_IteratorAggregate, zend_class_entry *class_entry_Dom_Countable, zend_class_entry *class_entry_Dom_DimensionFetchable) { zend_class_entry ce, *class_entry; INIT_NS_CLASS_ENTRY(ce, "Dom", "TokenList", class_Dom_TokenList_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; - zend_class_implements(class_entry, 2, class_entry_Dom_IteratorAggregate, class_entry_Dom_Countable); + zend_class_implements(class_entry, 3, class_entry_Dom_IteratorAggregate, class_entry_Dom_Countable, class_entry_Dom_DimensionFetchable); zval property_length_default_value; ZVAL_UNDEF(&property_length_default_value); diff --git a/ext/dom/tests/DOMNamedNodeMap_edge_case_offset.phpt b/ext/dom/tests/DOMNamedNodeMap_edge_case_offset.phpt index 30a961143c77c..fc3c69c9e9d51 100644 --- a/ext/dom/tests/DOMNamedNodeMap_edge_case_offset.phpt +++ b/ext/dom/tests/DOMNamedNodeMap_edge_case_offset.phpt @@ -26,4 +26,4 @@ try { --EXPECT-- int(1) must be between 0 and 2147483647 -Cannot access DOMNamedNodeMap without offset +Cannot fetch append object of type DOMNamedNodeMap diff --git a/ext/dom/tests/bug67949.phpt b/ext/dom/tests/bug67949.phpt index f087633bdfe6f..d62dc5167b183 100644 --- a/ext/dom/tests/bug67949.phpt +++ b/ext/dom/tests/bug67949.phpt @@ -87,7 +87,7 @@ Warning: Attempt to read property "textContent" on null in %s on line %d bool(false) NULL --- testing read_dimension with null offset --- -Cannot access DOMNodeList without offset +Cannot fetch append object of type DOMNodeList --- testing attribute access --- string(4) "href" ==DONE== diff --git a/ext/dom/tests/modern/html/interactions/HTMLCollection_dimension_errors.phpt b/ext/dom/tests/modern/html/interactions/HTMLCollection_dimension_errors.phpt index 571f94e4d3408..0b65e7ab286d9 100644 --- a/ext/dom/tests/modern/html/interactions/HTMLCollection_dimension_errors.phpt +++ b/ext/dom/tests/modern/html/interactions/HTMLCollection_dimension_errors.phpt @@ -27,6 +27,6 @@ try { ?> --EXPECT-- -Cannot append to Dom\HTMLCollection +Cannot fetch append object of type Dom\HTMLCollection Cannot access offset of type bool on Dom\HTMLCollection Cannot access offset of type bool in isset or empty diff --git a/ext/dom/tests/modern/spec/NamedNodeMap_dimensions.phpt b/ext/dom/tests/modern/spec/NamedNodeMap_dimensions.phpt index 38e00defe6223..0e3c177471b80 100644 --- a/ext/dom/tests/modern/spec/NamedNodeMap_dimensions.phpt +++ b/ext/dom/tests/modern/spec/NamedNodeMap_dimensions.phpt @@ -53,6 +53,10 @@ Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on l Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d +Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d + +Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d + Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d string(1) "b" bool(true) diff --git a/ext/dom/tests/modern/spec/NamedNodeMap_dimensions_errors.phpt b/ext/dom/tests/modern/spec/NamedNodeMap_dimensions_errors.phpt index 848b1a2c0b02f..d5a9ffd8d7a3b 100644 --- a/ext/dom/tests/modern/spec/NamedNodeMap_dimensions_errors.phpt +++ b/ext/dom/tests/modern/spec/NamedNodeMap_dimensions_errors.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Cannot append to Dom\NamedNodeMap +Cannot fetch append object of type Dom\NamedNodeMap diff --git a/ext/dom/tests/modern/spec/NodeList_dimensions.phpt b/ext/dom/tests/modern/spec/NodeList_dimensions.phpt index f5fe6480e641d..771c9c4b3e1f0 100644 --- a/ext/dom/tests/modern/spec/NodeList_dimensions.phpt +++ b/ext/dom/tests/modern/spec/NodeList_dimensions.phpt @@ -53,6 +53,10 @@ Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on l Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d +Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d + +Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d + Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d string(1) "b" bool(true) diff --git a/ext/dom/tests/modern/spec/NodeList_dimensions_errors.phpt b/ext/dom/tests/modern/spec/NodeList_dimensions_errors.phpt index bfb882823b787..6bd7613b4fbf2 100644 --- a/ext/dom/tests/modern/spec/NodeList_dimensions_errors.phpt +++ b/ext/dom/tests/modern/spec/NodeList_dimensions_errors.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Cannot append to Dom\NodeList +Cannot fetch append object of type Dom\NodeList diff --git a/ext/dom/tests/modern/token_list/dimensions.phpt b/ext/dom/tests/modern/token_list/dimensions.phpt index e1507d61b08a8..485e224dabefe 100644 --- a/ext/dom/tests/modern/token_list/dimensions.phpt +++ b/ext/dom/tests/modern/token_list/dimensions.phpt @@ -75,6 +75,10 @@ Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on l Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d +Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d + +Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d + Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d string(1) "B" bool(true) diff --git a/ext/dom/tests/modern/token_list/dimensions_error.phpt b/ext/dom/tests/modern/token_list/dimensions_error.phpt index c29fc058ef949..e49fec4f1560f 100644 --- a/ext/dom/tests/modern/token_list/dimensions_error.phpt +++ b/ext/dom/tests/modern/token_list/dimensions_error.phpt @@ -54,4 +54,4 @@ Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d -Cannot append to Dom\TokenList +Cannot fetch append object of type Dom\TokenList diff --git a/ext/dom/token_list.c b/ext/dom/token_list.c index 15eaeb402017d..0617d0844dd4d 100644 --- a/ext/dom/token_list.c +++ b/ext/dom/token_list.c @@ -300,9 +300,6 @@ static zend_long dom_token_list_offset_convert_to_long(zval *offset, bool *faile return 0; case IS_TRUE: return 1; - case IS_REFERENCE: - offset = Z_REFVAL_P(offset); - break; case IS_RESOURCE: zend_use_resource_as_offset(offset); return Z_RES_HANDLE_P(offset); @@ -310,17 +307,12 @@ static zend_long dom_token_list_offset_convert_to_long(zval *offset, bool *faile } } -zval *dom_token_list_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +zval *dom_token_list_read_dimension(zend_object *object, zval *offset, zval *rv) { - if (!offset) { - zend_throw_error(NULL, "Cannot append to Dom\\TokenList"); - return NULL; - } - bool failed; zend_long index = dom_token_list_offset_convert_to_long(offset, &failed); if (UNEXPECTED(failed)) { - zend_illegal_container_offset(object->ce->name, offset, type); + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_R); return NULL; } else { dom_token_list_item_read(php_dom_token_list_from_obj(object), rv, index); @@ -328,28 +320,50 @@ zval *dom_token_list_read_dimension(zend_object *object, zval *offset, int type, } } -int dom_token_list_has_dimension(zend_object *object, zval *offset, int check_empty) +bool dom_token_list_has_dimension(zend_object *object, zval *offset) { bool failed; zend_long index = dom_token_list_offset_convert_to_long(offset, &failed); if (UNEXPECTED(failed)) { zend_illegal_container_offset(object->ce->name, offset, BP_VAR_IS); - return 0; + return false; } else { dom_token_list_object *token_list = php_dom_token_list_from_obj(object); - if (check_empty) { - /* Need to perform an actual read to have the correct empty() semantics. */ - zval rv; - dom_token_list_item_read(token_list, &rv, index); - int is_true = zend_is_true(&rv); - zval_ptr_dtor_nogc(&rv); - return is_true; - } else { - return dom_token_list_item_exists(token_list, index); - } + return dom_token_list_item_exists(token_list, index); } } +PHP_METHOD(Dom_TokenList, offsetGet) +{ + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + dom_token_list_item_read(php_dom_token_list_from_obj(Z_OBJ_P(ZEND_THIS)), return_value, index); +} + +PHP_METHOD(Dom_TokenList, offsetFetch) +{ + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + dom_token_list_item_read(php_dom_token_list_from_obj(Z_OBJ_P(ZEND_THIS)), return_value, index); +} + +PHP_METHOD(Dom_TokenList, offsetExists) +{ + zend_long index; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + dom_token_list_object *token_list = php_dom_token_list_from_obj(Z_OBJ_P(ZEND_THIS)); + RETURN_BOOL(dom_token_list_item_exists(token_list, index)); +} + /* https://dom.spec.whatwg.org/#dom-domtokenlist-length */ zend_result dom_token_list_length_read(dom_object *obj, zval *retval) { diff --git a/ext/dom/token_list.h b/ext/dom/token_list.h index 4711852c4d876..c77549ceb8f18 100644 --- a/ext/dom/token_list.h +++ b/ext/dom/token_list.h @@ -37,8 +37,8 @@ static inline dom_token_list_object *php_dom_token_list_from_dom_obj(dom_object void dom_token_list_ctor(dom_token_list_object *intern, dom_object *element_obj); void dom_token_list_free_obj(zend_object *object); -zval *dom_token_list_read_dimension(zend_object *object, zval *offset, int type, zval *rv); -int dom_token_list_has_dimension(zend_object *object, zval *offset, int check_empty); +zval *dom_token_list_read_dimension(zend_object *object, zval *offset, zval *rv); +bool dom_token_list_has_dimension(zend_object *object, zval *offset); zend_object_iterator *dom_token_list_get_iterator(zend_class_entry *ce, zval *object, int by_ref); #endif diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index d797f5f93f809..b374df96616b8 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -209,6 +209,18 @@ static zend_class_entry *zend_ffi_ctype_ce; static zend_object_handlers zend_ffi_handlers; static zend_object_handlers zend_ffi_cdata_handlers; + +static zval *zend_ffi_cdata_read_dim(zend_object *obj, zval *offset, zval *rv); +static zval *zend_ffi_cdata_fetch_dim(zend_object *obj, zval *offset, zval *rv); +static bool zend_ffi_cdata_has_dim(zend_object *obj, zval *offset); +static void zend_ffi_cdata_write_dim(zend_object *obj, zval *offset, zval *value); +static const zend_class_dimensions_functions zend_ffi_cdata_dimensions_functions = { + .read_dimension = zend_ffi_cdata_read_dim, + .has_dimension = zend_ffi_cdata_has_dim, + .fetch_dimension = zend_ffi_cdata_fetch_dim, + .write_dimension = zend_ffi_cdata_write_dim, +}; + static zend_object_handlers zend_ffi_cdata_value_handlers; static zend_object_handlers zend_ffi_cdata_free_handlers; static zend_object_handlers zend_ffi_ctype_handlers; @@ -1367,7 +1379,9 @@ static zval *zend_ffi_cdata_write_field(zend_object *obj, zend_string *field_nam } /* }}} */ -static zval *zend_ffi_cdata_read_dim(zend_object *obj, zval *offset, int read_type, zval *rv) /* {{{ */ +static ZEND_COLD zend_never_inline void zend_ffi_use_after_free(void); + +static zval *zend_ffi_cdata_read_dim_impl(zend_object *obj, zval *offset, zval *rv, int op_type) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj; zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); @@ -1376,11 +1390,16 @@ static zval *zend_ffi_cdata_read_dim(zend_object *obj, zval *offset, int read_ty void *ptr; zend_ffi_flags is_const; + if (cdata->std.handlers == &zend_ffi_cdata_free_handlers) { + zend_ffi_use_after_free(); + return NULL; + } + if (EXPECTED(type->kind == ZEND_FFI_TYPE_ARRAY)) { if (UNEXPECTED((zend_ulong)(dim) >= (zend_ulong)type->array.length) && (UNEXPECTED(dim < 0) || UNEXPECTED(type->array.length != 0))) { zend_throw_error(zend_ffi_exception_ce, "C array index out of bounds"); - return &EG(uninitialized_zval); + return NULL; } is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST); @@ -1396,7 +1415,7 @@ static zval *zend_ffi_cdata_read_dim(zend_object *obj, zval *offset, int read_ty #if 0 if (UNEXPECTED(!cdata->ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); - return &EG(uninitialized_zval); + return NULL; } #endif ptr = (void*)(((char*)cdata->ptr) + dim_type->size * dim); @@ -1412,19 +1431,38 @@ static zval *zend_ffi_cdata_read_dim(zend_object *obj, zval *offset, int read_ty } if (UNEXPECTED(!cdata->ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); - return &EG(uninitialized_zval); + return NULL; } ptr = (void*)((*(char**)cdata->ptr) + dim_type->size * dim); } else { - zend_throw_error(zend_ffi_exception_ce, "Attempt to read element of non C array"); - return &EG(uninitialized_zval); + /* Same wording as zend_use_object_as_array() */ + zend_throw_error(zend_ffi_exception_ce, "Cannot use object of type FFI\\CData as array"); + return NULL; } - zend_ffi_cdata_to_zval(NULL, ptr, dim_type, read_type, rv, is_const, 0, 0); + zend_ffi_cdata_to_zval(NULL, ptr, dim_type, op_type, rv, is_const, false, 0); return rv; } /* }}} */ +static zval *zend_ffi_cdata_read_dim(zend_object *obj, zval *offset, zval *rv) /* {{{ */ +{ + return zend_ffi_cdata_read_dim_impl(obj, offset, rv, BP_VAR_R); +} + +static zval *zend_ffi_cdata_fetch_dim(zend_object *obj, zval *offset, zval *rv) /* {{{ */ +{ + return zend_ffi_cdata_read_dim_impl(obj, offset, rv, BP_VAR_FETCH); +} +/* }}} */ + +/** TODO This must be properly implemented */ +static bool zend_ffi_cdata_has_dim(zend_object *obj, zval *offset) /* {{{ */ +{ + zend_throw_error(NULL, "Cannot check if an offset exists for object of type %s", ZSTR_VAL(obj->ce->name));; + return false; +} + static void zend_ffi_cdata_write_dim(zend_object *obj, zval *offset, zval *value) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj; @@ -1433,8 +1471,8 @@ static void zend_ffi_cdata_write_dim(zend_object *obj, zval *offset, zval *value void *ptr; zend_ffi_flags is_const; - if (offset == NULL) { - zend_throw_error(zend_ffi_exception_ce, "Cannot add next element to object of type FFI\\CData"); + if (cdata->std.handlers == &zend_ffi_cdata_free_handlers) { + zend_ffi_use_after_free(); return; } @@ -1464,7 +1502,8 @@ static void zend_ffi_cdata_write_dim(zend_object *obj, zval *offset, zval *value } ptr = (void*)((*(char**)cdata->ptr) + type->size * dim); } else { - zend_throw_error(zend_ffi_exception_ce, "Attempt to assign element of non C array"); + /* Same wording as zend_use_object_as_array() */ + zend_throw_error(zend_ffi_exception_ce, "Cannot use object of type FFI\\CData as array"); return; } @@ -5075,32 +5114,6 @@ static ZEND_COLD zend_never_inline void zend_bad_array_access(zend_class_entry * } /* }}} */ -static ZEND_COLD zval *zend_fake_read_dimension(zend_object *obj, zval *offset, int type, zval *rv) /* {{{ */ -{ - zend_bad_array_access(obj->ce); - return NULL; -} -/* }}} */ - -static ZEND_COLD void zend_fake_write_dimension(zend_object *obj, zval *offset, zval *value) /* {{{ */ -{ - zend_bad_array_access(obj->ce); -} -/* }}} */ - -static ZEND_COLD int zend_fake_has_dimension(zend_object *obj, zval *offset, int check_empty) /* {{{ */ -{ - zend_bad_array_access(obj->ce); - return 0; -} -/* }}} */ - -static ZEND_COLD void zend_fake_unset_dimension(zend_object *obj, zval *offset) /* {{{ */ -{ - zend_bad_array_access(obj->ce); -} -/* }}} */ - static ZEND_COLD zend_never_inline void zend_bad_property_access(zend_class_entry *ce) /* {{{ */ { zend_throw_error(NULL, "Cannot access property of object of type %s", ZSTR_VAL(ce->name)); @@ -5186,32 +5199,6 @@ static zend_object *zend_ffi_free_clone_obj(zend_object *obj) /* {{{ */ } /* }}} */ -static ZEND_COLD zval *zend_ffi_free_read_dimension(zend_object *obj, zval *offset, int type, zval *rv) /* {{{ */ -{ - zend_ffi_use_after_free(); - return NULL; -} -/* }}} */ - -static ZEND_COLD void zend_ffi_free_write_dimension(zend_object *obj, zval *offset, zval *value) /* {{{ */ -{ - zend_ffi_use_after_free(); -} -/* }}} */ - -static ZEND_COLD int zend_ffi_free_has_dimension(zend_object *obj, zval *offset, int check_empty) /* {{{ */ -{ - zend_ffi_use_after_free(); - return 0; -} -/* }}} */ - -static ZEND_COLD void zend_ffi_free_unset_dimension(zend_object *obj, zval *offset) /* {{{ */ -{ - zend_ffi_use_after_free(); -} -/* }}} */ - static ZEND_COLD zval *zend_ffi_free_read_property(zend_object *obj, zend_string *member, int type, void **cache_slot, zval *rv) /* {{{ */ { zend_ffi_use_after_free(); @@ -5429,13 +5416,9 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_handlers.clone_obj = NULL; zend_ffi_handlers.read_property = zend_ffi_read_var; zend_ffi_handlers.write_property = zend_ffi_write_var; - zend_ffi_handlers.read_dimension = zend_fake_read_dimension; - zend_ffi_handlers.write_dimension = zend_fake_write_dimension; zend_ffi_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_handlers.has_property = zend_fake_has_property; zend_ffi_handlers.unset_property = zend_fake_unset_property; - zend_ffi_handlers.has_dimension = zend_fake_has_dimension; - zend_ffi_handlers.unset_dimension = zend_fake_unset_dimension; zend_ffi_handlers.get_method = zend_ffi_get_func; zend_ffi_handlers.compare = NULL; zend_ffi_handlers.cast_object = zend_fake_cast_object; @@ -5447,6 +5430,7 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_cdata_ce = register_class_FFI_CData(); zend_ffi_cdata_ce->create_object = zend_ffi_cdata_new; zend_ffi_cdata_ce->default_object_handlers = &zend_ffi_cdata_handlers; + zend_ffi_cdata_ce->dimension_handlers = (zend_class_dimensions_functions*)&zend_ffi_cdata_dimensions_functions; zend_ffi_cdata_ce->get_iterator = zend_ffi_cdata_get_iterator; memcpy(&zend_ffi_cdata_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); @@ -5455,13 +5439,9 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_cdata_handlers.clone_obj = zend_ffi_cdata_clone_obj; zend_ffi_cdata_handlers.read_property = zend_ffi_cdata_read_field; zend_ffi_cdata_handlers.write_property = zend_ffi_cdata_write_field; - zend_ffi_cdata_handlers.read_dimension = zend_ffi_cdata_read_dim; - zend_ffi_cdata_handlers.write_dimension = zend_ffi_cdata_write_dim; zend_ffi_cdata_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_cdata_handlers.has_property = zend_fake_has_property; zend_ffi_cdata_handlers.unset_property = zend_fake_unset_property; - zend_ffi_cdata_handlers.has_dimension = zend_fake_has_dimension; - zend_ffi_cdata_handlers.unset_dimension = zend_fake_unset_dimension; zend_ffi_cdata_handlers.get_method = zend_fake_get_method; zend_ffi_cdata_handlers.get_class_name = zend_ffi_cdata_get_class_name; zend_ffi_cdata_handlers.do_operation = zend_ffi_cdata_do_operation; @@ -5479,13 +5459,9 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_cdata_value_handlers.clone_obj = zend_ffi_cdata_clone_obj; zend_ffi_cdata_value_handlers.read_property = zend_ffi_cdata_get; zend_ffi_cdata_value_handlers.write_property = zend_ffi_cdata_set; - zend_ffi_cdata_value_handlers.read_dimension = zend_fake_read_dimension; - zend_ffi_cdata_value_handlers.write_dimension = zend_fake_write_dimension; zend_ffi_cdata_value_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_cdata_value_handlers.has_property = zend_fake_has_property; zend_ffi_cdata_value_handlers.unset_property = zend_fake_unset_property; - zend_ffi_cdata_value_handlers.has_dimension = zend_fake_has_dimension; - zend_ffi_cdata_value_handlers.unset_dimension = zend_fake_unset_dimension; zend_ffi_cdata_value_handlers.get_method = zend_fake_get_method; zend_ffi_cdata_value_handlers.get_class_name = zend_ffi_cdata_get_class_name; zend_ffi_cdata_value_handlers.compare = zend_ffi_cdata_compare_objects; @@ -5502,13 +5478,9 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_cdata_free_handlers.clone_obj = zend_ffi_free_clone_obj; zend_ffi_cdata_free_handlers.read_property = zend_ffi_free_read_property; zend_ffi_cdata_free_handlers.write_property = zend_ffi_free_write_property; - zend_ffi_cdata_free_handlers.read_dimension = zend_ffi_free_read_dimension; - zend_ffi_cdata_free_handlers.write_dimension = zend_ffi_free_write_dimension; zend_ffi_cdata_free_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_cdata_free_handlers.has_property = zend_ffi_free_has_property; zend_ffi_cdata_free_handlers.unset_property = zend_ffi_free_unset_property; - zend_ffi_cdata_free_handlers.has_dimension = zend_ffi_free_has_dimension; - zend_ffi_cdata_free_handlers.unset_dimension = zend_ffi_free_unset_dimension; zend_ffi_cdata_free_handlers.get_method = zend_fake_get_method; zend_ffi_cdata_free_handlers.get_class_name = zend_ffi_cdata_get_class_name; zend_ffi_cdata_free_handlers.compare = zend_ffi_cdata_compare_objects; @@ -5529,13 +5501,9 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_ctype_handlers.clone_obj = NULL; zend_ffi_ctype_handlers.read_property = zend_fake_read_property; zend_ffi_ctype_handlers.write_property = zend_fake_write_property; - zend_ffi_ctype_handlers.read_dimension = zend_fake_read_dimension; - zend_ffi_ctype_handlers.write_dimension = zend_fake_write_dimension; zend_ffi_ctype_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_ctype_handlers.has_property = zend_fake_has_property; zend_ffi_ctype_handlers.unset_property = zend_fake_unset_property; - zend_ffi_ctype_handlers.has_dimension = zend_fake_has_dimension; - zend_ffi_ctype_handlers.unset_dimension = zend_fake_unset_dimension; //zend_ffi_ctype_handlers.get_method = zend_fake_get_method; zend_ffi_ctype_handlers.get_class_name = zend_ffi_ctype_get_class_name; zend_ffi_ctype_handlers.compare = zend_ffi_ctype_compare_objects; diff --git a/ext/ffi/tests/042.phpt b/ext/ffi/tests/042.phpt index 6e0dd6292ae4c..ec726a4fe7cfa 100644 --- a/ext/ffi/tests/042.phpt +++ b/ext/ffi/tests/042.phpt @@ -10,7 +10,7 @@ $a = FFI::cdef()->new("uint8_t[8]"); $a[] = 0; ?> --EXPECTF-- -Fatal error: Uncaught FFI\Exception: Cannot add next element to object of type FFI\CData in %s:3 +Fatal error: Uncaught Error: Cannot append to object of type FFI\CData in %s:3 Stack trace: #0 {main} thrown in %s on line 3 diff --git a/ext/intl/resourcebundle/resourcebundle.stub.php b/ext/intl/resourcebundle/resourcebundle.stub.php index a768cf91cfe41..f744edd2b23d1 100644 --- a/ext/intl/resourcebundle/resourcebundle.stub.php +++ b/ext/intl/resourcebundle/resourcebundle.stub.php @@ -3,7 +3,7 @@ /** @generate-class-entries */ /** @not-serializable */ -class ResourceBundle implements IteratorAggregate, Countable +class ResourceBundle implements IteratorAggregate, Countable, DimensionReadable { public function __construct(?string $locale, ?string $bundle, bool $fallback = true) {} @@ -16,6 +16,12 @@ public static function create(?string $locale, ?string $bundle, bool $fallback = /** @tentative-return-type */ public function get(string|int $index, bool $fallback = true): ResourceBundle|array|string|int|null {} + /** @param string|int $offset */ + public function offsetGet(mixed $offset): ResourceBundle|array|string|int|null {} + + /** @param string|int $offset */ + public function offsetExists(mixed $offset): bool {} + /** * @tentative-return-type * @alias resourcebundle_count diff --git a/ext/intl/resourcebundle/resourcebundle_arginfo.h b/ext/intl/resourcebundle/resourcebundle_arginfo.h index bfbb3511e62bc..9ceae95c9204d 100644 --- a/ext/intl/resourcebundle/resourcebundle_arginfo.h +++ b/ext/intl/resourcebundle/resourcebundle_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e302e5ca1abcb9b52e3c14abbd38b9e8f1461390 */ + * Stub hash: 123046c3d095458d652a6b7f77ce88752579dbca */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ResourceBundle___construct, 0, 0, 2) ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 1) @@ -18,6 +18,14 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_ResourceBund ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fallback, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_ResourceBundle_offsetGet, 0, 1, ResourceBundle, MAY_BE_ARRAY|MAY_BE_STRING|MAY_BE_LONG|MAY_BE_NULL) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ResourceBundle_offsetExists, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ResourceBundle_count, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -36,6 +44,8 @@ ZEND_END_ARG_INFO() ZEND_METHOD(ResourceBundle, __construct); ZEND_FUNCTION(resourcebundle_create); ZEND_METHOD(ResourceBundle, get); +ZEND_METHOD(ResourceBundle, offsetGet); +ZEND_METHOD(ResourceBundle, offsetExists); ZEND_FUNCTION(resourcebundle_count); ZEND_FUNCTION(resourcebundle_locales); ZEND_FUNCTION(resourcebundle_get_error_code); @@ -46,6 +56,8 @@ static const zend_function_entry class_ResourceBundle_methods[] = { ZEND_ME(ResourceBundle, __construct, arginfo_class_ResourceBundle___construct, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("create", zif_resourcebundle_create, arginfo_class_ResourceBundle_create, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_ME(ResourceBundle, get, arginfo_class_ResourceBundle_get, ZEND_ACC_PUBLIC) + ZEND_ME(ResourceBundle, offsetGet, arginfo_class_ResourceBundle_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(ResourceBundle, offsetExists, arginfo_class_ResourceBundle_offsetExists, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("count", zif_resourcebundle_count, arginfo_class_ResourceBundle_count, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getLocales", zif_resourcebundle_locales, arginfo_class_ResourceBundle_getLocales, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("getErrorCode", zif_resourcebundle_get_error_code, arginfo_class_ResourceBundle_getErrorCode, ZEND_ACC_PUBLIC, NULL, NULL) @@ -54,14 +66,14 @@ static const zend_function_entry class_ResourceBundle_methods[] = { ZEND_FE_END }; -static zend_class_entry *register_class_ResourceBundle(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_ResourceBundle(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_DimensionReadable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "ResourceBundle", class_ResourceBundle_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; - zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); + zend_class_implements(class_entry, 3, class_entry_IteratorAggregate, class_entry_Countable, class_entry_DimensionReadable); return class_entry; } diff --git a/ext/intl/resourcebundle/resourcebundle_class.c b/ext/intl/resourcebundle/resourcebundle_class.c index 92475b0ed08d3..bdca0aa33d5d4 100644 --- a/ext/intl/resourcebundle/resourcebundle_class.c +++ b/ext/intl/resourcebundle/resourcebundle_class.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "php_intl.h" @@ -168,6 +169,43 @@ PHP_FUNCTION( resourcebundle_create ) } /* }}} */ +/* TODO Assume fallback is false or true? */ +static bool resource_bundle_offset_exists(zend_object *object, zend_string *offset_str, zend_long offset_int) +{ + UErrorCode private_code = 0; + ResourceBundle_object *rb = php_intl_resourcebundle_fetch_object(object); + if (offset_str) { + if (UNEXPECTED(ZSTR_LEN(offset_str) == 0)) { + return false; + } + const char *key = ZSTR_VAL(offset_str); + ures_getByKey(rb->me, key, rb->child, &private_code); + } else { + if (UNEXPECTED(offset_int < (zend_long)INT32_MIN || offset_int > (zend_long)INT32_MAX)) { + return false; + } + ures_getByIndex(rb->me, (int32_t)offset_int, rb->child, &private_code); + } + + if (U_FAILURE(private_code)) { + return false; + } else { + return true; + } +} + +static bool resource_bundle_array_has(zend_object *object, zval *offset) +{ + if (Z_TYPE_P(offset) == IS_LONG) { + return resource_bundle_offset_exists(object, /* offset_str */ NULL, Z_LVAL_P(offset)); + } else if (Z_TYPE_P(offset) == IS_STRING) { + return resource_bundle_offset_exists(object, Z_STR_P(offset), /* offset_int */ 0); + } else { + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_IS); + return false; + } +} + /* {{{ resourcebundle_array_fetch */ static zval *resource_bundle_array_fetch( zend_object *object, zend_string *offset_str, zend_long offset_int, @@ -240,20 +278,14 @@ static zval *resource_bundle_array_fetch( /* }}} */ /* {{{ resourcebundle_array_get */ -zval *resourcebundle_array_get(zend_object *object, zval *offset, int type, zval *rv) +static zval *resourcebundle_array_get(zend_object *object, zval *offset, zval *rv) { - if (offset == NULL) { - zend_throw_error(NULL, "Cannot apply [] to ResourceBundle object"); - return NULL; - } - - ZVAL_DEREF(offset); if (Z_TYPE_P(offset) == IS_LONG) { return resource_bundle_array_fetch(object, /* offset_str */ NULL, Z_LVAL_P(offset), rv, /* fallback */ true, /* arg_num */ 0); } else if (Z_TYPE_P(offset) == IS_STRING) { return resource_bundle_array_fetch(object, Z_STR_P(offset), /* offset_int */ 0, rv, /* fallback */ true, /* arg_num */ 0); } else { - zend_illegal_container_offset(object->ce->name, offset, type); + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_R); return NULL; } } @@ -298,7 +330,33 @@ PHP_METHOD(ResourceBundle , get) RETURN_THROWS(); } } -/* }}} */ + +PHP_METHOD(ResourceBundle, offsetGet) +{ + zend_string *offset_str = NULL; + zend_long offset_long = 0; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(offset_str, offset_long) + ZEND_PARSE_PARAMETERS_END(); + + zval *retval = resource_bundle_array_fetch(Z_OBJ_P(ZEND_THIS), offset_str, offset_long, return_value, /* fallback */ true, /* arg_num */ 1); + if (!retval) { + RETURN_THROWS(); + } +} + +PHP_METHOD(ResourceBundle, offsetExists) +{ + zend_string *offset_str = NULL; + zend_long offset_long = 0; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(offset_str, offset_long) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_BOOL(resource_bundle_offset_exists(Z_OBJ_P(ZEND_THIS), offset_str, offset_long)); +} /* {{{ resourcebundle_array_count */ static zend_result resourcebundle_array_count(zend_object *object, zend_long *count) @@ -414,21 +472,26 @@ PHP_METHOD(ResourceBundle, getIterator) { zend_create_internal_iterator_zval(return_value, ZEND_THIS); } +static /* const */ zend_class_dimensions_functions resourcebundle_dimensions_functions = { + .read_dimension = resourcebundle_array_get, + .has_dimension = resource_bundle_array_has, +}; + /* {{{ resourcebundle_register_class * Initialize 'ResourceBundle' class */ void resourcebundle_register_class( void ) { - ResourceBundle_ce_ptr = register_class_ResourceBundle(zend_ce_aggregate, zend_ce_countable); + ResourceBundle_ce_ptr = register_class_ResourceBundle(zend_ce_aggregate, zend_ce_countable, zend_ce_dimension_read); ResourceBundle_ce_ptr->create_object = ResourceBundle_object_create; ResourceBundle_ce_ptr->default_object_handlers = &ResourceBundle_object_handlers; ResourceBundle_ce_ptr->get_iterator = resourcebundle_get_iterator; + ResourceBundle_ce_ptr->dimension_handlers = &resourcebundle_dimensions_functions; ResourceBundle_object_handlers = std_object_handlers; ResourceBundle_object_handlers.offset = XtOffsetOf(ResourceBundle_object, zend); ResourceBundle_object_handlers.clone_obj = NULL; /* ICU ResourceBundle has no clone implementation */ ResourceBundle_object_handlers.free_obj = ResourceBundle_object_free; - ResourceBundle_object_handlers.read_dimension = resourcebundle_array_get; ResourceBundle_object_handlers.count_elements = resourcebundle_array_count; } /* }}} */ diff --git a/ext/intl/tests/resourcebundle_arrayaccess.phpt b/ext/intl/tests/resourcebundle_arrayaccess.phpt index 49ad46fbe3509..cc0ddae04118a 100644 --- a/ext/intl/tests/resourcebundle_arrayaccess.phpt +++ b/ext/intl/tests/resourcebundle_arrayaccess.phpt @@ -4,34 +4,39 @@ Test ResourceBundle array access and count - existing/missing keys intl --FILE-- --EXPECT-- +bool(false) +string(7) "default" length: 6 teststring: Hello World! testint: 2 diff --git a/ext/intl/tests/resourcebundle_dimension_errors.phpt b/ext/intl/tests/resourcebundle_dimension_errors.phpt index 77f30262008ad..98eb4e0cad4b7 100644 --- a/ext/intl/tests/resourcebundle_dimension_errors.phpt +++ b/ext/intl/tests/resourcebundle_dimension_errors.phpt @@ -14,16 +14,6 @@ try { } catch (\Throwable $e) { echo $e::class, ': ', $e->getMessage(), PHP_EOL; } -try { - var_dump(isset($r['non-existent'])); -} catch (\Throwable $e) { - echo $e::class, ': ', $e->getMessage(), PHP_EOL; -} -try { - var_dump($r['non-existent'] ?? "default"); -} catch (\Throwable $e) { - echo $e::class, ': ', $e->getMessage(), PHP_EOL; -} try { var_dump($r[12.5]); } catch (\Throwable $e) { @@ -52,9 +42,7 @@ try { ?> --EXPECT-- -Error: Cannot apply [] to ResourceBundle object -Error: Cannot use object of type ResourceBundle as array -string(7) "default" +Error: Cannot fetch append object of type ResourceBundle TypeError: Cannot access offset of type float on ResourceBundle TypeError: Cannot access offset of type stdClass on ResourceBundle ValueError: Offset cannot be empty diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 9ef79c80b58b2..ec7558cf5b77f 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -17,6 +17,7 @@ */ #include "Zend/zend_API.h" +#include "zend_interfaces_dimension.h" static ZEND_COLD void undef_result_after_exception(void) { const zend_op *opline = EG(opline_before_exception); @@ -1167,6 +1168,42 @@ static void ZEND_FASTCALL zend_jit_fetch_dim_str_is_helper(zend_string *str, zva } } +static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_jit_invalid_use_of_object_as_array(const zend_object *object, bool has_offset, int type) +{ + switch (type) { + case BP_VAR_R: + case BP_VAR_IS: + zend_throw_error(NULL, "Cannot read offset of object of type %s", ZSTR_VAL(object->ce->name)); + break; + case BP_VAR_W: + if (has_offset) { + zend_throw_error(NULL, "Cannot write to offset of object of type %s", ZSTR_VAL(object->ce->name)); + } else { + zend_throw_error(NULL, "Cannot append to object of type %s", ZSTR_VAL(object->ce->name)); + } + break; + case BP_VAR_RW: + zend_throw_error(NULL, "Cannot read-write offset of object of type %s", ZSTR_VAL(object->ce->name)); + break; + case BP_VAR_UNSET: + zend_throw_error(NULL, "Cannot unset offset of object of type %s", ZSTR_VAL(object->ce->name)); + break; + case BP_VAR_FETCH: + if (has_offset) { + zend_throw_error(NULL, "Cannot fetch offset of object of type %s", ZSTR_VAL(object->ce->name)); + } else { + zend_throw_error(NULL, "Cannot fetch append object of type %s", ZSTR_VAL(object->ce->name)); + } + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } +} + +static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_jit_use_object_as_array(const zend_object *object) +{ + zend_throw_error(NULL, "Cannot use object of type %s as array", ZSTR_VAL(object->ce->name)); +} + static void ZEND_FASTCALL zend_jit_fetch_dim_obj_r_helper(zval *container, zval *dim, zval *result) { zval *retval; @@ -1178,17 +1215,31 @@ static void ZEND_FASTCALL zend_jit_fetch_dim_obj_r_helper(zval *container, zval dim = &EG(uninitialized_zval); } - retval = obj->handlers->read_dimension(obj, dim, BP_VAR_R, result); + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(obj->ce->dimension_handlers->read_dimension)) { + ZVAL_DEREF(dim); + retval = obj->ce->dimension_handlers->read_dimension(obj, dim, result); - if (retval) { - if (result != retval) { - ZVAL_COPY_DEREF(result, retval); - } else if (UNEXPECTED(Z_ISREF_P(retval))) { - zend_unwrap_reference(retval); + ZEND_ASSERT(result != NULL); + if (EXPECTED(retval)) { + if (result != retval) { + ZVAL_COPY_DEREF(result, retval); + } else if (UNEXPECTED(Z_ISREF_P(retval))) { + zend_unwrap_reference(result); + } + } else { + ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); + ZVAL_NULL(result); + } + } else { + zend_jit_invalid_use_of_object_as_array(obj, /* has_offset */ true, BP_VAR_R); + ZVAL_NULL(result); } } else { + zend_jit_use_object_as_array(obj); ZVAL_NULL(result); } + if (UNEXPECTED(GC_DELREF(obj) == 0)) { zend_objects_store_del(obj); } @@ -1205,17 +1256,35 @@ static void ZEND_FASTCALL zend_jit_fetch_dim_obj_is_helper(zval *container, zval dim = &EG(uninitialized_zval); } - retval = obj->handlers->read_dimension(obj, dim, BP_VAR_IS, result); + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(obj->ce->dimension_handlers->read_dimension)) { + ZVAL_DEREF(dim); + if (UNEXPECTED(!obj->ce->dimension_handlers->has_dimension(obj, dim))) { + ZVAL_NULL(result); + goto end; + } + retval = obj->ce->dimension_handlers->read_dimension(obj, dim, result); - if (retval) { - if (result != retval) { - ZVAL_COPY_DEREF(result, retval); - } else if (UNEXPECTED(Z_ISREF_P(retval))) { - zend_unwrap_reference(result); + ZEND_ASSERT(result != NULL); + if (EXPECTED(retval)) { + if (result != retval) { + ZVAL_COPY_DEREF(result, retval); + } else if (UNEXPECTED(Z_ISREF_P(retval))) { + zend_unwrap_reference(result); + } + } else { + ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); + ZVAL_NULL(result); + } + } else { + zend_jit_invalid_use_of_object_as_array(obj, /* has_offset */ true, BP_VAR_IS); + ZVAL_NULL(result); } } else { + zend_jit_use_object_as_array(obj); ZVAL_NULL(result); } + end: if (UNEXPECTED(GC_DELREF(obj) == 0)) { zend_objects_store_del(obj); } @@ -1379,32 +1448,63 @@ static zend_always_inline void ZEND_FASTCALL zend_jit_fetch_dim_obj_helper(zval dim = &EG(uninitialized_zval); } - retval = obj->handlers->read_dimension(obj, dim, type, result); - if (UNEXPECTED(retval == &EG(uninitialized_zval))) { - zend_class_entry *ce = obj->ce; - - ZVAL_NULL(result); - zend_error(E_NOTICE, "Indirect modification of overloaded element of %s has no effect", ZSTR_VAL(ce->name)); - } else if (EXPECTED(retval && Z_TYPE_P(retval) != IS_UNDEF)) { - if (!Z_ISREF_P(retval)) { - if (result != retval) { - ZVAL_COPY(result, retval); - retval = result; + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(dim && obj->ce->dimension_handlers->fetch_dimension)) { + ZVAL_DEREF(dim); + /* For null coalesce we first check if the offset exist, + * if it does we call the fetch_dimension() handler, + * otherwise just return an undef result */ + if (UNEXPECTED(type == BP_VAR_IS)) { + ZEND_ASSERT(obj->ce->dimension_handlers->has_dimension); + /* The key does not exist */ + if (!obj->ce->dimension_handlers->has_dimension(obj, dim)) { + ZVAL_UNDEF(result); + goto clean_up; + } } - if (Z_TYPE_P(retval) != IS_OBJECT) { - zend_class_entry *ce = obj->ce; - zend_error(E_NOTICE, "Indirect modification of overloaded element of %s has no effect", ZSTR_VAL(ce->name)); + retval = obj->ce->dimension_handlers->fetch_dimension(obj, dim, result); + } else if (!dim && obj->ce->dimension_handlers->fetch_append) { + retval = obj->ce->dimension_handlers->fetch_append(obj, result); + } else { + zend_jit_invalid_use_of_object_as_array(obj, /* has_offset */ dim, BP_VAR_FETCH); + ZVAL_UNDEF(result); + goto clean_up; + } + if (UNEXPECTED(!retval)) { + ZEND_ASSERT(EG(exception) && "fetch/append_dimension() returned NULL without exception"); + ZVAL_UNDEF(result); + goto clean_up; + } + + if ( + !Z_ISREF_P(retval) + /* Support indirect for: + * $ao[$i] = &$var; + * and + * $ao[] = &$var; + * cases */ + && Z_TYPE_P(retval) != IS_INDIRECT + && Z_TYPE_P(retval) != IS_OBJECT + ) { + zend_class_entry *ce = obj->ce; + + /* BC Layer for ArrayAccess where we do nothing */ + if (UNEXPECTED(instanceof_function(ce, zend_ce_arrayaccess))) { + goto clean_up; } - } else if (UNEXPECTED(Z_REFCOUNT_P(retval) == 1)) { - ZVAL_UNREF(retval); + zend_throw_error(NULL, "%s::%s() must return a reference type", + ZSTR_VAL(ce->name), dim ? "offsetFetch" : "fetchAppend"); + ZVAL_UNDEF(result); + goto clean_up; } if (result != retval) { ZVAL_INDIRECT(result, retval); } } else { - ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); + zend_jit_use_object_as_array(obj); ZVAL_UNDEF(result); } + clean_up: if (UNEXPECTED(GC_DELREF(obj) == 0)) { zend_objects_store_del(obj); } @@ -1493,7 +1593,25 @@ static void ZEND_FASTCALL zend_jit_assign_dim_helper(zval *object_ptr, zval *dim ZVAL_DEREF(value); } - obj->handlers->write_dimension(obj, dim, value); + if (EXPECTED(obj->ce->dimension_handlers)) { + if ( + dim + && obj->ce->dimension_handlers->write_dimension + ) { + ZVAL_DEREF(dim); + obj->ce->dimension_handlers->write_dimension(obj, dim, value); + } else if ( + !dim + && obj->ce->dimension_handlers->append + ) { + obj->ce->dimension_handlers->append(obj, value); + } else { + zend_jit_invalid_use_of_object_as_array(obj, /* has_offset */ dim, BP_VAR_W); + } + } else { + zend_jit_use_object_as_array(obj); + } + if (result) { if (EXPECTED(!EG(exception))) { ZVAL_COPY(result, value); @@ -1567,8 +1685,6 @@ static void ZEND_FASTCALL zend_jit_assign_dim_op_helper(zval *container, zval *d { if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { zend_object *obj = Z_OBJ_P(container); - zval *z; - zval rv, res; GC_ADDREF(obj); if (dim && UNEXPECTED(Z_ISUNDEF_P(dim))) { @@ -1577,21 +1693,44 @@ static void ZEND_FASTCALL zend_jit_assign_dim_op_helper(zval *container, zval *d dim = &EG(uninitialized_zval); } - z = obj->handlers->read_dimension(obj, dim, BP_VAR_R, &rv); - if (z != NULL) { + if (EXPECTED(obj->ce->dimension_handlers)) { + if ( + obj->ce->dimension_handlers->read_dimension + && obj->ce->dimension_handlers->write_dimension + ) { + ZVAL_DEREF(dim); + zval *z; + zval rv, res; + + z = obj->ce->dimension_handlers->read_dimension(obj, dim, &rv); + if (UNEXPECTED(z == NULL)) { + ZEND_ASSERT(EG(exception) && "returned NULL without exception"); + /* Exception is thrown in this case */ + GC_DELREF(obj); + return; + } else { + if (binary_op(&res, Z_ISREF_P(z) ? Z_REFVAL_P(z) : z, value) == SUCCESS) { + obj->ce->dimension_handlers->write_dimension(obj, dim, &res); + } + } - if (binary_op(&res, Z_ISREF_P(z) ? Z_REFVAL_P(z) : z, value) == SUCCESS) { - obj->handlers->write_dimension(obj, dim, &res); - } - if (z == &rv) { - zval_ptr_dtor(&rv); + if (z == &rv) { + zval_ptr_dtor(&rv); + } + zval_ptr_dtor(&res); + } else { + zend_jit_invalid_use_of_object_as_array(obj, /* has_offset */ true, BP_VAR_RW); + /* Exception is thrown in this case */ + GC_DELREF(obj); + return; } - zval_ptr_dtor(&res); } else { + zend_jit_use_object_as_array(obj); /* Exception is thrown in this case */ GC_DELREF(obj); return; } + if (UNEXPECTED(GC_DELREF(obj) == 0)) { zend_objects_store_del(obj); //??? if (retval) { @@ -1724,7 +1863,7 @@ static void ZEND_FASTCALL zend_jit_fast_concat_tmp_helper(zval *result, zval *op ZSTR_VAL(result_str)[result_len] = '\0'; } -static int ZEND_FASTCALL zend_jit_isset_dim_helper(zval *container, zval *offset) +static bool ZEND_FASTCALL zend_jit_isset_dim_helper(zval *container, zval *offset) { if (UNEXPECTED(Z_TYPE_P(offset) == IS_UNDEF)) { zend_jit_undefined_op_helper(EG(current_execute_data)->opline->op2.var); @@ -1732,7 +1871,43 @@ static int ZEND_FASTCALL zend_jit_isset_dim_helper(zval *container, zval *offset } if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { - return Z_OBJ_HT_P(container)->has_dimension(Z_OBJ_P(container), offset, 0); + zend_object *obj = Z_OBJ_P(container); + if (EXPECTED(obj->ce->dimension_handlers)) { + if (EXPECTED(obj->ce->dimension_handlers->has_dimension)) { + ZVAL_DEREF(offset); + + /* Object handler can modify the value of the object via globals; thus take a copy */ + zval copy; + ZVAL_COPY(©, container); + const zend_class_entry *ce = obj->ce; + bool exists = ce->dimension_handlers->has_dimension(Z_OBJ(copy), offset); + if (!exists) { + zval_ptr_dtor(©); + return false; + } + + zval *retval; + zval slot; + retval = ce->dimension_handlers->read_dimension(Z_OBJ(copy), offset, &slot); + + zval_ptr_dtor(©); + if (UNEXPECTED(!retval)) { + ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); + return true; + } + ZEND_ASSERT(Z_TYPE_P(retval) != IS_UNDEF); + /* Check if value is null, if it is we consider it to not be set */ + bool result = Z_TYPE_P(retval) != IS_NULL; + zval_ptr_dtor(retval); + return result; + } else { + zend_jit_invalid_use_of_object_as_array(obj, /* has_offset */ true, BP_VAR_IS); + return false; + } + } else { + zend_jit_use_object_as_array(obj); + return false; + } } else if (EXPECTED(Z_TYPE_P(container) == IS_STRING)) { /* string offsets */ zend_long lval; @@ -1743,7 +1918,7 @@ static int ZEND_FASTCALL zend_jit_isset_dim_helper(zval *container, zval *offset lval += (zend_long)Z_STRLEN_P(container); } if (EXPECTED(lval >= 0) && (size_t)lval < Z_STRLEN_P(container)) { - return 1; + return true; } } else { ZVAL_DEREF(offset); @@ -1755,7 +1930,7 @@ static int ZEND_FASTCALL zend_jit_isset_dim_helper(zval *container, zval *offset } } } - return 0; + return false; } static void ZEND_FASTCALL zend_jit_free_call_frame(zend_execute_data *call) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 84dce9707e209..64729f079ba1a 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -12801,7 +12801,7 @@ static int zend_jit_isset_isempty_dim(zend_jit_ctx *jit, } else { arg2 = jit_ZVAL_ADDR(jit, op2_addr); } - ref = ir_CALL_2(IR_I32, ir_CONST_FC_FUNC(zend_jit_isset_dim_helper), arg1, arg2); + ref = ir_CALL_2(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_isset_dim_helper), arg1, arg2); if_true = ir_IF(ref); ir_IF_TRUE(if_true); ir_refs_add(true_inputs, ir_END()); diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 1a8ae8873c1cf..d9fe0ec9e3462 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -874,13 +874,15 @@ static void zend_file_cache_serialize_class(zval *zv, SERIALIZE_PTR(ce->iterator_funcs_ptr->zf_next); SERIALIZE_PTR(ce->iterator_funcs_ptr); } - - if (ce->arrayaccess_funcs_ptr) { - SERIALIZE_PTR(ce->arrayaccess_funcs_ptr->zf_offsetget); - SERIALIZE_PTR(ce->arrayaccess_funcs_ptr->zf_offsetexists); - SERIALIZE_PTR(ce->arrayaccess_funcs_ptr->zf_offsetset); - SERIALIZE_PTR(ce->arrayaccess_funcs_ptr->zf_offsetunset); - SERIALIZE_PTR(ce->arrayaccess_funcs_ptr); + if (ce->dimension_handlers) { + SERIALIZE_PTR(ce->dimension_handlers->read_dimension); + SERIALIZE_PTR(ce->dimension_handlers->has_dimension); + SERIALIZE_PTR(ce->dimension_handlers->fetch_dimension); + SERIALIZE_PTR(ce->dimension_handlers->write_dimension); + SERIALIZE_PTR(ce->dimension_handlers->append); + SERIALIZE_PTR(ce->dimension_handlers->fetch_append); + SERIALIZE_PTR(ce->dimension_handlers->unset_dimension); + SERIALIZE_PTR(ce->dimension_handlers); } ZEND_MAP_PTR_INIT(ce->static_members_table, NULL); @@ -1701,12 +1703,15 @@ static void zend_file_cache_unserialize_class(zval *zv, UNSERIALIZE_PTR(ce->iterator_funcs_ptr->zf_current); UNSERIALIZE_PTR(ce->iterator_funcs_ptr->zf_next); } - if (ce->arrayaccess_funcs_ptr) { - UNSERIALIZE_PTR(ce->arrayaccess_funcs_ptr); - UNSERIALIZE_PTR(ce->arrayaccess_funcs_ptr->zf_offsetget); - UNSERIALIZE_PTR(ce->arrayaccess_funcs_ptr->zf_offsetexists); - UNSERIALIZE_PTR(ce->arrayaccess_funcs_ptr->zf_offsetset); - UNSERIALIZE_PTR(ce->arrayaccess_funcs_ptr->zf_offsetunset); + if (ce->dimension_handlers) { + UNSERIALIZE_PTR(ce->dimension_handlers); + UNSERIALIZE_PTR(ce->dimension_handlers->read_dimension); + UNSERIALIZE_PTR(ce->dimension_handlers->has_dimension); + UNSERIALIZE_PTR(ce->dimension_handlers->fetch_dimension); + UNSERIALIZE_PTR(ce->dimension_handlers->write_dimension); + UNSERIALIZE_PTR(ce->dimension_handlers->append); + UNSERIALIZE_PTR(ce->dimension_handlers->fetch_append); + UNSERIALIZE_PTR(ce->dimension_handlers->unset_dimension); } if (!(script->corrupted)) { diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 7cc22f9669196..580b5c3025356 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -28,6 +28,7 @@ #include "zend_constants.h" #include "zend_operators.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "zend_attributes.h" #ifdef HAVE_JIT @@ -974,8 +975,8 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) if (ce->iterator_funcs_ptr) { ce->iterator_funcs_ptr = zend_shared_memdup(ce->iterator_funcs_ptr, sizeof(zend_class_iterator_funcs)); } - if (ce->arrayaccess_funcs_ptr) { - ce->arrayaccess_funcs_ptr = zend_shared_memdup(ce->arrayaccess_funcs_ptr, sizeof(zend_class_arrayaccess_funcs)); + if (ce->dimension_handlers) { + ce->dimension_handlers = zend_shared_memdup(ce->dimension_handlers, sizeof(zend_class_dimensions_functions)); } if (ce->ce_flags & ZEND_ACC_CACHED) { @@ -1133,14 +1134,6 @@ void zend_update_parent_ce(zend_class_entry *ce) ce->iterator_funcs_ptr->zf_next = zend_hash_str_find_ptr(&ce->function_table, "next", sizeof("next") - 1); } } - - if (ce->arrayaccess_funcs_ptr) { - ZEND_ASSERT(zend_class_implements_interface(ce, zend_ce_arrayaccess)); - ce->arrayaccess_funcs_ptr->zf_offsetget = zend_hash_str_find_ptr(&ce->function_table, "offsetget", sizeof("offsetget") - 1); - ce->arrayaccess_funcs_ptr->zf_offsetexists = zend_hash_str_find_ptr(&ce->function_table, "offsetexists", sizeof("offsetexists") - 1); - ce->arrayaccess_funcs_ptr->zf_offsetset = zend_hash_str_find_ptr(&ce->function_table, "offsetset", sizeof("offsetset") - 1); - ce->arrayaccess_funcs_ptr->zf_offsetunset = zend_hash_str_find_ptr(&ce->function_table, "offsetunset", sizeof("offsetunset") - 1); - } } /* update methods */ diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 881f3954c04f3..fc74ce7e86dff 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -481,8 +481,8 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) if (ce->iterator_funcs_ptr) { ADD_SIZE(sizeof(zend_class_iterator_funcs)); } - if (ce->arrayaccess_funcs_ptr) { - ADD_SIZE(sizeof(zend_class_arrayaccess_funcs)); + if (ce->dimension_handlers) { + ADD_SIZE(sizeof(zend_class_dimensions_functions)); } if (ce->ce_flags & ZEND_ACC_CACHED) { diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index da3714a8f5465..a33ded9fcd7db 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -31,6 +31,7 @@ #include "php_pdo_int.h" #include "zend_exceptions.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "php_memory_streams.h" #include "pdo_stmt_arginfo.h" @@ -2302,19 +2303,19 @@ static zval *row_prop_read(zend_object *object, zend_string *name, int type, voi return retval; } -static zval *row_dim_read(zend_object *object, zval *offset, int type, zval *rv) +static bool row_has_column_number(const pdo_stmt_t *stmt, zend_long column) { + return column >= 0 && column < stmt->column_count; +} + +static zval *row_dim_read(zend_object *object, zval *offset, zval *rv) { - if (UNEXPECTED(!offset)) { - zend_throw_error(NULL, "Cannot append to PDORow offset"); - return NULL; - } if (Z_TYPE_P(offset) == IS_LONG) { pdo_row_t *row = (pdo_row_t *)object; pdo_stmt_t *stmt = row->stmt; ZEND_ASSERT(stmt); ZVAL_NULL(rv); - if (Z_LVAL_P(offset) >= 0 && Z_LVAL_P(offset) < stmt->column_count) { + if (row_has_column_number(stmt, Z_LVAL_P(offset))) { fetch_value(stmt, rv, Z_LVAL_P(offset), NULL); } return rv; @@ -2323,7 +2324,7 @@ static zval *row_dim_read(zend_object *object, zval *offset, int type, zval *rv) if (!member) { return NULL; } - zval *result = row_prop_read(object, member, type, NULL, rv); + zval *result = row_prop_read(object, member, BP_VAR_R, NULL, rv); zend_string_release_ex(member, false); return result; } @@ -2335,15 +2336,6 @@ static zval *row_prop_write(zend_object *object, zend_string *name, zval *value, return value; } -static void row_dim_write(zend_object *object, zval *member, zval *value) -{ - if (!member) { - zend_throw_error(NULL, "Cannot append to PDORow offset"); - } else { - zend_throw_error(NULL, "Cannot write to PDORow offset"); - } -} - // todo: make row_prop_exists return bool as well static int row_prop_exists(zend_object *object, zend_string *name, int check_empty, void **cache_slot) { @@ -2370,35 +2362,20 @@ static int row_prop_exists(zend_object *object, zend_string *name, int check_emp return res; } - -// todo: make row_dim_exists return bool as well -static int row_dim_exists(zend_object *object, zval *offset, int check_empty) +static bool row_dim_exists(zend_object *object, zval *offset) { if (Z_TYPE_P(offset) == IS_LONG) { pdo_row_t *row = (pdo_row_t *)object; pdo_stmt_t *stmt = row->stmt; ZEND_ASSERT(stmt); - zend_long column = Z_LVAL_P(offset); - if (!check_empty) { - return column >= 0 && column < stmt->column_count; - } - - zval tmp_val; - zval *retval = row_read_column_number(stmt, column, &tmp_val); - if (!retval) { - return false; - } - ZEND_ASSERT(retval == &tmp_val); - bool res = check_empty ? i_zend_is_true(retval) : Z_TYPE(tmp_val) != IS_NULL; - zval_ptr_dtor_nogc(retval); - return res; + return row_has_column_number(stmt, Z_LVAL_P(offset)); } else { zend_string *member = zval_try_get_string(offset); if (!member) { return 0; } - int result = row_prop_exists(object, member, check_empty, NULL); + bool result = row_prop_exists(object, member, /* check_empty */ false, NULL); zend_string_release_ex(member, false); return result; } @@ -2409,11 +2386,6 @@ static void row_prop_delete(zend_object *object, zend_string *offset, void **cac zend_throw_error(NULL, "Cannot unset PDORow property"); } -static void row_dim_delete(zend_object *object, zval *offset) -{ - zend_throw_error(NULL, "Cannot unset PDORow offset"); -} - static HashTable *row_get_properties_for(zend_object *object, zend_prop_purpose purpose) { pdo_row_t *row = (pdo_row_t *)object; @@ -2476,6 +2448,65 @@ zend_object *pdo_row_new(zend_class_entry *ce) return &row->std; } +PHP_METHOD(PDORow, offsetGet) +{ + zend_string *offset_str = NULL; + zend_long offset_long = 0; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(offset_str, offset_long) + ZEND_PARSE_PARAMETERS_END(); + + if (offset_str) { + if (zend_string_equals_literal(offset_str, "queryString")) { + zend_argument_value_error(1, "cannot be \"queryString\""); + RETURN_THROWS(); + } + RETVAL_NULL(); + row_prop_read(Z_OBJ_P(ZEND_THIS), offset_str, /* type */ BP_VAR_R, /* cache_slot */NULL, return_value); + return; + } else { + pdo_row_t *row = (pdo_row_t *)Z_OBJ_P(ZEND_THIS); + pdo_stmt_t *stmt = row->stmt; + ZEND_ASSERT(stmt); + + RETVAL_NULL(); + if (row_has_column_number(stmt, offset_long)) { + fetch_value(stmt, return_value, offset_long, NULL); + } + return; + } +} + +PHP_METHOD(PDORow, offsetExists) +{ + zend_string *offset_str = NULL; + zend_long offset_long = 0; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(offset_str, offset_long) + ZEND_PARSE_PARAMETERS_END(); + + if (offset_str) { + if (zend_string_equals_literal(offset_str, "queryString")) { + zend_argument_value_error(1, "cannot be \"queryString\""); + RETURN_THROWS(); + } + RETURN_BOOL(row_prop_exists(Z_OBJ_P(ZEND_THIS), offset_str, /* check_empty */ false, NULL)); + } else { + pdo_row_t *row = (pdo_row_t *)Z_OBJ_P(ZEND_THIS); + pdo_stmt_t *stmt = row->stmt; + ZEND_ASSERT(stmt); + + RETURN_BOOL(row_has_column_number(stmt, offset_long)); + } +} + +static /* const */ zend_class_dimensions_functions pdo_row_dimensions_functions = { + .read_dimension = row_dim_read, + .has_dimension = row_dim_exists, +}; + void pdo_stmt_init(void) { pdo_dbstmt_ce = register_class_PDOStatement(zend_ce_aggregate); @@ -2492,9 +2523,10 @@ void pdo_stmt_init(void) pdo_dbstmt_object_handlers.compare = zend_objects_not_comparable; pdo_dbstmt_object_handlers.clone_obj = NULL; - pdo_row_ce = register_class_PDORow(); + pdo_row_ce = register_class_PDORow(zend_ce_dimension_read); pdo_row_ce->create_object = pdo_row_new; pdo_row_ce->default_object_handlers = &pdo_row_object_handlers; + pdo_row_ce->dimension_handlers = &pdo_row_dimensions_functions; memcpy(&pdo_row_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); pdo_row_object_handlers.free_obj = pdo_row_free_storage; @@ -2504,10 +2536,6 @@ void pdo_stmt_init(void) pdo_row_object_handlers.write_property = row_prop_write; pdo_row_object_handlers.has_property = row_prop_exists; pdo_row_object_handlers.unset_property = row_prop_delete; - pdo_row_object_handlers.read_dimension = row_dim_read; - pdo_row_object_handlers.write_dimension = row_dim_write; - pdo_row_object_handlers.has_dimension = row_dim_exists; - pdo_row_object_handlers.unset_dimension = row_dim_delete; pdo_row_object_handlers.get_properties_for = row_get_properties_for; pdo_row_object_handlers.get_constructor = row_get_ctor; pdo_row_object_handlers.compare = zend_objects_not_comparable; diff --git a/ext/pdo/pdo_stmt.stub.php b/ext/pdo/pdo_stmt.stub.php index b5783d72684a1..ebec14112d948 100644 --- a/ext/pdo/pdo_stmt.stub.php +++ b/ext/pdo/pdo_stmt.stub.php @@ -68,7 +68,13 @@ public function getIterator(): Iterator {} } /** @not-serializable */ -final class PDORow +final class PDORow implements DimensionReadable { public string $queryString; + + /** @param string|int $offset */ + public function offsetGet(mixed $offset): mixed {} + + /** @param string|int $offset */ + public function offsetExists(mixed $offset): bool {} } diff --git a/ext/pdo/pdo_stmt_arginfo.h b/ext/pdo/pdo_stmt_arginfo.h index d0d90899125f4..d7673abb08c75 100644 --- a/ext/pdo/pdo_stmt_arginfo.h +++ b/ext/pdo/pdo_stmt_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 6a5b332ba4bfeceaca6aad734d38dabb66d82c97 */ + * Stub hash: 5ce85d34205ca72d6a0ea43e8f730a3c1adad987 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDOStatement_bindColumn, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_MASK(0, column, MAY_BE_STRING|MAY_BE_LONG, NULL) @@ -87,6 +87,14 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_PDOStatement_getIterator, 0, 0, Iterator, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_PDORow_offsetGet, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_PDORow_offsetExists, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_METHOD(PDOStatement, bindColumn); ZEND_METHOD(PDOStatement, bindParam); ZEND_METHOD(PDOStatement, bindValue); @@ -107,6 +115,8 @@ ZEND_METHOD(PDOStatement, rowCount); ZEND_METHOD(PDOStatement, setAttribute); ZEND_METHOD(PDOStatement, setFetchMode); ZEND_METHOD(PDOStatement, getIterator); +ZEND_METHOD(PDORow, offsetGet); +ZEND_METHOD(PDORow, offsetExists); static const zend_function_entry class_PDOStatement_methods[] = { ZEND_ME(PDOStatement, bindColumn, arginfo_class_PDOStatement_bindColumn, ZEND_ACC_PUBLIC) @@ -133,6 +143,8 @@ static const zend_function_entry class_PDOStatement_methods[] = { }; static const zend_function_entry class_PDORow_methods[] = { + ZEND_ME(PDORow, offsetGet, arginfo_class_PDORow_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(PDORow, offsetExists, arginfo_class_PDORow_offsetExists, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -154,13 +166,14 @@ static zend_class_entry *register_class_PDOStatement(zend_class_entry *class_ent return class_entry; } -static zend_class_entry *register_class_PDORow(void) +static zend_class_entry *register_class_PDORow(zend_class_entry *class_entry_DimensionReadable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "PDORow", class_PDORow_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NOT_SERIALIZABLE; + zend_class_implements(class_entry, 1, class_entry_DimensionReadable); zval property_queryString_default_value; ZVAL_UNDEF(&property_queryString_default_value); diff --git a/ext/pdo/tests/pdo_035.phpt b/ext/pdo/tests/pdo_035.phpt index 05bf32ba9afb1..6316751876b9c 100644 --- a/ext/pdo/tests/pdo_035.phpt +++ b/ext/pdo/tests/pdo_035.phpt @@ -106,7 +106,7 @@ $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); const TABLE_NAME = 'test_pdo_35_pdo_row'; $db->exec("DROP TABLE " . TABLE_NAME); ?> ---EXPECTF-- +--EXPECT-- object(PDORow)#3 (3) { ["queryString"]=> string(40) "SELECT id, name FROM test_pdo_35_pdo_row" @@ -252,11 +252,10 @@ Read: string(1) "0" string(1) "0" Errors: -Cannot write to PDORow offset -Cannot append to PDORow offset - -Notice: Indirect modification of overloaded element of PDORow has no effect in %s on line %d -Cannot append to PDORow offset -Cannot unset PDORow offset +Cannot write to offset of object of type PDORow +Cannot append to object of type PDORow +Cannot fetch offset of object of type PDORow +Cannot fetch append object of type PDORow +Cannot unset offset of object of type PDORow Cannot write to PDORow property Cannot unset PDORow property diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 9d16512ec5d13..771c63bee6cf0 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -20,6 +20,7 @@ #include "phar_internal.h" #include "func_interceptors.h" #include "phar_object_arginfo.h" +#include "zend_interfaces_dimension.h" static zend_class_entry *phar_ce_archive; static zend_class_entry *phar_ce_data; @@ -3527,6 +3528,10 @@ PHP_METHOD(Phar, offsetExists) } RETURN_TRUE; } else { + /* If the info class is not based on PharFileInfo, directories are not directly instantiable */ + if (UNEXPECTED(!instanceof_function(phar_obj->spl.info_class, phar_ce_entry))) { + RETURN_FALSE; + } if (zend_hash_str_exists(&phar_obj->archive->virtual_dirs, fname, (uint32_t) fname_len)) { RETURN_TRUE; } diff --git a/ext/phar/tests/phar_oo_011.phpt b/ext/phar/tests/phar_oo_011.phpt index 85c7575bcc085..98b4c34bd1539 100644 --- a/ext/phar/tests/phar_oo_011.phpt +++ b/ext/phar/tests/phar_oo_011.phpt @@ -13,10 +13,22 @@ $pharconfig = 0; require_once 'files/phar_oo_test.inc'; $phar = new Phar($fname); -$phar->setInfoClass('SplFileObject'); $phar['hi/f.php'] = 'hi'; var_dump(isset($phar['hi'])); +var_dump($phar['hi']); +var_dump(isset($phar['hi/f.php'])); +echo $phar['hi/f.php']; +echo "\n"; + +$phar->setInfoClass('SplFileObject'); +$phar['hi/f.php'] = 'hi'; +var_dump(isset($phar['hi'])); +try { + var_dump($phar['hi']); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} var_dump(isset($phar['hi/f.php'])); echo $phar['hi/f.php']; echo "\n"; @@ -27,7 +39,17 @@ echo "\n"; unlink(__DIR__ . '/files/phar_oo_011.phar.php'); __halt_compiler(); ?> ---EXPECT-- +--EXPECTF-- +bool(true) +object(PharFileInfo)#%d (2) { + ["pathName":"SplFileInfo":private]=> + string(%d) "phar://%s/phar_oo_011.phar.php/hi" + ["fileName":"SplFileInfo":private]=> + string(2) "hi" +} bool(true) +phar://%s/phar_oo_011.phar.php/hi/f.php +bool(false) +LogicException: Cannot use SplFileObject with directories bool(true) hi diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index d3fee2952c651..58ca60349ed79 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -30,6 +30,7 @@ #include "simplexml_arginfo.h" #include "zend_exceptions.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "ext/spl/spl_iterators.h" PHP_SXE_API zend_class_entry *ce_SimpleXMLIterator; @@ -334,9 +335,16 @@ static zval *sxe_property_read(zend_object *object, zend_string *name, int type, /* }}} */ /* {{{ sxe_dimension_read() */ -static zval *sxe_dimension_read(zend_object *object, zval *offset, int type, zval *rv) +static zval *sxe_dimension_read(zend_object *object, zval *offset, zval *rv) { - return sxe_prop_dim_read(object, offset, 0, 1, type, rv); + return sxe_prop_dim_read(object, offset, 0, 1, BP_VAR_R, rv); +} +/* }}} */ + +/* {{{ sxe_dimension_fetch() */ +static zval *sxe_dimension_fetch(zend_object *object, zval *offset, zval *rv) +{ + return sxe_prop_dim_read(object, offset, 0, 1, BP_VAR_W, rv); } /* }}} */ @@ -607,6 +615,20 @@ static void sxe_dimension_write(zend_object *object, zval *offset, zval *value) } /* }}} */ +/* {{{ sxe_dimension_append() */ +static void sxe_dimension_append(zend_object *object, zval *value) +{ + sxe_prop_dim_write(object, NULL, value, 0, 1, NULL); +} +/* }}} */ + +/* {{{ sxe_dimension_append() */ +static zval *sxe_dimension_fetch_append(zend_object *object, zval *rv) +{ + return sxe_prop_dim_read(object, NULL, 0, 1, BP_VAR_W, rv); +} +/* }}} */ + static zval *sxe_property_get_adr(zend_object *object, zend_string *zname, int fetch_type, void **cache_slot) /* {{{ */ { php_sxe_object *sxe; @@ -760,9 +782,9 @@ static int sxe_property_exists(zend_object *object, zend_string *name, int check /* }}} */ /* {{{ sxe_dimension_exists() */ -static int sxe_dimension_exists(zend_object *object, zval *member, int check_empty) +static bool sxe_dimension_exists(zend_object *object, zval *offset) { - return sxe_prop_dim_exists(object, member, check_empty, 0, 1); + return sxe_prop_dim_exists(object, offset, /* check_empty */ false, 0, 1); } /* }}} */ @@ -2336,6 +2358,76 @@ PHP_METHOD(SimpleXMLElement, __construct) } /* }}} */ +PHP_METHOD(SimpleXMLElement, offsetGet) +{ + zval *offset = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &offset) == FAILURE) { + RETURN_THROWS(); + } + + sxe_dimension_read(Z_OBJ_P(ZEND_THIS), offset, return_value); +} + +PHP_METHOD(SimpleXMLElement, offsetFetch) +{ + zval *offset = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &offset) == FAILURE) { + RETURN_THROWS(); + } + + sxe_dimension_fetch(Z_OBJ_P(ZEND_THIS), offset, return_value); +} + +PHP_METHOD(SimpleXMLElement, offsetExists) +{ + zval *offset = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &offset) == FAILURE) { + RETURN_THROWS(); + } + + RETURN_BOOL(sxe_dimension_exists(Z_OBJ_P(ZEND_THIS), offset)); +} + +PHP_METHOD(SimpleXMLElement, offsetSet) +{ + zval *offset = NULL; + zval *value = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &offset, &value) == FAILURE) { + RETURN_THROWS(); + } + + sxe_dimension_write(Z_OBJ_P(ZEND_THIS), offset, value); +} + +PHP_METHOD(SimpleXMLElement, append) +{ + zval *value = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &value) == FAILURE) { + RETURN_THROWS(); + } + + sxe_dimension_append(Z_OBJ_P(ZEND_THIS), value); +} + +PHP_METHOD(SimpleXMLElement, fetchAppend) +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + sxe_dimension_fetch_append(Z_OBJ_P(ZEND_THIS), return_value); +} + +PHP_METHOD(SimpleXMLElement, offsetUnset) +{ + zval *offset = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &offset) == FAILURE) { + RETURN_THROWS(); + } + + sxe_dimension_delete(Z_OBJ_P(ZEND_THIS), offset); +} + static const zend_object_iterator_funcs php_sxe_iterator_funcs = { /* {{{ */ php_sxe_iterator_dtor, php_sxe_iterator_valid, @@ -2632,13 +2724,32 @@ zend_module_entry simplexml_module_entry = { /* {{{ */ ZEND_GET_MODULE(simplexml) #endif +static /* const */ zend_class_dimensions_functions sxe_dimensions_functions = { + .read_dimension = sxe_dimension_read, + .has_dimension = sxe_dimension_exists, + .fetch_dimension = sxe_dimension_fetch, + .write_dimension = sxe_dimension_write, + .append = sxe_dimension_append, + .fetch_append = sxe_dimension_fetch_append, + .unset_dimension = sxe_dimension_delete +}; + /* {{{ PHP_MINIT_FUNCTION(simplexml) */ PHP_MINIT_FUNCTION(simplexml) { - ce_SimpleXMLElement = register_class_SimpleXMLElement(zend_ce_stringable, zend_ce_countable, spl_ce_RecursiveIterator); + ce_SimpleXMLElement = register_class_SimpleXMLElement( + zend_ce_stringable, + zend_ce_countable, + spl_ce_RecursiveIterator, + zend_ce_dimension_fetch, + zend_ce_dimension_write, + zend_ce_dimension_fetch_append, + zend_ce_dimension_unset + ); ce_SimpleXMLElement->create_object = sxe_object_new; ce_SimpleXMLElement->default_object_handlers = &sxe_object_handlers; ce_SimpleXMLElement->get_iterator = php_sxe_get_iterator; + ce_SimpleXMLElement->dimension_handlers = &sxe_dimensions_functions; memcpy(&sxe_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); sxe_object_handlers.offset = XtOffsetOf(php_sxe_object, zo); @@ -2646,13 +2757,9 @@ PHP_MINIT_FUNCTION(simplexml) sxe_object_handlers.clone_obj = sxe_object_clone; sxe_object_handlers.read_property = sxe_property_read; sxe_object_handlers.write_property = sxe_property_write; - sxe_object_handlers.read_dimension = sxe_dimension_read; - sxe_object_handlers.write_dimension = sxe_dimension_write; sxe_object_handlers.get_property_ptr_ptr = sxe_property_get_adr; sxe_object_handlers.has_property = sxe_property_exists; sxe_object_handlers.unset_property = sxe_property_delete; - sxe_object_handlers.has_dimension = sxe_dimension_exists; - sxe_object_handlers.unset_dimension = sxe_dimension_delete; sxe_object_handlers.get_properties = sxe_get_properties; sxe_object_handlers.compare = sxe_objects_compare; sxe_object_handlers.cast_object = sxe_object_cast; diff --git a/ext/simplexml/simplexml.stub.php b/ext/simplexml/simplexml.stub.php index 2053fec6fdd2e..5a0335f4b5de7 100644 --- a/ext/simplexml/simplexml.stub.php +++ b/ext/simplexml/simplexml.stub.php @@ -9,7 +9,7 @@ function simplexml_load_string(string $data, ?string $class_name = SimpleXMLElem function simplexml_import_dom(object $node, ?string $class_name = SimpleXMLElement::class): ?SimpleXMLElement {} /** @not-serializable */ -class SimpleXMLElement implements Stringable, Countable, RecursiveIterator +class SimpleXMLElement implements Stringable, Countable, RecursiveIterator, DimensionFetchable, DimensionWritable, FetchAppendable, DimensionUnsetable { /** @tentative-return-type */ public function xpath(string $expression): array|null|false {} @@ -74,6 +74,20 @@ public function hasChildren(): bool {} /** @tentative-return-type */ public function getChildren(): ?SimpleXMLElement {} + + public function offsetGet(mixed $offset): mixed {} + + public function &offsetFetch(mixed $offset): mixed {} + + public function offsetExists(mixed $offset): bool {} + + public function offsetSet(mixed $offset, mixed $value): void {} + + public function append(mixed $value): void{} + + public function &fetchAppend(): mixed {} + + public function offsetUnset(mixed $offset): void {} } class SimpleXMLIterator extends SimpleXMLElement diff --git a/ext/simplexml/simplexml_arginfo.h b/ext/simplexml/simplexml_arginfo.h index 285df20813f07..0a840c1b50e90 100644 --- a/ext/simplexml/simplexml_arginfo.h +++ b/ext/simplexml/simplexml_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 36eac2dee86bcc386c24e2cc14caa7bd3d709e82 */ + * Stub hash: 1f47dd95cea7a21b478238ba3f4039b78e66ee11 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_simplexml_load_file, 0, 1, SimpleXMLElement, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -100,6 +100,34 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_SimpleXMLElement_getChildren, 0, 0, SimpleXMLElement, 1) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SimpleXMLElement_offsetGet, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SimpleXMLElement_offsetFetch, 1, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SimpleXMLElement_offsetExists, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SimpleXMLElement_offsetSet, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SimpleXMLElement_append, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SimpleXMLElement_fetchAppend, 1, 0, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SimpleXMLElement_offsetUnset, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_FUNCTION(simplexml_load_file); ZEND_FUNCTION(simplexml_load_string); ZEND_FUNCTION(simplexml_import_dom); @@ -123,6 +151,13 @@ ZEND_METHOD(SimpleXMLElement, key); ZEND_METHOD(SimpleXMLElement, next); ZEND_METHOD(SimpleXMLElement, hasChildren); ZEND_METHOD(SimpleXMLElement, getChildren); +ZEND_METHOD(SimpleXMLElement, offsetGet); +ZEND_METHOD(SimpleXMLElement, offsetFetch); +ZEND_METHOD(SimpleXMLElement, offsetExists); +ZEND_METHOD(SimpleXMLElement, offsetSet); +ZEND_METHOD(SimpleXMLElement, append); +ZEND_METHOD(SimpleXMLElement, fetchAppend); +ZEND_METHOD(SimpleXMLElement, offsetUnset); static const zend_function_entry ext_functions[] = { ZEND_FE(simplexml_load_file, arginfo_simplexml_load_file) @@ -153,6 +188,13 @@ static const zend_function_entry class_SimpleXMLElement_methods[] = { ZEND_ME(SimpleXMLElement, next, arginfo_class_SimpleXMLElement_next, ZEND_ACC_PUBLIC) ZEND_ME(SimpleXMLElement, hasChildren, arginfo_class_SimpleXMLElement_hasChildren, ZEND_ACC_PUBLIC) ZEND_ME(SimpleXMLElement, getChildren, arginfo_class_SimpleXMLElement_getChildren, ZEND_ACC_PUBLIC) + ZEND_ME(SimpleXMLElement, offsetGet, arginfo_class_SimpleXMLElement_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(SimpleXMLElement, offsetFetch, arginfo_class_SimpleXMLElement_offsetFetch, ZEND_ACC_PUBLIC) + ZEND_ME(SimpleXMLElement, offsetExists, arginfo_class_SimpleXMLElement_offsetExists, ZEND_ACC_PUBLIC) + ZEND_ME(SimpleXMLElement, offsetSet, arginfo_class_SimpleXMLElement_offsetSet, ZEND_ACC_PUBLIC) + ZEND_ME(SimpleXMLElement, append, arginfo_class_SimpleXMLElement_append, ZEND_ACC_PUBLIC) + ZEND_ME(SimpleXMLElement, fetchAppend, arginfo_class_SimpleXMLElement_fetchAppend, ZEND_ACC_PUBLIC) + ZEND_ME(SimpleXMLElement, offsetUnset, arginfo_class_SimpleXMLElement_offsetUnset, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -160,14 +202,14 @@ static const zend_function_entry class_SimpleXMLIterator_methods[] = { ZEND_FE_END }; -static zend_class_entry *register_class_SimpleXMLElement(zend_class_entry *class_entry_Stringable, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_RecursiveIterator) +static zend_class_entry *register_class_SimpleXMLElement(zend_class_entry *class_entry_Stringable, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_RecursiveIterator, zend_class_entry *class_entry_DimensionFetchable, zend_class_entry *class_entry_DimensionWritable, zend_class_entry *class_entry_FetchAppendable, zend_class_entry *class_entry_DimensionUnsetable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "SimpleXMLElement", class_SimpleXMLElement_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; - zend_class_implements(class_entry, 3, class_entry_Stringable, class_entry_Countable, class_entry_RecursiveIterator); + zend_class_implements(class_entry, 7, class_entry_Stringable, class_entry_Countable, class_entry_RecursiveIterator, class_entry_DimensionFetchable, class_entry_DimensionWritable, class_entry_FetchAppendable, class_entry_DimensionUnsetable); return class_entry; } diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 9205b523079ea..dbddd652ad359 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -22,6 +22,7 @@ #include "ext/standard/php_var.h" #include "zend_smart_str.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "zend_exceptions.h" #include "spl_iterators.h" @@ -46,9 +47,6 @@ typedef struct _spl_array_object { bool is_child; Bucket *bucket; zend_function *fptr_offset_get; - zend_function *fptr_offset_set; - zend_function *fptr_offset_has; - zend_function *fptr_offset_del; zend_function *fptr_count; zend_class_entry* ce_get_iterator; zend_object std; @@ -199,18 +197,7 @@ static zend_object *spl_array_object_new_ex(zend_class_entry *class_type, zend_o if (intern->fptr_offset_get->common.scope == parent) { intern->fptr_offset_get = NULL; } - intern->fptr_offset_set = zend_hash_str_find_ptr(&class_type->function_table, "offsetset", sizeof("offsetset") - 1); - if (intern->fptr_offset_set->common.scope == parent) { - intern->fptr_offset_set = NULL; - } - intern->fptr_offset_has = zend_hash_str_find_ptr(&class_type->function_table, "offsetexists", sizeof("offsetexists") - 1); - if (intern->fptr_offset_has->common.scope == parent) { - intern->fptr_offset_has = NULL; - } - intern->fptr_offset_del = zend_hash_str_find_ptr(&class_type->function_table, "offsetunset", sizeof("offsetunset") - 1); - if (intern->fptr_offset_del->common.scope == parent) { - intern->fptr_offset_del = NULL; - } + /* Find count() method */ intern->fptr_count = zend_hash_find_ptr(&class_type->function_table, ZSTR_KNOWN(ZEND_STR_COUNT)); if (intern->fptr_count->common.scope == parent) { @@ -257,10 +244,11 @@ static void spl_hash_key_release(spl_hash_key *key) { /* This function does not throw any exceptions for illegal offsets, calls to * zend_illegal_container_offset(); need to be made if the return value is FAILURE */ -static zend_result get_hash_key(spl_hash_key *key, spl_array_object *intern, zval *offset) +static zend_result get_hash_key(spl_hash_key *key, spl_array_object *intern, const zval *offset) { + ZEND_ASSERT(Z_TYPE_P(offset) != IS_REFERENCE); key->release_key = false; -try_again: + switch (Z_TYPE_P(offset)) { case IS_NULL: key->key = ZSTR_EMPTY_ALLOC(); @@ -293,9 +281,6 @@ static zend_result get_hash_key(spl_hash_key *key, spl_array_object *intern, zva key->key = NULL; key->h = Z_LVAL_P(offset); break; - case IS_REFERENCE: - ZVAL_DEREF(offset); - goto try_again; default: return FAILURE; } @@ -307,16 +292,17 @@ static zend_result get_hash_key(spl_hash_key *key, spl_array_object *intern, zva return SUCCESS; } -static zval *spl_array_get_dimension_ptr(bool check_inherited, spl_array_object *intern, const zend_string *ce_name, - zval *offset, int type) /* {{{ */ -{ +static zval *spl_array_get_dimension_ptr( + spl_array_object *intern, + const zend_string *ce_name, + const zval *offset, + int type +) { zval *retval; spl_hash_key key; HashTable *ht = spl_array_get_hash_table(intern); - if (!offset || Z_ISUNDEF_P(offset) || !ht) { - return &EG(uninitialized_zval); - } + ZEND_ASSERT(ht); if ((type == BP_VAR_W || type == BP_VAR_RW) && intern->nApplyCount > 0) { zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited"); @@ -394,59 +380,59 @@ static zval *spl_array_get_dimension_ptr(bool check_inherited, spl_array_object } } return retval; -} /* }}} */ - -static int spl_array_has_dimension(zend_object *object, zval *offset, int check_empty); +} -static zval *spl_array_read_dimension_ex(int check_inherited, zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ +static zval *spl_array_read_dimension_ex(zend_object *object, const zval *offset, int type, zval *rv) /* {{{ */ { spl_array_object *intern = spl_array_from_obj(object); - zval *ret; + zval *ret = spl_array_get_dimension_ptr(intern, object->ce->name, offset, type); - if (check_inherited && - (intern->fptr_offset_get || (type == BP_VAR_IS && intern->fptr_offset_has))) { - if (type == BP_VAR_IS) { - if (!spl_array_has_dimension(object, offset, 0)) { - return &EG(uninitialized_zval); - } - } - - if (intern->fptr_offset_get) { - zval tmp; - if (!offset) { - ZVAL_UNDEF(&tmp); - offset = &tmp; - } - zend_call_method_with_1_params(object, object->ce, &intern->fptr_offset_get, "offsetGet", rv, offset); - - if (!Z_ISUNDEF_P(rv)) { - return rv; - } - return &EG(uninitialized_zval); - } + if (ret == &EG(error_zval) || EG(exception)) { + return NULL; } - ret = spl_array_get_dimension_ptr(check_inherited, intern, object->ce->name, offset, type); - - /* When in a write context, - * ZE has to be fooled into thinking this is in a reference set - * by separating (if necessary) and returning as IS_REFERENCE (with refcount == 1) - */ - - if ((type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) && - !Z_ISREF_P(ret) && - EXPECTED(ret != &EG(uninitialized_zval))) { - ZVAL_NEW_REF(ret, ret); + if (type == BP_VAR_W) { + ZVAL_MAKE_REF(ret); + //ZVAL_COPY(rv, ret); + return ret; + } else { + ZVAL_COPY_DEREF(rv, ret); + return rv; } - return ret; } /* }}} */ -static zval *spl_array_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ +static zval *spl_array_read_dimension(zend_object *object, /* const */ zval *offset, zval *rv) /* {{{ */ { - return spl_array_read_dimension_ex(1, object, offset, type, rv); + return spl_array_read_dimension_ex(object, offset, BP_VAR_R, rv); } /* }}} */ +static zval *spl_array_fetch_dimension(zend_object *object, /* const */ zval *offset, zval *rv) +{ + return spl_array_read_dimension_ex(object, offset, BP_VAR_W, rv); +} + +/* We have a dedicated handler for ArrayObject as it needs to handle constructs like: +$ao = new ArrayObject; +foreach ([1, 2, 3] as $i => $var) +{ + $ao[$i] = &$var; +} +$ao[] = &$var; + * which are normally reserved for arrays */ +static zval *spl_array_object_fetch_dimension(zend_object *object, /* const */ zval *offset, zval *rv) +{ + spl_array_object *intern = spl_array_from_obj(object); + zval *ret = spl_array_get_dimension_ptr(intern, object->ce->name, offset, BP_VAR_W); + + if (ret == &EG(error_zval) || EG(exception)) { + return NULL; + } + + ZVAL_INDIRECT(rv, ret); + return rv; +} + /* * The assertion(HT_ASSERT_RC1(ht)) failed because the refcount was increased manually when intern->is_child is true. * We have to set the refcount to 1 to make assertion success and restore the refcount to the original value after @@ -463,23 +449,11 @@ static uint32_t spl_array_set_refcount(bool is_child, HashTable *ht, uint32_t re return old_refcount; } /* }}} */ -static void spl_array_write_dimension_ex(int check_inherited, zend_object *object, zval *offset, zval *value) /* {{{ */ +static void spl_array_write_dimension(zend_object *object, /* const */ zval *offset, zval *value) /* {{{ */ { spl_array_object *intern = spl_array_from_obj(object); - HashTable *ht; spl_hash_key key; - if (check_inherited && intern->fptr_offset_set) { - zval tmp; - - if (!offset) { - ZVAL_NULL(&tmp); - offset = &tmp; - } - zend_call_method_with_2_params(object, object->ce, &intern->fptr_offset_set, "offsetSet", NULL, offset, value); - return; - } - if (intern->nApplyCount > 0) { zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited"); return; @@ -488,16 +462,7 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec Z_TRY_ADDREF_P(value); uint32_t refcount = 0; - if (!offset || Z_TYPE_P(offset) == IS_NULL) { - ht = spl_array_get_hash_table(intern); - refcount = spl_array_set_refcount(intern->is_child, ht, 1); - zend_hash_next_index_insert(ht, value); - - if (refcount) { - spl_array_set_refcount(intern->is_child, ht, refcount); - } - return; - } + HashTable *ht = spl_array_get_hash_table(intern); if (get_hash_key(&key, intern, offset) == FAILURE) { zend_illegal_container_offset(object->ce->name, offset, BP_VAR_W); @@ -505,7 +470,6 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec return; } - ht = spl_array_get_hash_table(intern); refcount = spl_array_set_refcount(intern->is_child, ht, 1); if (key.key) { zend_hash_update_ind(ht, key.key, value); @@ -519,22 +483,92 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec } } /* }}} */ -static void spl_array_write_dimension(zend_object *object, zval *offset, zval *value) /* {{{ */ +static void spl_array_append(zend_object *object, zval *value) { - spl_array_write_dimension_ex(1, object, offset, value); -} /* }}} */ + spl_array_object *intern = spl_array_from_obj(object); -static void spl_array_unset_dimension_ex(int check_inherited, zend_object *object, zval *offset) /* {{{ */ + if (spl_array_is_object(intern)) { + zend_throw_error(NULL, "Cannot append properties to objects, use %s::offsetSet() instead", ZSTR_VAL(object->ce->name)); + return; + } + if (intern->nApplyCount > 0) { + zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited"); + return; + } + + Z_TRY_ADDREF_P(value); + HashTable *ht = spl_array_get_hash_table(intern); + uint32_t refcount = spl_array_set_refcount(intern->is_child, ht, 1); + zend_hash_next_index_insert(ht, value); + + if (refcount) { + spl_array_set_refcount(intern->is_child, ht, refcount); + } +} + +static zval* spl_array_fetch_append(zend_object *object, zval *rv) { - HashTable *ht; spl_array_object *intern = spl_array_from_obj(object); - spl_hash_key key; - if (check_inherited && intern->fptr_offset_del) { - zend_call_method_with_1_params(object, object->ce, &intern->fptr_offset_del, "offsetUnset", NULL, offset); - return; + if (spl_array_is_object(intern)) { + zend_throw_error(NULL, "Cannot append properties to objects, use %s::offsetSet() instead", ZSTR_VAL(object->ce->name)); + return NULL; + } + + HashTable *ht = spl_array_get_hash_table(intern); + uint32_t refcount = spl_array_set_refcount(intern->is_child, ht, 1); + + zval dummy; + ZVAL_NULL(&dummy); + zval *ret = zend_hash_next_index_insert(ht, &dummy); + + if (refcount) { + spl_array_set_refcount(intern->is_child, ht, refcount); } + ZVAL_MAKE_REF(ret); + ZVAL_COPY(rv, ret); + + return rv; +} + +/* We have a dedicated handler for ArrayObject as it needs to handle constructs like: +$ao = new ArrayObject; +foreach ([1, 2, 3] as $i => $var) +{ + $ao[$i] = &$var; +} +$ao[] = &$var; + * which are normally reserved for arrays */ +static zval *spl_array_object_fetch_append(zend_object *object, zval *rv) +{ + spl_array_object *intern = spl_array_from_obj(object); + + if (spl_array_is_object(intern)) { + zend_throw_error(NULL, "Cannot append properties to objects, use %s::offsetSet() instead", ZSTR_VAL(object->ce->name)); + return NULL; + } + + HashTable *ht = spl_array_get_hash_table(intern); + uint32_t refcount = spl_array_set_refcount(intern->is_child, ht, 1); + + zval dummy; + ZVAL_NULL(&dummy); + zval *ret = zend_hash_next_index_insert(ht, &dummy); + + if (refcount) { + spl_array_set_refcount(intern->is_child, ht, refcount); + } + + ZVAL_INDIRECT(rv, ret); + return rv; +} + +static void spl_array_unset_dimension(zend_object *object, /* const */ zval *offset) /* {{{ */ +{ + spl_array_object *intern = spl_array_from_obj(object); + spl_hash_key key; + if (intern->nApplyCount > 0) { zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited"); return; @@ -545,7 +579,7 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec return; } - ht = spl_array_get_hash_table(intern); + HashTable *ht = spl_array_get_hash_table(intern); uint32_t refcount = spl_array_set_refcount(intern->is_child, ht, 1); if (key.key) { @@ -576,80 +610,27 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec } } /* }}} */ -static void spl_array_unset_dimension(zend_object *object, zval *offset) /* {{{ */ -{ - spl_array_unset_dimension_ex(1, object, offset); -} /* }}} */ - -/* check_empty can take value 0, 1, or 2 - * 0/1 are used as normal boolean, but 2 is used for the case when this function is called from - * the offsetExists() method, in which case it needs to report the offset exist even if the value is null */ -static bool spl_array_has_dimension_ex(bool check_inherited, zend_object *object, zval *offset, int check_empty) /* {{{ */ +static bool spl_array_has_dimension(zend_object *object, /* const */ zval *offset) /* {{{ */ { spl_array_object *intern = spl_array_from_obj(object); - zval rv, *value = NULL, *tmp; - - if (check_inherited && intern->fptr_offset_has) { - zend_call_method_with_1_params(object, object->ce, &intern->fptr_offset_has, "offsetExists", &rv, offset); - - if (!zend_is_true(&rv)) { - zval_ptr_dtor(&rv); - return 0; - } - zval_ptr_dtor(&rv); - - /* For isset calls we don't need to check the value, so return early */ - if (!check_empty) { - return 1; - } else if (intern->fptr_offset_get) { - value = spl_array_read_dimension_ex(1, object, offset, BP_VAR_R, &rv); - } - } - - if (!value) { - HashTable *ht = spl_array_get_hash_table(intern); - spl_hash_key key; - - if (get_hash_key(&key, intern, offset) == FAILURE) { - zend_illegal_container_offset(object->ce->name, offset, BP_VAR_IS); - return 0; - } - - if (key.key) { - tmp = zend_hash_find(ht, key.key); - spl_hash_key_release(&key); - } else { - tmp = zend_hash_index_find(ht, key.h); - } + zval *tmp = NULL; - if (!tmp) { - return 0; - } - - /* check_empty is only equal to 2 if it is called from offsetExists on this class, - * where it needs to report an offset exists even if the value is null */ - if (check_empty == 2) { - return 1; - } + const HashTable *ht = spl_array_get_hash_table(intern); + spl_hash_key key; - if (check_empty && check_inherited && intern->fptr_offset_get) { - value = spl_array_read_dimension_ex(1, object, offset, BP_VAR_R, &rv); - } else { - value = tmp; - } + if (get_hash_key(&key, intern, offset) == FAILURE) { + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_IS); + return false; } - if (value == &rv) { - zval_ptr_dtor(&rv); + if (key.key) { + tmp = zend_hash_find(ht, key.key); + spl_hash_key_release(&key); + } else { + tmp = zend_hash_index_find(ht, key.h); } - /* empty() check the value is not falsy, isset() only check it is not null */ - return check_empty ? zend_is_true(value) : Z_TYPE_P(value) != IS_NULL; -} /* }}} */ - -static int spl_array_has_dimension(zend_object *object, zval *offset, int check_empty) /* {{{ */ -{ - return spl_array_has_dimension_ex(/* check_inherited */ true, object, offset, check_empty); + return tmp != NULL; } /* }}} */ /* {{{ Returns whether the requested $index exists. */ @@ -659,22 +640,40 @@ PHP_METHOD(ArrayObject, offsetExists) if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &index) == FAILURE) { RETURN_THROWS(); } - RETURN_BOOL(spl_array_has_dimension_ex(/* check_inherited */ false, Z_OBJ_P(ZEND_THIS), index, 2)); + RETURN_BOOL(spl_array_has_dimension(Z_OBJ_P(ZEND_THIS), index)); } /* }}} */ /* {{{ Returns the value at the specified $index. */ PHP_METHOD(ArrayObject, offsetGet) { - zval *value, *index; + zval *index; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &index) == FAILURE) { RETURN_THROWS(); } - value = spl_array_read_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index, BP_VAR_R, return_value); - if (value != return_value) { - RETURN_COPY_DEREF(value); + + zval *value = spl_array_read_dimension(Z_OBJ_P(ZEND_THIS), index, return_value); + if (value == NULL) { + RETURN_THROWS(); } } /* }}} */ + +PHP_METHOD(ArrayObject, offsetFetch) +{ + zval *index = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &index) == FAILURE) { + RETURN_THROWS(); + } + + zval *value = spl_array_fetch_dimension(Z_OBJ_P(ZEND_THIS), index, return_value); + if (value == NULL) { + RETURN_THROWS(); + } + ZVAL_COPY(return_value, value); + ZEND_ASSERT(Z_ISREF_P(return_value)); + ZEND_ASSERT(Z_ISREF_P(value)); +} + /* {{{ Sets the value at the specified $index to $newval. */ PHP_METHOD(ArrayObject, offsetSet) { @@ -682,19 +681,14 @@ PHP_METHOD(ArrayObject, offsetSet) if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &index, &value) == FAILURE) { RETURN_THROWS(); } - spl_array_write_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index, value); + spl_array_write_dimension(Z_OBJ_P(ZEND_THIS), index, value); } /* }}} */ + +// TODO Remove to just use spl_array_append() void spl_array_iterator_append(zval *object, zval *append_value) /* {{{ */ { - spl_array_object *intern = Z_SPLARRAY_P(object); - - if (spl_array_is_object(intern)) { - zend_throw_error(NULL, "Cannot append properties to objects, use %s::offsetSet() instead", ZSTR_VAL(Z_OBJCE_P(object)->name)); - return; - } - - spl_array_write_dimension(Z_OBJ_P(object), NULL, append_value); + spl_array_append(Z_OBJ_P(object), append_value); } /* }}} */ /* {{{ Appends the value (cannot be called for objects). */ @@ -705,9 +699,17 @@ PHP_METHOD(ArrayObject, append) if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &value) == FAILURE) { RETURN_THROWS(); } - spl_array_iterator_append(ZEND_THIS, value); + spl_array_append(Z_OBJ_P(ZEND_THIS), value); } /* }}} */ +PHP_METHOD(ArrayObject, fetchAppend) +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + spl_array_fetch_append(Z_OBJ_P(ZEND_THIS), return_value); +} + /* {{{ Unsets the value at the specified $index. */ PHP_METHOD(ArrayObject, offsetUnset) { @@ -715,7 +717,7 @@ PHP_METHOD(ArrayObject, offsetUnset) if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &index) == FAILURE) { RETURN_THROWS(); } - spl_array_unset_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index); + spl_array_unset_dimension(Z_OBJ_P(ZEND_THIS), index); } /* }}} */ /* {{{ Return a copy of the contained array */ @@ -813,7 +815,7 @@ static zval *spl_array_read_property(zend_object *object, zend_string *name, int && !zend_std_has_property(object, name, ZEND_PROPERTY_EXISTS, NULL)) { zval member; ZVAL_STR(&member, name); - return spl_array_read_dimension(object, &member, type, rv); + return spl_array_read_dimension_ex(object, &member, type, rv); } return zend_std_read_property(object, name, type, cache_slot, rv); } /* }}} */ @@ -845,7 +847,8 @@ static zval *spl_array_get_property_ptr_ptr(zend_object *object, zend_string *na return NULL; } ZVAL_STR(&member, name); - return spl_array_get_dimension_ptr(1, intern, object->ce->name, &member, type); + + return spl_array_get_dimension_ptr(intern, object->ce->name, &member, type); } return zend_std_get_property_ptr_ptr(object, name, type, cache_slot); } /* }}} */ @@ -858,7 +861,8 @@ static int spl_array_has_property(zend_object *object, zend_string *name, int ha && !zend_std_has_property(object, name, ZEND_PROPERTY_EXISTS, NULL)) { zval member; ZVAL_STR(&member, name); - return spl_array_has_dimension(object, &member, has_set_exists); + // TODO Need fixing with empty? + return spl_array_has_dimension(object, &member); } return zend_std_has_property(object, name, has_set_exists, cache_slot); } /* }}} */ @@ -1870,22 +1874,46 @@ PHP_METHOD(RecursiveArrayIterator, getChildren) } /* }}} */ +static /* const */ zend_class_dimensions_functions spl_array_object_dimensions_functions = { + .read_dimension = spl_array_read_dimension, + .has_dimension = spl_array_has_dimension, + .fetch_dimension = spl_array_object_fetch_dimension, + .write_dimension = spl_array_write_dimension, + .append = spl_array_append, + .fetch_append = spl_array_object_fetch_append, + .unset_dimension = spl_array_unset_dimension +}; +static /* const */ zend_class_dimensions_functions spl_array_iterator_dimensions_functions = { + .read_dimension = spl_array_read_dimension, + .has_dimension = spl_array_has_dimension, + .fetch_dimension = spl_array_fetch_dimension, + .write_dimension = spl_array_write_dimension, + .append = spl_array_append, + .fetch_append = spl_array_fetch_append, + .unset_dimension = spl_array_unset_dimension +}; + /* {{{ PHP_MINIT_FUNCTION(spl_array) */ PHP_MINIT_FUNCTION(spl_array) { - spl_ce_ArrayObject = register_class_ArrayObject(zend_ce_aggregate, zend_ce_arrayaccess, zend_ce_serializable, zend_ce_countable); + spl_ce_ArrayObject = register_class_ArrayObject( + zend_ce_aggregate, + zend_ce_dimension_fetch, + zend_ce_dimension_write, + zend_ce_dimension_fetch_append, + zend_ce_dimension_unset, + zend_ce_serializable, + zend_ce_countable + ); spl_ce_ArrayObject->create_object = spl_array_object_new; spl_ce_ArrayObject->default_object_handlers = &spl_handler_ArrayObject; + spl_ce_ArrayObject->dimension_handlers = &spl_array_object_dimensions_functions; memcpy(&spl_handler_ArrayObject, &std_object_handlers, sizeof(zend_object_handlers)); spl_handler_ArrayObject.offset = XtOffsetOf(spl_array_object, std); spl_handler_ArrayObject.clone_obj = spl_array_object_clone; - spl_handler_ArrayObject.read_dimension = spl_array_read_dimension; - spl_handler_ArrayObject.write_dimension = spl_array_write_dimension; - spl_handler_ArrayObject.unset_dimension = spl_array_unset_dimension; - spl_handler_ArrayObject.has_dimension = spl_array_has_dimension; spl_handler_ArrayObject.count_elements = spl_array_object_count_elements; spl_handler_ArrayObject.get_properties_for = spl_array_get_properties_for; @@ -1899,14 +1927,24 @@ PHP_MINIT_FUNCTION(spl_array) spl_handler_ArrayObject.compare = spl_array_compare_objects; spl_handler_ArrayObject.free_obj = spl_array_object_free_storage; - spl_ce_ArrayIterator = register_class_ArrayIterator(spl_ce_SeekableIterator, zend_ce_arrayaccess, zend_ce_serializable, zend_ce_countable); + spl_ce_ArrayIterator = register_class_ArrayIterator( + spl_ce_SeekableIterator, + zend_ce_dimension_fetch, + zend_ce_dimension_write, + zend_ce_dimension_fetch_append, + zend_ce_dimension_unset, + zend_ce_serializable, + zend_ce_countable + ); spl_ce_ArrayIterator->create_object = spl_array_object_new; spl_ce_ArrayIterator->default_object_handlers = &spl_handler_ArrayObject; spl_ce_ArrayIterator->get_iterator = spl_array_get_iterator; + spl_ce_ArrayIterator->dimension_handlers = &spl_array_iterator_dimensions_functions; spl_ce_RecursiveArrayIterator = register_class_RecursiveArrayIterator(spl_ce_ArrayIterator, spl_ce_RecursiveIterator); spl_ce_RecursiveArrayIterator->create_object = spl_array_object_new; spl_ce_RecursiveArrayIterator->get_iterator = spl_array_get_iterator; + spl_ce_RecursiveArrayIterator->dimension_handlers = &spl_array_iterator_dimensions_functions; return SUCCESS; } diff --git a/ext/spl/spl_array.stub.php b/ext/spl/spl_array.stub.php index 1f4c81057dcbc..ffb99fb5a8b27 100644 --- a/ext/spl/spl_array.stub.php +++ b/ext/spl/spl_array.stub.php @@ -2,7 +2,7 @@ /** @generate-class-entries */ -class ArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable +class ArrayObject implements IteratorAggregate, DimensionFetchable, DimensionWritable, FetchAppendable, DimensionUnsetable, Serializable, Countable { /** @cvalue SPL_ARRAY_STD_PROP_LIST */ public const int STD_PROP_LIST = UNKNOWN; @@ -17,6 +17,8 @@ public function offsetExists(mixed $key): bool {} /** @tentative-return-type */ public function offsetGet(mixed $key): mixed {} + public function &offsetFetch(mixed $offset): mixed {} + /** @tentative-return-type */ public function offsetSet(mixed $key, mixed $value): void {} @@ -26,6 +28,8 @@ public function offsetUnset(mixed $key): void {} /** @tentative-return-type */ public function append(mixed $value): void {} + public function &fetchAppend(): mixed {} + /** @tentative-return-type */ public function getArrayCopy(): array {} @@ -84,7 +88,7 @@ public function getIteratorClass(): string {} public function __debugInfo(): array {} } -class ArrayIterator implements SeekableIterator, ArrayAccess, Serializable, Countable +class ArrayIterator implements SeekableIterator, DimensionFetchable, DimensionWritable, FetchAppendable, DimensionUnsetable, Serializable, Countable { /** @cvalue SPL_ARRAY_STD_PROP_LIST */ public const int STD_PROP_LIST = UNKNOWN; @@ -105,6 +109,9 @@ public function offsetExists(mixed $key): bool {} */ public function offsetGet(mixed $key): mixed {} + /** @implementation-alias ArrayObject::offsetFetch */ + public function &offsetFetch(mixed $offset): mixed {} + /** * @tentative-return-type * @implementation-alias ArrayObject::offsetSet @@ -123,6 +130,9 @@ public function offsetUnset(mixed $key): void {} */ public function append(mixed $value): void {} + /** @implementation-alias ArrayObject::fetchAppend */ + public function &fetchAppend(): mixed {} + /** * @tentative-return-type * @implementation-alias ArrayObject::getArrayCopy diff --git a/ext/spl/spl_array_arginfo.h b/ext/spl/spl_array_arginfo.h index e846943c3cbfe..4d79dae9a6715 100644 --- a/ext/spl/spl_array_arginfo.h +++ b/ext/spl/spl_array_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c52e89992bd3c04877daab47f4328af0b6ce619e */ + * Stub hash: f063d5aafc6624d9b3e15ac572d2f6c68d205919 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ArrayObject___construct, 0, 0, 0) ZEND_ARG_TYPE_MASK(0, array, MAY_BE_ARRAY|MAY_BE_OBJECT, "[]") @@ -15,6 +15,10 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ArrayObject_offs ZEND_ARG_TYPE_INFO(0, key, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ArrayObject_offsetFetch, 1, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ArrayObject_offsetSet, 0, 2, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, key, IS_MIXED, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -28,6 +32,9 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ArrayObject_appe ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ArrayObject_fetchAppend, 1, 0, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ArrayObject_getArrayCopy, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -94,12 +101,16 @@ ZEND_END_ARG_INFO() #define arginfo_class_ArrayIterator_offsetGet arginfo_class_ArrayObject_offsetGet +#define arginfo_class_ArrayIterator_offsetFetch arginfo_class_ArrayObject_offsetFetch + #define arginfo_class_ArrayIterator_offsetSet arginfo_class_ArrayObject_offsetSet #define arginfo_class_ArrayIterator_offsetUnset arginfo_class_ArrayObject_offsetUnset #define arginfo_class_ArrayIterator_append arginfo_class_ArrayObject_append +#define arginfo_class_ArrayIterator_fetchAppend arginfo_class_ArrayObject_fetchAppend + #define arginfo_class_ArrayIterator_getArrayCopy arginfo_class_ArrayObject_getArrayCopy #define arginfo_class_ArrayIterator_count arginfo_class_ArrayObject_count @@ -156,9 +167,11 @@ ZEND_END_ARG_INFO() ZEND_METHOD(ArrayObject, __construct); ZEND_METHOD(ArrayObject, offsetExists); ZEND_METHOD(ArrayObject, offsetGet); +ZEND_METHOD(ArrayObject, offsetFetch); ZEND_METHOD(ArrayObject, offsetSet); ZEND_METHOD(ArrayObject, offsetUnset); ZEND_METHOD(ArrayObject, append); +ZEND_METHOD(ArrayObject, fetchAppend); ZEND_METHOD(ArrayObject, getArrayCopy); ZEND_METHOD(ArrayObject, count); ZEND_METHOD(ArrayObject, getFlags); @@ -192,9 +205,11 @@ static const zend_function_entry class_ArrayObject_methods[] = { ZEND_ME(ArrayObject, __construct, arginfo_class_ArrayObject___construct, ZEND_ACC_PUBLIC) ZEND_ME(ArrayObject, offsetExists, arginfo_class_ArrayObject_offsetExists, ZEND_ACC_PUBLIC) ZEND_ME(ArrayObject, offsetGet, arginfo_class_ArrayObject_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(ArrayObject, offsetFetch, arginfo_class_ArrayObject_offsetFetch, ZEND_ACC_PUBLIC) ZEND_ME(ArrayObject, offsetSet, arginfo_class_ArrayObject_offsetSet, ZEND_ACC_PUBLIC) ZEND_ME(ArrayObject, offsetUnset, arginfo_class_ArrayObject_offsetUnset, ZEND_ACC_PUBLIC) ZEND_ME(ArrayObject, append, arginfo_class_ArrayObject_append, ZEND_ACC_PUBLIC) + ZEND_ME(ArrayObject, fetchAppend, arginfo_class_ArrayObject_fetchAppend, ZEND_ACC_PUBLIC) ZEND_ME(ArrayObject, getArrayCopy, arginfo_class_ArrayObject_getArrayCopy, ZEND_ACC_PUBLIC) ZEND_ME(ArrayObject, count, arginfo_class_ArrayObject_count, ZEND_ACC_PUBLIC) ZEND_ME(ArrayObject, getFlags, arginfo_class_ArrayObject_getFlags, ZEND_ACC_PUBLIC) @@ -221,9 +236,11 @@ static const zend_function_entry class_ArrayIterator_methods[] = { ZEND_ME(ArrayIterator, __construct, arginfo_class_ArrayIterator___construct, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("offsetExists", zim_ArrayObject_offsetExists, arginfo_class_ArrayIterator_offsetExists, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("offsetGet", zim_ArrayObject_offsetGet, arginfo_class_ArrayIterator_offsetGet, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("offsetFetch", zim_ArrayObject_offsetFetch, arginfo_class_ArrayIterator_offsetFetch, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("offsetSet", zim_ArrayObject_offsetSet, arginfo_class_ArrayIterator_offsetSet, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("offsetUnset", zim_ArrayObject_offsetUnset, arginfo_class_ArrayIterator_offsetUnset, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("append", zim_ArrayObject_append, arginfo_class_ArrayIterator_append, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("fetchAppend", zim_ArrayObject_fetchAppend, arginfo_class_ArrayIterator_fetchAppend, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getArrayCopy", zim_ArrayObject_getArrayCopy, arginfo_class_ArrayIterator_getArrayCopy, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("count", zim_ArrayObject_count, arginfo_class_ArrayIterator_count, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getFlags", zim_ArrayObject_getFlags, arginfo_class_ArrayIterator_getFlags, ZEND_ACC_PUBLIC, NULL, NULL) @@ -254,13 +271,13 @@ static const zend_function_entry class_RecursiveArrayIterator_methods[] = { ZEND_FE_END }; -static zend_class_entry *register_class_ArrayObject(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_ArrayAccess, zend_class_entry *class_entry_Serializable, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_ArrayObject(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_DimensionFetchable, zend_class_entry *class_entry_DimensionWritable, zend_class_entry *class_entry_FetchAppendable, zend_class_entry *class_entry_DimensionUnsetable, zend_class_entry *class_entry_Serializable, zend_class_entry *class_entry_Countable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "ArrayObject", class_ArrayObject_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 4, class_entry_IteratorAggregate, class_entry_ArrayAccess, class_entry_Serializable, class_entry_Countable); + zend_class_implements(class_entry, 7, class_entry_IteratorAggregate, class_entry_DimensionFetchable, class_entry_DimensionWritable, class_entry_FetchAppendable, class_entry_DimensionUnsetable, class_entry_Serializable, class_entry_Countable); zval const_STD_PROP_LIST_value; ZVAL_LONG(&const_STD_PROP_LIST_value, SPL_ARRAY_STD_PROP_LIST); @@ -277,13 +294,13 @@ static zend_class_entry *register_class_ArrayObject(zend_class_entry *class_entr return class_entry; } -static zend_class_entry *register_class_ArrayIterator(zend_class_entry *class_entry_SeekableIterator, zend_class_entry *class_entry_ArrayAccess, zend_class_entry *class_entry_Serializable, zend_class_entry *class_entry_Countable) +static zend_class_entry *register_class_ArrayIterator(zend_class_entry *class_entry_SeekableIterator, zend_class_entry *class_entry_DimensionFetchable, zend_class_entry *class_entry_DimensionWritable, zend_class_entry *class_entry_FetchAppendable, zend_class_entry *class_entry_DimensionUnsetable, zend_class_entry *class_entry_Serializable, zend_class_entry *class_entry_Countable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "ArrayIterator", class_ArrayIterator_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 4, class_entry_SeekableIterator, class_entry_ArrayAccess, class_entry_Serializable, class_entry_Countable); + zend_class_implements(class_entry, 7, class_entry_SeekableIterator, class_entry_DimensionFetchable, class_entry_DimensionWritable, class_entry_FetchAppendable, class_entry_DimensionUnsetable, class_entry_Serializable, class_entry_Countable); zval const_STD_PROP_LIST_value; ZVAL_LONG(&const_STD_PROP_LIST_value, SPL_ARRAY_STD_PROP_LIST); diff --git a/ext/spl/spl_dllist.c b/ext/spl/spl_dllist.c index c73ba47b46049..9bfb8369545f7 100644 --- a/ext/spl/spl_dllist.c +++ b/ext/spl/spl_dllist.c @@ -22,6 +22,7 @@ #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_hash.h" +#include "zend_interfaces_dimension.h" #include "ext/standard/php_var.h" #include "zend_smart_str.h" diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index 368b5e5d10dce..984c0ba84c3bb 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -22,6 +22,7 @@ #include "php.h" #include "zend_interfaces.h" #include "zend_exceptions.h" +#include "zend_interfaces_dimension.h" #include "spl_fixedarray_arginfo.h" #include "spl_fixedarray.h" @@ -31,10 +32,6 @@ static zend_object_handlers spl_handler_SplFixedArray; PHPAPI zend_class_entry *spl_ce_SplFixedArray; -/* Check if the object is an instance of a subclass of SplFixedArray that overrides method's implementation. - * Expect subclassing SplFixedArray to be rare and check that first. */ -#define HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, method) UNEXPECTED((object)->ce != spl_ce_SplFixedArray && (object)->ce->arrayaccess_funcs_ptr->method->common.scope != spl_ce_SplFixedArray) - typedef struct _spl_fixedarray { zend_long size; /* It is possible to resize this, so this can't be combined with the object */ @@ -319,9 +316,9 @@ static zend_object *spl_fixedarray_object_clone(zend_object *old_object) return new_object; } -static zend_long spl_offset_convert_to_long(zval *offset) /* {{{ */ +static zend_long spl_offset_convert_to_long(const zval *offset) /* {{{ */ { - try_again: + ZEND_ASSERT(Z_TYPE_P(offset) != IS_REFERENCE); switch (Z_TYPE_P(offset)) { case IS_STRING: { zend_ulong index; @@ -338,9 +335,6 @@ static zend_long spl_offset_convert_to_long(zval *offset) /* {{{ */ return 0; case IS_TRUE: return 1; - case IS_REFERENCE: - offset = Z_REFVAL_P(offset); - goto try_again; case IS_RESOURCE: zend_use_resource_as_offset(offset); return Z_RES_HANDLE_P(offset); @@ -351,17 +345,10 @@ static zend_long spl_offset_convert_to_long(zval *offset) /* {{{ */ return 0; } -static zval *spl_fixedarray_object_read_dimension_helper(spl_fixedarray_object *intern, zval *offset) +static zval *spl_fixedarray_object_read_dimension_helper(const spl_fixedarray_object *intern, const zval *offset) { zend_long index; - /* we have to return NULL on error here to avoid memleak because of - * ZE duplicating uninitialized_zval_ptr */ - if (!offset) { - zend_throw_error(NULL, "[] operator not supported for SplFixedArray"); - return NULL; - } - index = spl_offset_convert_to_long(offset); if (EG(exception)) { return NULL; @@ -375,42 +362,39 @@ static zval *spl_fixedarray_object_read_dimension_helper(spl_fixedarray_object * } } -static int spl_fixedarray_object_has_dimension(zend_object *object, zval *offset, int check_empty); - -static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +static zval *spl_fixedarray_object_read_dimension(zend_object *object, /* const */ zval *offset, zval *rv) { - if (type == BP_VAR_IS && !spl_fixedarray_object_has_dimension(object, offset, 0)) { - return &EG(uninitialized_zval); - } - - if (HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, zf_offsetget)) { - zval tmp; - if (!offset) { - ZVAL_NULL(&tmp); - offset = &tmp; - } - zend_call_known_instance_method_with_1_params(object->ce->arrayaccess_funcs_ptr->zf_offsetget, object, rv, offset); - if (!Z_ISUNDEF_P(rv)) { - return rv; - } - return &EG(uninitialized_zval); - } - spl_fixedarray_object *intern = spl_fixed_array_from_obj(object); return spl_fixedarray_object_read_dimension_helper(intern, offset); } -static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *intern, zval *offset, zval *value) +static zval *spl_fixedarray_object_fetch_dimension_helper(const spl_fixedarray_object *intern, const zval *offset, zval *rv) { zend_long index; - if (!offset) { - /* '$array[] = value' syntax is not supported */ - zend_throw_error(NULL, "[] operator not supported for SplFixedArray"); - return; + index = spl_offset_convert_to_long(offset); + if (EG(exception)) { + return NULL; } - index = spl_offset_convert_to_long(offset); + if (index < 0 || index >= intern->array.size) { + zend_throw_exception(spl_ce_OutOfBoundsException, "Index invalid or out of range", 0); + return NULL; + } else { + ZVAL_INDIRECT(rv, &intern->array.elements[index]); + return rv; + } +} + +static zval *spl_fixedarray_object_fetch_dimension(zend_object *object, /* const */ zval *offset, zval *rv) +{ + spl_fixedarray_object *intern = spl_fixed_array_from_obj(object); + return spl_fixedarray_object_fetch_dimension_helper(intern, offset, rv); +} + +static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *intern, const zval *offset, zval *value) +{ + zend_long index = spl_offset_convert_to_long(offset); if (EG(exception)) { return; } @@ -428,24 +412,13 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object * } } -static void spl_fixedarray_object_write_dimension(zend_object *object, zval *offset, zval *value) +static void spl_fixedarray_object_write_dimension(zend_object *object, /* const */ zval *offset, zval *value) { - if (HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, zf_offsetset)) { - zval tmp; - - if (!offset) { - ZVAL_NULL(&tmp); - offset = &tmp; - } - zend_call_known_instance_method_with_2_params(object->ce->arrayaccess_funcs_ptr->zf_offsetset, object, NULL, offset, value); - return; - } - spl_fixedarray_object *intern = spl_fixed_array_from_obj(object); spl_fixedarray_object_write_dimension_helper(intern, offset, value); } -static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *intern, zval *offset) +static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *intern, const zval *offset) { zend_long index; @@ -463,18 +436,13 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object * } } -static void spl_fixedarray_object_unset_dimension(zend_object *object, zval *offset) +static void spl_fixedarray_object_unset_dimension(zend_object *object, /* const */ zval *offset) { - if (UNEXPECTED(HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, zf_offsetunset))) { - zend_call_known_instance_method_with_1_params(object->ce->arrayaccess_funcs_ptr->zf_offsetunset, object, NULL, offset); - return; - } - spl_fixedarray_object *intern = spl_fixed_array_from_obj(object); spl_fixedarray_object_unset_dimension_helper(intern, offset); } -static bool spl_fixedarray_object_has_dimension_helper(spl_fixedarray_object *intern, zval *offset, bool check_empty) +static bool spl_fixedarray_object_has_dimension_helper(const spl_fixedarray_object *intern, const zval *offset) { zend_long index; @@ -487,27 +455,14 @@ static bool spl_fixedarray_object_has_dimension_helper(spl_fixedarray_object *in return false; } - if (check_empty) { - return zend_is_true(&intern->array.elements[index]); - } - return Z_TYPE(intern->array.elements[index]) != IS_NULL; } -static int spl_fixedarray_object_has_dimension(zend_object *object, zval *offset, int check_empty) +static bool spl_fixedarray_object_has_dimension(zend_object *object, /* const */ zval *offset) { - if (HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, zf_offsetexists)) { - zval rv; - - zend_call_known_instance_method_with_1_params(object->ce->arrayaccess_funcs_ptr->zf_offsetexists, object, &rv, offset); - bool result = zend_is_true(&rv); - zval_ptr_dtor(&rv); - return result; - } - spl_fixedarray_object *intern = spl_fixed_array_from_obj(object); - return spl_fixedarray_object_has_dimension_helper(intern, offset, check_empty); + return spl_fixedarray_object_has_dimension_helper(intern, offset); } static zend_result spl_fixedarray_object_count_elements(zend_object *object, zend_long *count) @@ -802,7 +757,7 @@ PHP_METHOD(SplFixedArray, offsetExists) intern = Z_SPLFIXEDARRAY_P(ZEND_THIS); - RETURN_BOOL(spl_fixedarray_object_has_dimension_helper(intern, zindex, 0)); + RETURN_BOOL(spl_fixedarray_object_has_dimension_helper(intern, zindex)); } /* Returns the value at the specified $index. */ @@ -825,6 +780,21 @@ PHP_METHOD(SplFixedArray, offsetGet) } } +/* Returns the value at the specified $index. */ +PHP_METHOD(SplFixedArray, offsetFetch) +{ + zval *offset; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &offset) == FAILURE) { + RETURN_THROWS(); + } + + zval *retval = spl_fixedarray_object_fetch_dimension_helper(Z_SPLFIXEDARRAY_P(ZEND_THIS), offset, return_value); + if (!retval) { + RETURN_THROWS(); + } +} + /* Sets the value at the specified $index to $newval. */ PHP_METHOD(SplFixedArray, offsetSet) { @@ -955,22 +925,33 @@ static zend_object_iterator *spl_fixedarray_get_iterator(zend_class_entry *ce, z return &iterator->intern; } +static /* const */ zend_class_dimensions_functions spl_fixed_array_dimensions_functions = { + .read_dimension = spl_fixedarray_object_read_dimension, + .has_dimension = spl_fixedarray_object_has_dimension, + .fetch_dimension = spl_fixedarray_object_fetch_dimension, + .write_dimension = spl_fixedarray_object_write_dimension, + .unset_dimension = spl_fixedarray_object_unset_dimension +}; + PHP_MINIT_FUNCTION(spl_fixedarray) { spl_ce_SplFixedArray = register_class_SplFixedArray( - zend_ce_aggregate, zend_ce_arrayaccess, zend_ce_countable, php_json_serializable_ce); + zend_ce_aggregate, + zend_ce_dimension_fetch, + zend_ce_dimension_write, + zend_ce_dimension_unset, + zend_ce_countable, + php_json_serializable_ce + ); spl_ce_SplFixedArray->create_object = spl_fixedarray_new; spl_ce_SplFixedArray->default_object_handlers = &spl_handler_SplFixedArray; spl_ce_SplFixedArray->get_iterator = spl_fixedarray_get_iterator; + spl_ce_SplFixedArray->dimension_handlers = &spl_fixed_array_dimensions_functions; memcpy(&spl_handler_SplFixedArray, &std_object_handlers, sizeof(zend_object_handlers)); spl_handler_SplFixedArray.offset = XtOffsetOf(spl_fixedarray_object, std); spl_handler_SplFixedArray.clone_obj = spl_fixedarray_object_clone; - spl_handler_SplFixedArray.read_dimension = spl_fixedarray_object_read_dimension; - spl_handler_SplFixedArray.write_dimension = spl_fixedarray_object_write_dimension; - spl_handler_SplFixedArray.unset_dimension = spl_fixedarray_object_unset_dimension; - spl_handler_SplFixedArray.has_dimension = spl_fixedarray_object_has_dimension; spl_handler_SplFixedArray.count_elements = spl_fixedarray_object_count_elements; spl_handler_SplFixedArray.get_properties_for = spl_fixedarray_object_get_properties_for; spl_handler_SplFixedArray.get_gc = spl_fixedarray_object_get_gc; diff --git a/ext/spl/spl_fixedarray.stub.php b/ext/spl/spl_fixedarray.stub.php index e4b8fdce065bf..ebd51a2ff61f3 100644 --- a/ext/spl/spl_fixedarray.stub.php +++ b/ext/spl/spl_fixedarray.stub.php @@ -2,7 +2,7 @@ /** @generate-class-entries */ -class SplFixedArray implements IteratorAggregate, ArrayAccess, Countable, JsonSerializable +class SplFixedArray implements IteratorAggregate, DimensionFetchable, DimensionWritable, DimensionUnsetable, Countable, JsonSerializable { public function __construct(int $size = 0) {} @@ -40,6 +40,9 @@ public function offsetExists($index): bool {} */ public function offsetGet($index): mixed {} + /** @param int $index */ + public function &offsetFetch(mixed $index): mixed {} + /** * @param int $index * @tentative-return-type diff --git a/ext/spl/spl_fixedarray_arginfo.h b/ext/spl/spl_fixedarray_arginfo.h index ece12b998b0c6..78e83ec00b433 100644 --- a/ext/spl/spl_fixedarray_arginfo.h +++ b/ext/spl/spl_fixedarray_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 35a9585a433b9e3d57263c07ccafd1b6edd2f10b */ + * Stub hash: e55e574f53b9f222fe6637166436bd8965af6ce5 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFixedArray___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, size, IS_LONG, 0, "0") @@ -40,6 +40,10 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplFixedArray_of ZEND_ARG_INFO(0, index) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SplFixedArray_offsetFetch, 1, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, index, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplFixedArray_offsetSet, 0, 2, IS_VOID, 0) ZEND_ARG_INFO(0, index) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -65,6 +69,7 @@ ZEND_METHOD(SplFixedArray, getSize); ZEND_METHOD(SplFixedArray, setSize); ZEND_METHOD(SplFixedArray, offsetExists); ZEND_METHOD(SplFixedArray, offsetGet); +ZEND_METHOD(SplFixedArray, offsetFetch); ZEND_METHOD(SplFixedArray, offsetSet); ZEND_METHOD(SplFixedArray, offsetUnset); ZEND_METHOD(SplFixedArray, getIterator); @@ -82,6 +87,7 @@ static const zend_function_entry class_SplFixedArray_methods[] = { ZEND_ME(SplFixedArray, setSize, arginfo_class_SplFixedArray_setSize, ZEND_ACC_PUBLIC) ZEND_ME(SplFixedArray, offsetExists, arginfo_class_SplFixedArray_offsetExists, ZEND_ACC_PUBLIC) ZEND_ME(SplFixedArray, offsetGet, arginfo_class_SplFixedArray_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(SplFixedArray, offsetFetch, arginfo_class_SplFixedArray_offsetFetch, ZEND_ACC_PUBLIC) ZEND_ME(SplFixedArray, offsetSet, arginfo_class_SplFixedArray_offsetSet, ZEND_ACC_PUBLIC) ZEND_ME(SplFixedArray, offsetUnset, arginfo_class_SplFixedArray_offsetUnset, ZEND_ACC_PUBLIC) ZEND_ME(SplFixedArray, getIterator, arginfo_class_SplFixedArray_getIterator, ZEND_ACC_PUBLIC) @@ -89,13 +95,13 @@ static const zend_function_entry class_SplFixedArray_methods[] = { ZEND_FE_END }; -static zend_class_entry *register_class_SplFixedArray(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_ArrayAccess, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_JsonSerializable) +static zend_class_entry *register_class_SplFixedArray(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_DimensionFetchable, zend_class_entry *class_entry_DimensionWritable, zend_class_entry *class_entry_DimensionUnsetable, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_JsonSerializable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "SplFixedArray", class_SplFixedArray_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 4, class_entry_IteratorAggregate, class_entry_ArrayAccess, class_entry_Countable, class_entry_JsonSerializable); + zend_class_implements(class_entry, 6, class_entry_IteratorAggregate, class_entry_DimensionFetchable, class_entry_DimensionWritable, class_entry_DimensionUnsetable, class_entry_Countable, class_entry_JsonSerializable); return class_entry; } diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c index 349410ed9feba..8118ced84476a 100644 --- a/ext/spl/spl_iterators.c +++ b/ext/spl/spl_iterators.c @@ -21,6 +21,7 @@ #include "php.h" #include "zend_exceptions.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "ext/pcre/php_pcre.h" #include "spl_iterators.h" diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index 5f5fbca70d4f3..828cbe744f03f 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -24,6 +24,7 @@ #include "ext/standard/php_var.h" #include "zend_smart_str.h" #include "zend_interfaces.h" +#include "zend_interfaces_dimension.h" #include "zend_exceptions.h" #include "php_spl.h" /* For php_spl_object_hash() */ @@ -40,10 +41,9 @@ PHPAPI zend_class_entry *spl_ce_MultipleIterator; static zend_object_handlers spl_handler_SplObjectStorage; -/* Bit flags for marking internal functionality overridden by SplObjectStorage subclasses. */ -#define SOS_OVERRIDDEN_READ_DIMENSION 1 -#define SOS_OVERRIDDEN_WRITE_DIMENSION 2 -#define SOS_OVERRIDDEN_UNSET_DIMENSION 4 +/* Stubs for dimension handler to be able to compare them */ +static void spl_object_storage_write_dimension(zend_object *object, zval *offset, zval *inf); +static void spl_object_storage_unset_dimension(zend_object *object, zval *offset); typedef struct _spl_SplObjectStorage { /* {{{ */ HashTable storage; @@ -150,7 +150,6 @@ static spl_SplObjectStorageElement *spl_object_storage_attach_handle(spl_SplObje uint32_t handle = obj->handle; zval *entry_zv = zend_hash_index_lookup(&intern->storage, handle); spl_SplObjectStorageElement *pelement; - ZEND_ASSERT(!(intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION)); if (Z_TYPE_P(entry_zv) != IS_NULL) { zval zv_inf; @@ -176,7 +175,11 @@ static spl_SplObjectStorageElement *spl_object_storage_attach_handle(spl_SplObje static spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *intern, zend_object *obj, zval *inf) /* {{{ */ { - if (EXPECTED(!(intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION))) { + if (EXPECTED( + !intern->fptr_get_hash + && obj->ce->dimension_handlers + && obj->ce->dimension_handlers->write_dimension == spl_object_storage_write_dimension + )) { return spl_object_storage_attach_handle(intern, obj, inf); } /* getHash or offsetSet is overridden. */ @@ -221,9 +224,14 @@ static spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStora static zend_result spl_object_storage_detach(spl_SplObjectStorage *intern, zend_object *obj) /* {{{ */ { - if (EXPECTED(!(intern->flags & SOS_OVERRIDDEN_UNSET_DIMENSION))) { + if (EXPECTED( + !intern->fptr_get_hash + && obj->ce->dimension_handlers + && obj->ce->dimension_handlers->unset_dimension == spl_object_storage_unset_dimension + )) { return zend_hash_index_del(&intern->storage, obj->handle); } + /* getHash or offsetUnset is overridden. */ zend_result ret = FAILURE; zend_hash_key key; if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) { @@ -249,9 +257,6 @@ static void spl_object_storage_addall(spl_SplObjectStorage *intern, spl_SplObjec intern->index = 0; } /* }}} */ -#define SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zstr_method) \ - (class_type->arrayaccess_funcs_ptr && class_type->arrayaccess_funcs_ptr->zstr_method) - static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend_object *orig) /* {{{ */ { spl_SplObjectStorage *intern; @@ -275,21 +280,6 @@ static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend if (get_hash->common.scope != spl_ce_SplObjectStorage) { intern->fptr_get_hash = get_hash; } - if (intern->fptr_get_hash != NULL || - SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zf_offsetget) || - SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zf_offsetexists)) { - intern->flags |= SOS_OVERRIDDEN_READ_DIMENSION; - } - - if (intern->fptr_get_hash != NULL || - SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zf_offsetset)) { - intern->flags |= SOS_OVERRIDDEN_WRITE_DIMENSION; - } - - if (intern->fptr_get_hash != NULL || - SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zf_offsetunset)) { - intern->flags |= SOS_OVERRIDDEN_UNSET_DIMENSION; - } } break; } @@ -437,39 +427,33 @@ PHP_METHOD(SplObjectStorage, attach) spl_object_storage_attach(intern, obj, inf); } /* }}} */ -// todo: make spl_object_storage_has_dimension return bool as well -static int spl_object_storage_has_dimension(zend_object *object, zval *offset, int check_empty) +static bool spl_object_storage_has_dimension(zend_object *object, /* const */ zval *offset) { - spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); - if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_READ_DIMENSION))) { - /* Can't optimize empty()/isset() check if getHash, offsetExists, or offsetGet is overridden */ - return zend_std_has_dimension(object, offset, check_empty); + if (UNEXPECTED(Z_TYPE_P(offset) != IS_OBJECT)) { + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_R); + return false; } + + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); spl_SplObjectStorageElement *element = zend_hash_index_find_ptr(&intern->storage, Z_OBJ_HANDLE_P(offset)); if (!element) { - return 0; + return false; } - if (check_empty) { - return i_zend_is_true(&element->inf); - } - /* NOTE: SplObjectStorage->offsetExists() is an alias of SplObjectStorage->contains(), so this returns true even if the value is null. */ - return 1; + return true; } -static zval *spl_object_storage_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +static zval *spl_object_storage_read_dimension(zend_object *object, /* const */ zval *offset, zval *rv) { - spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); - if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_READ_DIMENSION))) { - /* Can't optimize it if getHash, offsetExists, or offsetGet is overridden */ - return zend_std_read_dimension(object, offset, type, rv); + if (UNEXPECTED(Z_TYPE_P(offset) != IS_OBJECT)) { + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_IS); + return NULL; } + + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); spl_SplObjectStorageElement *element = zend_hash_index_find_ptr(&intern->storage, Z_OBJ_HANDLE_P(offset)); if (!element) { - if (type == BP_VAR_IS) { - return &EG(uninitialized_zval); - } zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Object not found"); return NULL; } else { @@ -482,21 +466,23 @@ static zval *spl_object_storage_read_dimension(zend_object *object, zval *offset static void spl_object_storage_write_dimension(zend_object *object, zval *offset, zval *inf) { - spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); - if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION))) { - zend_std_write_dimension(object, offset, inf); + if (UNEXPECTED(Z_TYPE_P(offset) != IS_OBJECT)) { + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_W); return; } + + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); spl_object_storage_attach_handle(intern, Z_OBJ_P(offset), inf); } -static void spl_object_storage_unset_dimension(zend_object *object, zval *offset) +static void spl_object_storage_unset_dimension(zend_object *object, /* const */ zval *offset) { - spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); - if (UNEXPECTED(Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_UNSET_DIMENSION))) { - zend_std_unset_dimension(object, offset); + if (UNEXPECTED(Z_TYPE_P(offset) != IS_OBJECT)) { + zend_illegal_container_offset(object->ce->name, offset, BP_VAR_R); return; } + + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); zend_hash_index_del(&intern->storage, Z_OBJ_HANDLE_P(offset)); } @@ -1361,15 +1347,32 @@ PHP_METHOD(MultipleIterator, key) } /* }}} */ +static /* const */ zend_class_dimensions_functions spl_object_storage_dimensions_functions = { + .read_dimension = spl_object_storage_read_dimension, + .has_dimension = spl_object_storage_has_dimension, + // TODO Support fetching? + //.fetch_dimension = spl_object_storage_fetch_dimension, + .write_dimension = spl_object_storage_write_dimension, + .unset_dimension = spl_object_storage_unset_dimension +}; + /* {{{ PHP_MINIT_FUNCTION(spl_observer) */ PHP_MINIT_FUNCTION(spl_observer) { spl_ce_SplObserver = register_class_SplObserver(); spl_ce_SplSubject = register_class_SplSubject(); - spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, spl_ce_SeekableIterator, zend_ce_serializable, zend_ce_arrayaccess); + spl_ce_SplObjectStorage = register_class_SplObjectStorage( + zend_ce_countable, + spl_ce_SeekableIterator, + zend_ce_serializable, + zend_ce_dimension_read, + zend_ce_dimension_write, + zend_ce_dimension_unset + ); spl_ce_SplObjectStorage->create_object = spl_SplObjectStorage_new; spl_ce_SplObjectStorage->default_object_handlers = &spl_handler_SplObjectStorage; + spl_ce_SplObjectStorage->dimension_handlers = &spl_object_storage_dimensions_functions; memcpy(&spl_handler_SplObjectStorage, &std_object_handlers, sizeof(zend_object_handlers)); @@ -1378,10 +1381,6 @@ PHP_MINIT_FUNCTION(spl_observer) spl_handler_SplObjectStorage.clone_obj = spl_object_storage_clone; spl_handler_SplObjectStorage.get_gc = spl_object_storage_get_gc; spl_handler_SplObjectStorage.free_obj = spl_SplObjectStorage_free_storage; - spl_handler_SplObjectStorage.read_dimension = spl_object_storage_read_dimension; - spl_handler_SplObjectStorage.write_dimension = spl_object_storage_write_dimension; - spl_handler_SplObjectStorage.has_dimension = spl_object_storage_has_dimension; - spl_handler_SplObjectStorage.unset_dimension = spl_object_storage_unset_dimension; spl_ce_MultipleIterator = register_class_MultipleIterator(zend_ce_iterator); spl_ce_MultipleIterator->create_object = spl_SplObjectStorage_new; diff --git a/ext/spl/spl_observer.stub.php b/ext/spl/spl_observer.stub.php index 9e78272cf3506..28716de98ee26 100644 --- a/ext/spl/spl_observer.stub.php +++ b/ext/spl/spl_observer.stub.php @@ -20,7 +20,7 @@ public function detach(SplObserver $observer): void; public function notify(): void; } -class SplObjectStorage implements Countable, SeekableIterator, Serializable, ArrayAccess +class SplObjectStorage implements Countable, SeekableIterator, Serializable, DimensionFetchable, DimensionWritable, DimensionUnsetable { /** @tentative-return-type */ public function attach(object $object, mixed $info = null): void {} diff --git a/ext/spl/spl_observer_arginfo.h b/ext/spl/spl_observer_arginfo.h index 730f4c279c254..6b0b166afe941 100644 --- a/ext/spl/spl_observer_arginfo.h +++ b/ext/spl/spl_observer_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a846c9dd240b6f0666cd5e805abfacabe360cf4c */ + * Stub hash: 1d7459545b5752edf4101459ed5a3d67185055a5 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplObserver_update, 0, 1, IS_VOID, 0) ZEND_ARG_OBJ_INFO(0, subject, SplSubject, 0) @@ -250,13 +250,13 @@ static zend_class_entry *register_class_SplSubject(void) return class_entry; } -static zend_class_entry *register_class_SplObjectStorage(zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_SeekableIterator, zend_class_entry *class_entry_Serializable, zend_class_entry *class_entry_ArrayAccess) +static zend_class_entry *register_class_SplObjectStorage(zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_SeekableIterator, zend_class_entry *class_entry_Serializable, zend_class_entry *class_entry_DimensionFetchable, zend_class_entry *class_entry_DimensionWritable, zend_class_entry *class_entry_DimensionUnsetable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "SplObjectStorage", class_SplObjectStorage_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 4, class_entry_Countable, class_entry_SeekableIterator, class_entry_Serializable, class_entry_ArrayAccess); + zend_class_implements(class_entry, 6, class_entry_Countable, class_entry_SeekableIterator, class_entry_Serializable, class_entry_DimensionFetchable, class_entry_DimensionWritable, class_entry_DimensionUnsetable); return class_entry; } diff --git a/ext/spl/tests/ArrayObject/indirect_assign_reference_to_offset.phpt b/ext/spl/tests/ArrayObject/indirect_assign_reference_to_offset.phpt new file mode 100644 index 0000000000000..248af94f4ad9d --- /dev/null +++ b/ext/spl/tests/ArrayObject/indirect_assign_reference_to_offset.phpt @@ -0,0 +1,63 @@ +--TEST-- +Setting indirections must work for ArrayObject +--FILE-- + $var) +{ + $a[$i] = &$var; +} +$a[] = &$var; +var_dump($a); + +echo "Using ArrayObject\n"; +$ao = new ArrayObject; +foreach ([1, 2, 3] as $i => $var) +{ + $ao[$i] = &$var; +} +var_dump($ao); +$ao[] = &$var; +var_dump($ao); +?> +--EXPECT-- +Using array +array(4) { + [0]=> + &int(3) + [1]=> + &int(3) + [2]=> + &int(3) + [3]=> + &int(3) +} +Using ArrayObject +object(ArrayObject)#1 (1) { + ["storage":"ArrayObject":private]=> + array(3) { + [0]=> + &int(3) + [1]=> + &int(3) + [2]=> + &int(3) + } +} +object(ArrayObject)#1 (1) { + ["storage":"ArrayObject":private]=> + array(4) { + [0]=> + &int(3) + [1]=> + &int(3) + [2]=> + &int(3) + [3]=> + &int(3) + } +} diff --git a/ext/spl/tests/SplFixedArray_indirect_modification.phpt b/ext/spl/tests/SplFixedArray_indirect_modification.phpt index ab85b3a09eec1..8fa33890daf6c 100644 --- a/ext/spl/tests/SplFixedArray_indirect_modification.phpt +++ b/ext/spl/tests/SplFixedArray_indirect_modification.phpt @@ -1,14 +1,35 @@ --TEST-- -SplFixedArray indirect modification notice +SplFixedArray indirect modification by fetching reference --FILE-- getMessage(), PHP_EOL; +} +var_dump($a); +unset($r); +var_dump($a); +$v = $a[0]; +var_dump($v); +$v = 10; +var_dump($v); var_dump($a); ?> ---EXPECTF-- -Notice: Indirect modification of overloaded element of SplFixedArray has no effect in %s on line %d +--EXPECT-- +object(SplFixedArray)#1 (1) { + [0]=> + &int(3) +} +object(SplFixedArray)#1 (1) { + [0]=> + &int(3) +} +int(3) +int(10) object(SplFixedArray)#1 (1) { [0]=> - NULL + &int(3) } diff --git a/ext/spl/tests/SplObjectStorage/SplObjectStorage_coalesce.phpt b/ext/spl/tests/SplObjectStorage/SplObjectStorage_coalesce.phpt index 5a89b46be6b8f..52fbde6d97860 100644 --- a/ext/spl/tests/SplObjectStorage/SplObjectStorage_coalesce.phpt +++ b/ext/spl/tests/SplObjectStorage/SplObjectStorage_coalesce.phpt @@ -30,20 +30,27 @@ var_dump(empty($s[$o2])); var_dump($s->contains($o2)); try { $s['invalid'] = 123; -} catch (Error $e) { +} catch (Throwable $e) { printf("%s: %s\n", $e::class, $e->getMessage()); } try { var_dump(isset($s['invalid'])); -} catch (Error $e) { +} catch (Throwable $e) { printf("%s: %s\n", $e::class, $e->getMessage()); } -$a = &$s[$o1]; + +// Fetching is not supported +try { + $a = &$s[$o1]; +} catch (Throwable $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + var_dump($s); ?> ---EXPECTF-- +--EXPECT-- string(7) "default" string(7) "dynamic" string(7) "dynamic" @@ -57,17 +64,16 @@ bool(true) object(stdClass)#4 (0) { } check isset/empty/contains for null. offsetExists returns true as long as the entry is there. -bool(true) +bool(false) bool(true) bool(true) check isset/empty/contains for false. bool(true) bool(true) bool(true) -TypeError: SplObjectStorage::offsetSet(): Argument #1 ($object) must be of type object, string given -TypeError: SplObjectStorage::offsetExists(): Argument #1 ($object) must be of type object, string given - -Notice: Indirect modification of overloaded element of SplObjectStorage has no effect in %s on line 38 +TypeError: Cannot access offset of type string on SplObjectStorage +TypeError: Cannot access offset of type string on SplObjectStorage +Error: Cannot fetch offset of object of type SplObjectStorage object(SplObjectStorage)#1 (1) { ["storage":"SplObjectStorage":private]=> array(2) { diff --git a/ext/spl/tests/arrayIterator_count_basic1.phpt b/ext/spl/tests/arrayIterator_count_basic1.phpt new file mode 100644 index 0000000000000..a987e18523c31 --- /dev/null +++ b/ext/spl/tests/arrayIterator_count_basic1.phpt @@ -0,0 +1,38 @@ +--TEST-- +SPL: ArrayIterator::count() basic functionality. +--FILE-- +==ArrayIterator== +count(), $ao->count()); +?> +--EXPECT-- +==ArrayIterator== +int(99) +int(0) +int(99) +int(1) +int(99) +int(2) +int(99) +int(1) diff --git a/ext/spl/tests/arrayObject_count_basic1.phpt b/ext/spl/tests/arrayObject_count_basic1.phpt index 0554616b5f82e..4d8db69e96e34 100644 --- a/ext/spl/tests/arrayObject_count_basic1.phpt +++ b/ext/spl/tests/arrayObject_count_basic1.phpt @@ -1,5 +1,5 @@ --TEST-- -SPL: ArrayObject::count() and ArrayIterator::count() basic functionality. +SPL: ArrayObject::count() basic functionality. --FILE-- ==ArrayObject== count(), $ao->count()); -?> -==ArrayIterator== -count(), $ao->count()); @@ -61,12 +36,3 @@ int(99) int(2) int(99) int(1) -==ArrayIterator== -int(99) -int(0) -int(99) -int(1) -int(99) -int(2) -int(99) -int(1) diff --git a/ext/spl/tests/array_010.phpt b/ext/spl/tests/array_010.phpt index ce59dcaf0b6a9..dac943e084643 100644 --- a/ext/spl/tests/array_010.phpt +++ b/ext/spl/tests/array_010.phpt @@ -1,5 +1,5 @@ --TEST-- -SPL: ArrayIterator implements ArrayAccess +SPL: ArrayObject implements ArrayAccess --FILE-- append($value); + foreach($dataArray as $value) { + $this->append($value); + } } public function offsetSet($index, $value): void { parent::offsetSet($index, $value); } + // TODO This should work even with old version + //public function append($value): void + //{ + // parent::append($value); + //} } $data1=array('one', 'two', 'three'); diff --git a/ext/spl/tests/bug52861.phpt b/ext/spl/tests/bug52861.phpt index 1213a1e1bc6bc..e1d1fb3bd5c7a 100644 --- a/ext/spl/tests/bug52861.phpt +++ b/ext/spl/tests/bug52861.phpt @@ -1,5 +1,5 @@ --TEST-- -Bug #52861 (unset failes with ArrayObject and deep arrays) +Bug #52861 (unset fails with ArrayObject and deep arrays) --FILE-- array('bar' => array('baz' => 'boo')))); diff --git a/ext/spl/tests/bug53362.phpt b/ext/spl/tests/bug53362.phpt index f291770cd0bca..db0b8f7924afb 100644 --- a/ext/spl/tests/bug53362.phpt +++ b/ext/spl/tests/bug53362.phpt @@ -11,12 +11,24 @@ class obj extends SplFixedArray{ $obj = new obj; -$obj[]=2; -$obj[]=2; -$obj[]=2; +try { + $obj[]=2; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + $obj[]=2; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + $obj[]=2; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} ?> --EXPECT-- -NULL -NULL -NULL +Error: Cannot append to object of type obj +Error: Cannot append to object of type obj +Error: Cannot append to object of type obj diff --git a/ext/spl/tests/bug64106.phpt b/ext/spl/tests/bug64106.phpt index 7476d7af7ff41..2d8ded0263afb 100644 --- a/ext/spl/tests/bug64106.phpt +++ b/ext/spl/tests/bug64106.phpt @@ -8,10 +8,12 @@ class MyFixedArray extends SplFixedArray { } $array = new MyFixedArray(10); -$array[][1] = 10; +try { + $array[][1] = 10; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -NULL - -Notice: Indirect modification of overloaded element of MyFixedArray has no effect in %s on line %d +--EXPECT-- +Error: Cannot fetch append object of type MyFixedArray diff --git a/ext/spl/tests/bug66834.phpt b/ext/spl/tests/bug66834.phpt index 864ac3725c29e..a0d48c8da1aaf 100644 --- a/ext/spl/tests/bug66834.phpt +++ b/ext/spl/tests/bug66834.phpt @@ -1,5 +1,5 @@ --TEST-- -SPL: Bug #66834 +SPL: Bug #66834 empty() does not work on classes that extend ArrayObject --FILE-- offsetExists('foo'), $object->offsetExists('sbb'), isset($obje ==== class with offsetExists() and offsetGet() ==== string(37) "Called: ArrayObjectBoth::offsetExists" string(37) "Called: ArrayObjectBoth::offsetExists" +string(34) "Called: ArrayObjectBoth::offsetGet" string(37) "Called: ArrayObjectBoth::offsetExists" string(34) "Called: ArrayObjectBoth::offsetGet" bool(true) @@ -89,13 +90,15 @@ bool(true) bool(true) string(37) "Called: ArrayObjectBoth::offsetExists" string(37) "Called: ArrayObjectBoth::offsetExists" +string(34) "Called: ArrayObjectBoth::offsetGet" string(37) "Called: ArrayObjectBoth::offsetExists" string(34) "Called: ArrayObjectBoth::offsetGet" bool(true) -bool(true) +bool(false) bool(true) string(37) "Called: ArrayObjectBoth::offsetExists" string(37) "Called: ArrayObjectBoth::offsetExists" +string(34) "Called: ArrayObjectBoth::offsetGet" string(37) "Called: ArrayObjectBoth::offsetExists" string(34) "Called: ArrayObjectBoth::offsetGet" bool(true) @@ -118,7 +121,7 @@ string(39) "Called: ArrayObjectExists::offsetExists" string(39) "Called: ArrayObjectExists::offsetExists" string(39) "Called: ArrayObjectExists::offsetExists" bool(true) -bool(true) +bool(false) bool(true) string(39) "Called: ArrayObjectExists::offsetExists" string(39) "Called: ArrayObjectExists::offsetExists" @@ -134,14 +137,17 @@ bool(false) bool(true) ==== class with offsetGet() ==== string(33) "Called: ArrayObjectGet::offsetGet" +string(33) "Called: ArrayObjectGet::offsetGet" bool(true) bool(true) bool(true) string(33) "Called: ArrayObjectGet::offsetGet" +string(33) "Called: ArrayObjectGet::offsetGet" bool(true) bool(false) bool(true) string(33) "Called: ArrayObjectGet::offsetGet" +string(33) "Called: ArrayObjectGet::offsetGet" bool(true) bool(true) bool(false) @@ -150,9 +156,11 @@ bool(false) bool(true) ==== class with offsetGet() and offsetSet() ==== +Warning: Undefined array key "foo" in %s on line %d + Warning: Undefined array key "foo" in %s on line %d bool(false) bool(true) bool(false) -bool(true) +bool(false) bool(true) diff --git a/ext/spl/tests/bug70852.phpt b/ext/spl/tests/bug70852.phpt index 44168f990320a..41f0854ab7f1a 100644 --- a/ext/spl/tests/bug70852.phpt +++ b/ext/spl/tests/bug70852.phpt @@ -10,6 +10,4 @@ var_dump($y[NULL]++); --EXPECTF-- Warning: Undefined array key "" in %s on line %d NULL - -Warning: Undefined array key "" in %s on line %d NULL diff --git a/ext/spl/tests/bug74058.phpt b/ext/spl/tests/bug74058.phpt index 32c57153ec100..e1f3454d8792b 100644 --- a/ext/spl/tests/bug74058.phpt +++ b/ext/spl/tests/bug74058.phpt @@ -59,12 +59,8 @@ var_dump($y['a2']); ?> --EXPECTF-- -offsetSet('a1') -offsetGet('a1') object(stdClass)#%s (0) { } -offsetGet('a1') -offsetGet('a1') object(stdClass)#%s (1) { ["b"]=> string(10) "some value" @@ -74,7 +70,6 @@ offsetGet('a2') object(stdClass)#%s (0) { } offsetGet('a2') -offsetGet('a2') object(stdClass)#%s (1) { ["b"]=> string(10) "some value" diff --git a/ext/spl/tests/bug74478.phpt b/ext/spl/tests/bug74478.phpt index c491b666f5dcc..ae9353f6406a5 100644 --- a/ext/spl/tests/bug74478.phpt +++ b/ext/spl/tests/bug74478.phpt @@ -31,7 +31,7 @@ var_dump($fixedData[0] ?? 42); var_dump($fixedData[0][1][2] ?? 42); $fixedData[0] = new MyFixedArray(10); -$fixedData[0][1] = new MyFixedArray(10); +// TODO Add support? $fixedData[0][1] = new MyFixedArray(10); var_dump(isset($fixedData[0][1][2])); var_dump($fixedData[0][1][2] ?? 42); @@ -46,17 +46,11 @@ int(42) offsetExists(0) int(42) offsetSet(0) -offsetGet(0) -offsetSet(1) offsetExists(0) offsetGet(0) offsetExists(1) -offsetGet(1) -offsetExists(2) bool(false) offsetExists(0) offsetGet(0) offsetExists(1) -offsetGet(1) -offsetExists(2) int(42) diff --git a/ext/spl/tests/fixedarray_002.phpt b/ext/spl/tests/fixedarray_002.phpt index 329fa57eacb16..9477c5f6fd765 100644 --- a/ext/spl/tests/fixedarray_002.phpt +++ b/ext/spl/tests/fixedarray_002.phpt @@ -82,8 +82,10 @@ A::offsetSet A::offsetUnset A::offsetExists A::offsetExists +A::offsetGet A::offsetExists A::offsetExists +A::offsetGet bool(false) bool(true) bool(true) diff --git a/ext/spl/tests/fixedarray_003.phpt b/ext/spl/tests/fixedarray_003.phpt index cca9ac07e9f7a..fbb507f9d8a6c 100644 --- a/ext/spl/tests/fixedarray_003.phpt +++ b/ext/spl/tests/fixedarray_003.phpt @@ -194,11 +194,15 @@ isset() bool(true) bool(true) +Deprecated: Implicit conversion from float 2.5 to int loses precision in %s on line %d + Deprecated: Implicit conversion from float 2.5 to int loses precision in %s on line %d bool(true) Cannot access offset of type array on SplFixedArray Cannot access offset of type stdClass on SplFixedArray +Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d + Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d bool(true) bool(true) @@ -209,11 +213,15 @@ empty() bool(false) bool(false) +Deprecated: Implicit conversion from float 2.5 to int loses precision in %s on line %d + Deprecated: Implicit conversion from float 2.5 to int loses precision in %s on line %d bool(false) Cannot access offset of type array on SplFixedArray Cannot access offset of type stdClass on SplFixedArray +Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d + Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d bool(false) bool(false) diff --git a/ext/spl/tests/fixedarray_004.phpt b/ext/spl/tests/fixedarray_004.phpt index c1bf3054030a1..ca44ee359d63c 100644 --- a/ext/spl/tests/fixedarray_004.phpt +++ b/ext/spl/tests/fixedarray_004.phpt @@ -13,4 +13,4 @@ try { ?> --EXPECT-- -[] operator not supported for SplFixedArray +Cannot append to object of type SplFixedArray diff --git a/ext/spl/tests/fixedarray_006.phpt b/ext/spl/tests/fixedarray_006.phpt index 75ecbb7c7f224..e383684225fee 100644 --- a/ext/spl/tests/fixedarray_006.phpt +++ b/ext/spl/tests/fixedarray_006.phpt @@ -18,5 +18,5 @@ print "ok\n"; ?> --EXPECT-- -[] operator not supported for SplFixedArray +Cannot append to object of type SplFixedArray ok diff --git a/ext/spl/tests/fixedarray_008.phpt b/ext/spl/tests/fixedarray_008.phpt index 8bac0824d1383..a4821d268a119 100644 --- a/ext/spl/tests/fixedarray_008.phpt +++ b/ext/spl/tests/fixedarray_008.phpt @@ -10,7 +10,11 @@ $a[0] = 1; $a[1] = 2; $a[2] = $a; -$a[2][0] = 3; +try { + $a[2][0] = 3; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} foreach ($a as $x) { if (is_object($x)) { diff --git a/ext/spl/tests/fixedarray_012.phpt b/ext/spl/tests/fixedarray_012.phpt index 0c54dc10552c4..7029d2db60d5e 100644 --- a/ext/spl/tests/fixedarray_012.phpt +++ b/ext/spl/tests/fixedarray_012.phpt @@ -15,5 +15,5 @@ print "ok\n"; ?> --EXPECT-- -[] operator not supported for SplFixedArray +Cannot fetch append object of type SplFixedArray ok diff --git a/ext/spl/tests/fixedarray_013.phpt b/ext/spl/tests/fixedarray_013.phpt index fa0d33a41655c..94ba10cba8569 100644 --- a/ext/spl/tests/fixedarray_013.phpt +++ b/ext/spl/tests/fixedarray_013.phpt @@ -18,4 +18,4 @@ try { ?> --EXPECT-- -[] operator not supported for SplFixedArray +Cannot fetch append object of type SplFixedArray diff --git a/ext/spl/tests/fixedarray_014.phpt b/ext/spl/tests/fixedarray_014.phpt index 75108b5868476..4070f25524606 100644 --- a/ext/spl/tests/fixedarray_014.phpt +++ b/ext/spl/tests/fixedarray_014.phpt @@ -3,13 +3,13 @@ SPL: FixedArray: Trying to access inexistent item --FILE-- getMessage(); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } ?> --EXPECT-- -Index invalid or out of range +OutOfBoundsException: Index invalid or out of range diff --git a/ext/spl/tests/iterator_035.phpt b/ext/spl/tests/iterator_035.phpt index 0688635450367..9c994b0d7f809 100644 --- a/ext/spl/tests/iterator_035.phpt +++ b/ext/spl/tests/iterator_035.phpt @@ -7,14 +7,23 @@ $tmp = 1; $a = new ArrayIterator(); $a[] = $tmp; -$a[] = &$tmp; +var_dump($a); +try { + $a[] = &$tmp; + var_dump($a); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} echo "Done\n"; ?> ---EXPECTF-- -Notice: Indirect modification of overloaded element of ArrayIterator has no effect in %s on line %d - -Fatal error: Uncaught Error: Cannot assign by reference to an array dimension of an object in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +object(ArrayIterator)#1 (1) { + ["storage":"ArrayIterator":private]=> + array(1) { + [0]=> + int(1) + } +} +Error: Cannot assign by reference to an array dimension of an object +Done diff --git a/ext/zend_test/object_handlers.c b/ext/zend_test/object_handlers.c index 15e362605f8ec..cc5a05505821d 100644 --- a/ext/zend_test/object_handlers.c +++ b/ext/zend_test/object_handlers.c @@ -17,6 +17,7 @@ #include "object_handlers.h" #include "zend_API.h" #include "object_handlers_arginfo.h" +// #include "zend_dimension_handlers.h" /* donc refers to DoOperationNoCast */ static zend_class_entry *donc_ce; @@ -251,33 +252,70 @@ static void dimension_common_helper(zend_object *object, zval *offset, int prop_ } } -static zval* dimension_handlers_no_ArrayAccess_read_dimension(zend_object *object, zval *offset, int type, zval *rv) { +static zval* dimension_handlers_no_ArrayAccess_ce_handler_read_dimension(zend_object *object, zval *offset, zval *rv) { dimension_common_helper(object, offset, 0); - /* ReadType */ - ZVAL_LONG(OBJ_PROP_NUM(object, 4), type); /* Normal logic */ ZVAL_BOOL(rv, true); return rv; } +static bool dimension_handlers_no_ArrayAccess_ce_handler_has_dimension(zend_object *object, zval *offset) { + dimension_common_helper(object, offset, 2); + + /* Normal logic */ + return true; +} + static void dimension_handlers_no_ArrayAccess_write_dimension(zend_object *object, zval *offset, zval *value) { dimension_common_helper(object, offset, 1); } -static int dimension_handlers_no_ArrayAccess_has_dimension(zend_object *object, zval *offset, int check_empty) { - /* checkEmpty */ - ZVAL_LONG(OBJ_PROP_NUM(object, 6), check_empty); - dimension_common_helper(object, offset, 2); +static void dimension_handlers_no_ArrayAccess_unset_dimension(zend_object *object, zval *offset) { + dimension_common_helper(object, offset, 3); +} + +static void dimension_handlers_no_ArrayAccess_append_dimension(zend_object *object, zval *value) { + // TODO FIX ME AND USE dimension_common_helper() when I added the append and fetch properties + /* hasOffset */ + ZVAL_BOOL(OBJ_PROP_NUM(object, 5), false); +} +static zval *dimension_handlers_no_ArrayAccess_fetch_dimension(zend_object *object, zval *offset, zval *rv) { + // TODO FIX ME AND USE dimension_common_helper() when I added the append and fetch properties + /* hasOffset */ + ZVAL_BOOL(OBJ_PROP_NUM(object, 5), offset != NULL); + if (offset) { + ZVAL_COPY(OBJ_PROP_NUM(object, 7), offset); + } /* Normal logic */ - return 1; + GC_ADDREF(object); + ZVAL_OBJ(rv, object); + ZVAL_MAKE_REF(rv); + return rv; } -static void dimension_handlers_no_ArrayAccess_unset_dimension(zend_object *object, zval *offset) { - dimension_common_helper(object, offset, 3); +static zval *dimension_handlers_no_ArrayAccess_fetch_append(zend_object *object, zval *rv) { + // TODO FIX ME AND USE dimension_common_helper() when I added the append and fetch properties + /* hasOffset */ + ZVAL_BOOL(OBJ_PROP_NUM(object, 5), false); + /* Normal logic */ + GC_ADDREF(object); + ZVAL_OBJ(rv, object); + ZVAL_MAKE_REF(rv); + return rv; } +static /* const */ zend_class_dimensions_functions dimension_handlers_no_ArrayAccess_dimension_functions = { + .read_dimension = dimension_handlers_no_ArrayAccess_ce_handler_read_dimension, + .has_dimension = dimension_handlers_no_ArrayAccess_ce_handler_has_dimension, + .fetch_dimension = dimension_handlers_no_ArrayAccess_fetch_dimension, + .write_dimension = dimension_handlers_no_ArrayAccess_write_dimension, + .append = dimension_handlers_no_ArrayAccess_append_dimension, + .fetch_append = dimension_handlers_no_ArrayAccess_fetch_append, + .unset_dimension = dimension_handlers_no_ArrayAccess_unset_dimension +}; + void zend_test_object_handlers_init(void) { /* DoOperationNoCast class */ @@ -305,8 +343,6 @@ void zend_test_object_handlers_init(void) dimension_handlers_no_ArrayAccess_ce = register_class_DimensionHandlersNoArrayAccess(); dimension_handlers_no_ArrayAccess_ce->create_object = dimension_handlers_no_ArrayAccess_object_create; memcpy(&dimension_handlers_no_ArrayAccess_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); - dimension_handlers_no_ArrayAccess_object_handlers.read_dimension = dimension_handlers_no_ArrayAccess_read_dimension; - dimension_handlers_no_ArrayAccess_object_handlers.write_dimension = dimension_handlers_no_ArrayAccess_write_dimension; - dimension_handlers_no_ArrayAccess_object_handlers.has_dimension = dimension_handlers_no_ArrayAccess_has_dimension; - dimension_handlers_no_ArrayAccess_object_handlers.unset_dimension = dimension_handlers_no_ArrayAccess_unset_dimension; + + dimension_handlers_no_ArrayAccess_ce->dimension_handlers = &dimension_handlers_no_ArrayAccess_dimension_functions; } diff --git a/tests/classes/array_access_001.phpt b/tests/classes/array_access_001.phpt index 9498d3ce5576d..0d2916ffbe3e1 100644 --- a/tests/classes/array_access_001.phpt +++ b/tests/classes/array_access_001.phpt @@ -112,12 +112,16 @@ ObjectOne::offsetExists(6) bool(true) ===isset=== ObjectOne::offsetExists(0) +ObjectOne::offsetGet(0) bool(true) ObjectOne::offsetExists(1) +ObjectOne::offsetGet(1) bool(true) ObjectOne::offsetExists(2) +ObjectOne::offsetGet(2) bool(true) ObjectOne::offsetExists(4th) +ObjectOne::offsetGet(4th) bool(true) ObjectOne::offsetExists(5th) bool(false) diff --git a/tests/classes/array_access_002.phpt b/tests/classes/array_access_002.phpt index 6e6adacdc68ba..4445af85f5f86 100644 --- a/tests/classes/array_access_002.phpt +++ b/tests/classes/array_access_002.phpt @@ -112,12 +112,16 @@ ObjectOne::offsetExists(6) bool(true) ===isset=== ObjectOne::offsetExists(0) +ObjectOne::offsetGet(0) bool(true) ObjectOne::offsetExists(1) +ObjectOne::offsetGet(1) bool(true) ObjectOne::offsetExists(2) +ObjectOne::offsetGet(2) bool(true) ObjectOne::offsetExists(4th) +ObjectOne::offsetGet(4th) bool(true) ObjectOne::offsetExists(5th) bool(false) diff --git a/win32/build/config.w32 b/win32/build/config.w32 index ae9b6146b5f06..4c0c3dfd83162 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -240,6 +240,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ + zend_interfaces_dimension.c \ zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c");