diff --git a/UPGRADING b/UPGRADING index 6a61e9a427e11..f2f53ee4b8d1b 100644 --- a/UPGRADING +++ b/UPGRADING @@ -215,6 +215,8 @@ PHP 8.4 UPGRADE NOTES RFC: https://wiki.php.net/rfc/new_without_parentheses . Added the #[\Deprecated] attribute. RFC: https://wiki.php.net/rfc/deprecated_attribute + . Implemented property hooks. + RFC: https://wiki.php.net/rfc/property-hooks - Curl: . curl_version() returns an additional feature_list value, which is an diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index 01eb37d7081bb..4b27aebc9d39a 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -197,6 +197,9 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx LITERAL_INFO(opline->op2.constant, 2); } break; + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: + LITERAL_INFO(opline->op1.constant, 1); + break; case ZEND_CATCH: LITERAL_INFO(opline->op1.constant, 2); break; diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c index 2620c906d8397..ce6c43afaedbe 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -48,6 +48,7 @@ static void zend_delete_call_instructions(zend_op_array *op_array, zend_op *opli case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_FCALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: if (call == 0) { MAKE_NOP(opline); return; @@ -169,12 +170,15 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) case ZEND_INIT_METHOD_CALL: case ZEND_INIT_FCALL: case ZEND_NEW: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: /* The argument passing optimizations are valid for prototypes as well, * as inheritance cannot change between ref <-> non-ref arguments. */ call_stack[call].func = zend_optimizer_get_called_func( ctx->script, op_array, opline, &call_stack[call].is_prototype); call_stack[call].try_inline = - !call_stack[call].is_prototype && opline->opcode != ZEND_NEW; + !call_stack[call].is_prototype + && opline->opcode != ZEND_NEW + && opline->opcode != ZEND_INIT_PARENT_PROPERTY_HOOK_CALL; ZEND_FALLTHROUGH; case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_USER_CALL: @@ -212,6 +216,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) } } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL || fcall->opcode == ZEND_INIT_METHOD_CALL + || fcall->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL || fcall->opcode == ZEND_NEW) { /* We don't have specialized opcodes for this, do nothing */ } else { diff --git a/Zend/Optimizer/zend_call_graph.c b/Zend/Optimizer/zend_call_graph.c index 5db8d06674b59..fd63f587336a3 100644 --- a/Zend/Optimizer/zend_call_graph.c +++ b/Zend/Optimizer/zend_call_graph.c @@ -61,6 +61,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32 case ZEND_INIT_FCALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: call_stack[call] = call_info; func = zend_optimizer_get_called_func( script, op_array, opline, &is_prototype); diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 3738e058cd881..85b74fc967b2e 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -974,6 +974,28 @@ zend_function *zend_optimizer_get_called_func( } } break; + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: { + zend_class_entry *scope = op_array->scope; + ZEND_ASSERT(scope != NULL); + if ((scope->ce_flags & ZEND_ACC_LINKED) && scope->parent) { + zend_class_entry *parent_scope = scope->parent; + zend_string *prop_name = Z_STR_P(CRT_CONSTANT(opline->op1)); + zend_property_hook_kind hook_kind = opline->op2.num; + zend_property_info *prop_info = zend_get_property_info(parent_scope, prop_name, /* silent */ true); + + if (prop_info + && prop_info != ZEND_WRONG_PROPERTY_INFO + && !(prop_info->flags & ZEND_ACC_PRIVATE) + && prop_info->hooks) { + zend_function *fbc = prop_info->hooks[hook_kind]; + if (fbc) { + *is_prototype = false; + return fbc; + } + } + } + break; + } case ZEND_NEW: { zend_class_entry *ce = zend_optimizer_get_class_entry_from_op1( @@ -1531,6 +1553,19 @@ void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void zend_foreach_op_array_helper(op_array, func, context); } } ZEND_HASH_FOREACH_END(); + + zend_property_info *property; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property) { + zend_function **hooks = property->hooks; + if (property->ce == ce && property->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + zend_function *hook = hooks[i]; + if (hook && hook->common.scope == ce) { + zend_foreach_op_array_helper(&hooks[i]->op_array, func, context); + } + } + } + } ZEND_HASH_FOREACH_END(); } ZEND_HASH_FOREACH_END(); } diff --git a/Zend/tests/alternative_offset_syntax_compile_error_in_const_expr.phpt b/Zend/tests/alternative_offset_syntax_compile_error_in_const_expr.phpt index 7001d924e8bd7..db7c152b86d2b 100644 --- a/Zend/tests/alternative_offset_syntax_compile_error_in_const_expr.phpt +++ b/Zend/tests/alternative_offset_syntax_compile_error_in_const_expr.phpt @@ -6,4 +6,4 @@ const FOO_COMPILE_ERROR = "BAR"{0}; var_dump(FOO_COMPILE_ERROR); ?> --EXPECTF-- -Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 2 +Parse error: syntax error, unexpected token "{", expecting "," or ";" in %s on line %d diff --git a/Zend/tests/alternative_offset_syntax_compile_error_outside_const_expr.phpt b/Zend/tests/alternative_offset_syntax_compile_error_outside_const_expr.phpt index c5e5848b6c400..df0dc78cb0638 100644 --- a/Zend/tests/alternative_offset_syntax_compile_error_outside_const_expr.phpt +++ b/Zend/tests/alternative_offset_syntax_compile_error_outside_const_expr.phpt @@ -6,4 +6,4 @@ $foo = 'BAR'; var_dump($foo{0}); ?> --EXPECTF-- -Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 3 +Parse error: syntax error, unexpected token "{", expecting ")" in %s on line %d diff --git a/Zend/tests/alternative_offset_syntax_in_encaps_string.phpt b/Zend/tests/alternative_offset_syntax_in_encaps_string.phpt index 2771b33afa89c..83163487082cf 100644 --- a/Zend/tests/alternative_offset_syntax_in_encaps_string.phpt +++ b/Zend/tests/alternative_offset_syntax_in_encaps_string.phpt @@ -3,4 +3,4 @@ Alternative offset syntax should emit only E_COMPILE_ERROR in string interpolati --FILE-- --EXPECTF-- -Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 1 +Parse error: syntax error, unexpected token "{", expecting "->" or "?->" or "[" in %s on line %d diff --git a/Zend/tests/errmsg_037.phpt b/Zend/tests/errmsg_037.phpt index 259eaa45106c3..00df06b13ddea 100644 --- a/Zend/tests/errmsg_037.phpt +++ b/Zend/tests/errmsg_037.phpt @@ -10,4 +10,4 @@ class test { echo "Done\n"; ?> --EXPECTF-- -Fatal error: Cannot use the abstract modifier on a property in %s on line %d +Fatal error: Only hooked properties may be declared abstract in %s on line %d diff --git a/Zend/tests/errmsg_038.phpt b/Zend/tests/errmsg_038.phpt deleted file mode 100644 index 060dc984dab03..0000000000000 --- a/Zend/tests/errmsg_038.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -errmsg: properties cannot be final ---FILE-- - ---EXPECTF-- -Fatal error: Cannot use the final modifier on a property in %s on line %d diff --git a/Zend/tests/new_without_parentheses/unset_new.phpt b/Zend/tests/new_without_parentheses/unset_new.phpt index 3d46d1ca3faa4..a176b6d09fdb0 100644 --- a/Zend/tests/new_without_parentheses/unset_new.phpt +++ b/Zend/tests/new_without_parentheses/unset_new.phpt @@ -7,4 +7,4 @@ unset(new ArrayObject()); ?> --EXPECTF-- -Parse error: syntax error, unexpected token ")", expecting "->" or "?->" or "{" or "[" in %s on line %d +Parse error: syntax error, unexpected token ")", expecting "->" or "?->" or "[" in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_hook.phpt b/Zend/tests/property_hooks/abstract_hook.phpt new file mode 100644 index 0000000000000..333df1d8de746 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_hook.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract hooks compile successfully +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/abstract_hook_in_non_abstract_class.phpt b/Zend/tests/property_hooks/abstract_hook_in_non_abstract_class.phpt new file mode 100644 index 0000000000000..fe27f3ecebc5e --- /dev/null +++ b/Zend/tests/property_hooks/abstract_hook_in_non_abstract_class.phpt @@ -0,0 +1,15 @@ +--TEST-- +Abstract hooks in non-abstract class gives an error +--FILE-- + +--EXPECTF-- +Fatal error: Class Test contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Test::$prop::get) in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_hook_not_implemented.phpt b/Zend/tests/property_hooks/abstract_hook_not_implemented.phpt new file mode 100644 index 0000000000000..bb94c0650b01a --- /dev/null +++ b/Zend/tests/property_hooks/abstract_hook_not_implemented.phpt @@ -0,0 +1,17 @@ +--TEST-- +Abstract hooks that are not implemented throw an error +--FILE-- + +--EXPECTF-- +Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_prop_hooks.phpt b/Zend/tests/property_hooks/abstract_prop_hooks.phpt new file mode 100644 index 0000000000000..ea331a43ee64d --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_hooks.phpt @@ -0,0 +1,17 @@ +--TEST-- +Explicit hooked property satisfies abstract property +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/abstract_prop_not_implemented.phpt b/Zend/tests/property_hooks/abstract_prop_not_implemented.phpt new file mode 100644 index 0000000000000..8e0b150795628 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_not_implemented.phpt @@ -0,0 +1,14 @@ +--TEST-- +Abstract property not implemented throws an error +--FILE-- + +--EXPECTF-- +Fatal error: Class A contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (A::$prop::get, A::$prop::set) in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_prop_plain.phpt b/Zend/tests/property_hooks/abstract_prop_plain.phpt new file mode 100644 index 0000000000000..181ed2b202e8d --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_plain.phpt @@ -0,0 +1,17 @@ +--TEST-- +Plain property satisfies abstract property +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/abstract_prop_without_hooks.phpt b/Zend/tests/property_hooks/abstract_prop_without_hooks.phpt new file mode 100644 index 0000000000000..e1ee2b9a58a06 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_without_hooks.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract property without hook is illegal +--FILE-- + +--EXPECTF-- +Fatal error: Only hooked properties may be declared abstract in %s on line %d diff --git a/Zend/tests/property_hooks/array_access.phpt b/Zend/tests/property_hooks/array_access.phpt new file mode 100644 index 0000000000000..d9ad934a11238 --- /dev/null +++ b/Zend/tests/property_hooks/array_access.phpt @@ -0,0 +1,49 @@ +--TEST-- +Array offset on ArrayAccess object in virtual property is allowed +--FILE-- + $this->collection; + } +} + +$c = new C(); +var_dump($c->prop['foo']); +var_dump($c->prop[] = 'foo'); +var_dump(isset($c->prop['foo'])); +unset($c->prop['foo']); + +?> +--EXPECT-- +Collection::offsetGet +bool(true) +Collection::offsetSet +string(3) "foo" +Collection::offsetExists +bool(true) +Collection::offsetUnset diff --git a/Zend/tests/property_hooks/ast_printing.phpt b/Zend/tests/property_hooks/ast_printing.phpt new file mode 100644 index 0000000000000..128a849640160 --- /dev/null +++ b/Zend/tests/property_hooks/ast_printing.phpt @@ -0,0 +1,45 @@ +--TEST-- +Hook AST printing +--FILE-- +prop1 = 42; + } + } + public $prop3 = 1 { + get => 42; + } + }); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +assert(false && new class { + public $prop1 { + get; + set; + } + public $prop2 { + get { + return parent::$prop1::get(); + } + final set { + echo 'Foo'; + $this->prop1 = 42; + } + } + public $prop3 = 1 { + get => 42; + } +}) diff --git a/Zend/tests/property_hooks/attributes.phpt b/Zend/tests/property_hooks/attributes.phpt new file mode 100644 index 0000000000000..b3777792e89ae --- /dev/null +++ b/Zend/tests/property_hooks/attributes.phpt @@ -0,0 +1,40 @@ +--TEST-- +Hooks accept method-targeted attributes +--FILE-- +getHook(PropertyHookType::Get)->getAttributes()[0]; +var_dump($getAttr->getName()); +var_dump($getAttr->getArguments()); +var_dump($getAttr->newInstance()); + +$setAttr = (new ReflectionProperty(C::class, 'prop'))->getHook(PropertyHookType::Set)->getAttributes()[0]; +var_dump($setAttr->getName()); +var_dump($setAttr->getArguments()); +var_dump($setAttr->newInstance()); + +?> +--EXPECT-- +string(1) "A" +array(0) { +} +object(A)#3 (0) { +} +string(1) "B" +array(0) { +} +object(B)#5 (0) { +} diff --git a/Zend/tests/property_hooks/backed_delegated_read_wirte.phpt b/Zend/tests/property_hooks/backed_delegated_read_wirte.phpt new file mode 100644 index 0000000000000..df85768fe516b --- /dev/null +++ b/Zend/tests/property_hooks/backed_delegated_read_wirte.phpt @@ -0,0 +1,66 @@ +--TEST-- +Attempted read/write of backing value in a delegated method throws +--SKIPIF-- + +--EXTENSIONS-- +zend_test +--INI-- +; The test may use a large amount of memory on systems with a large stack limit +memory_limit=2G +--FILE-- + $this->getProp($this->prop); + set { + $this->setProp($this->prop, $value); + } + } + + private function getProp($prop) { + return $this->prop; + } + + private function setProp($prop, $value) { + $this->prop = $value; + } + + public $prop2 = 42 { + get => $this->prop2; + set => $value; + } +} + +class Child extends Test { + public $prop2 = 42 { + get => parent::$prop2::get(); + set { parent::$prop2::set($value); } + } +} + +$test = new Test; + +try { + $test->prop = 0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump($test->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$child = new Child(); +$child->prop2 = 43; +var_dump($child->prop2); + +?> +--EXPECTF-- +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? +int(43) diff --git a/Zend/tests/property_hooks/backed_implicit_get.phpt b/Zend/tests/property_hooks/backed_implicit_get.phpt new file mode 100644 index 0000000000000..0ae7c8542a2dc --- /dev/null +++ b/Zend/tests/property_hooks/backed_implicit_get.phpt @@ -0,0 +1,22 @@ +--TEST-- +Backed property with implicit get +--FILE-- +prop = $value; + } + } +} + +$c = new C(); +$c->prop = 42; +var_dump($c->prop); + +?> +--EXPECT-- +C::$prop::set +int(42) diff --git a/Zend/tests/property_hooks/backed_implicit_set.phpt b/Zend/tests/property_hooks/backed_implicit_set.phpt new file mode 100644 index 0000000000000..49a2a8499fb87 --- /dev/null +++ b/Zend/tests/property_hooks/backed_implicit_set.phpt @@ -0,0 +1,22 @@ +--TEST-- +Backed property with implicit set +--FILE-- +prop; + } + } +} + +$c = new C(); +$c->prop = 42; +var_dump($c->prop); + +?> +--EXPECT-- +C::$prop::get +int(42) diff --git a/Zend/tests/property_hooks/backed_invariant.phpt b/Zend/tests/property_hooks/backed_invariant.phpt new file mode 100644 index 0000000000000..9398e2ea94fc2 --- /dev/null +++ b/Zend/tests/property_hooks/backed_invariant.phpt @@ -0,0 +1,16 @@ +--TEST-- +Backed property is invariant +--FILE-- + $this->prop; } +} + +class B extends A { + public string $prop { get => 'foo'; } +} + +?> +--EXPECTF-- +Fatal error: Type of B::$prop must be string|int (as in class A) in %s on line %d diff --git a/Zend/tests/property_hooks/backing_value_simple.phpt b/Zend/tests/property_hooks/backing_value_simple.phpt new file mode 100644 index 0000000000000..0f44483bb8b9f --- /dev/null +++ b/Zend/tests/property_hooks/backing_value_simple.phpt @@ -0,0 +1,45 @@ +--TEST-- +Reads and writes from backing store are only cached for $this +--FILE-- +switch) { + $other = new C(); + $other->switch = false; + } else { + $other = $this; + } + var_dump($other->prop); + return 1; + } + set => $this->prop; + } +} + +function test() { + $c = new C(); + $c->switch = true; + var_dump($c->prop); +} + +test(); +test(); + +?> +--EXPECT-- +C::$prop::get +C::$prop::get +int(42) +int(1) +int(1) +C::$prop::get +C::$prop::get +int(42) +int(1) +int(1) diff --git a/Zend/tests/property_hooks/bug001.phpt b/Zend/tests/property_hooks/bug001.phpt new file mode 100644 index 0000000000000..5328506d476cb --- /dev/null +++ b/Zend/tests/property_hooks/bug001.phpt @@ -0,0 +1,30 @@ +--TEST-- +Bug 001 +--FILE-- + $this->_x; + } +} + +var_dump((new ReflectionProperty(C::class, 'x'))->isVirtual()); + +$c = new C; + +try { + $c->x = 3; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +bool(true) +Property C::$x is read-only diff --git a/Zend/tests/property_hooks/bug002.phpt b/Zend/tests/property_hooks/bug002.phpt new file mode 100644 index 0000000000000..8e409224811d2 --- /dev/null +++ b/Zend/tests/property_hooks/bug002.phpt @@ -0,0 +1,32 @@ +--TEST-- +Bug 002 +--FILE-- + $this->getBar(); + } + + protected function getBar() { + return 'bar'; + } +} + +class A { + use Foo; +} + +class B { + use Foo; +} + +$a = new A(); +$b = new B(); +var_dump($a->bar); +var_dump($b->bar); + +?> +--EXPECT-- +string(3) "bar" +string(3) "bar" diff --git a/Zend/tests/property_hooks/bug003.phpt b/Zend/tests/property_hooks/bug003.phpt new file mode 100644 index 0000000000000..9a0c5036f934a --- /dev/null +++ b/Zend/tests/property_hooks/bug003.phpt @@ -0,0 +1,23 @@ +--TEST-- +Callable conversion of parent hook call +--FILE-- +x = 0; + +?> +--EXPECTF-- +Fatal error: Cannot create Closure for parent property hook call in %s on line %d diff --git a/Zend/tests/property_hooks/bug004.phpt b/Zend/tests/property_hooks/bug004.phpt new file mode 100644 index 0000000000000..9887b6dc78135 --- /dev/null +++ b/Zend/tests/property_hooks/bug004.phpt @@ -0,0 +1,16 @@ +--TEST-- +Set hook value param must not have a default value +--FILE-- + +--EXPECTF-- +Fatal error: Parameter $value of set hook B::$x must not have a default value in %s on line %d diff --git a/Zend/tests/property_hooks/bug005.phpt b/Zend/tests/property_hooks/bug005.phpt new file mode 100644 index 0000000000000..79eba9f3e4eba --- /dev/null +++ b/Zend/tests/property_hooks/bug005.phpt @@ -0,0 +1,21 @@ +--TEST-- +Parent hook call restriction may fail due to mangled name +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/bug006.phpt b/Zend/tests/property_hooks/bug006.phpt new file mode 100644 index 0000000000000..edb962c492ef4 --- /dev/null +++ b/Zend/tests/property_hooks/bug006.phpt @@ -0,0 +1,25 @@ +--TEST-- +Abstract properties correctly track virtualness +--FILE-- + "foo"; + } +} + +class X extends Y { + public string $prop { + get => "bar"; + } +} + +$x = new X; +$x->prop = 1; +var_dump($x->prop); + +?> +--EXPECT-- +string(3) "bar" diff --git a/Zend/tests/property_hooks/bug007.phpt b/Zend/tests/property_hooks/bug007.phpt new file mode 100644 index 0000000000000..00c8f210a464e --- /dev/null +++ b/Zend/tests/property_hooks/bug007.phpt @@ -0,0 +1,25 @@ +--TEST-- +Assign by reference to backed property is forbidden for &get-only +--FILE-- +prop; + } + } +} + +$test = new Test(); +$test->prop = &$ref; + +?> +--EXPECTF-- +Test::$prop::get + +Fatal error: Uncaught Error: Cannot assign by reference to overloaded object in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/property_hooks/bug008.phpt b/Zend/tests/property_hooks/bug008.phpt new file mode 100644 index 0000000000000..9c321cfe16e14 --- /dev/null +++ b/Zend/tests/property_hooks/bug008.phpt @@ -0,0 +1,24 @@ +--TEST-- +Assign by reference to backed property is forbidden for &get-only +--FILE-- +_bar; + } + } +} + +$foo = new Foo; +$foo->bar = 'bar'; + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Property Foo::$bar is read-only in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/property_hooks/bug009.phpt b/Zend/tests/property_hooks/bug009.phpt new file mode 100644 index 0000000000000..b7d014638b549 --- /dev/null +++ b/Zend/tests/property_hooks/bug009.phpt @@ -0,0 +1,28 @@ +--TEST-- +Assign by reference to backed property is allowed for &get-only +--FILE-- +bar; + } + } +} + +$foo = new Foo; +$foo->bar[] = 'bar'; +var_dump($foo); + +?> +--EXPECTF-- +Foo::$bar::get +object(Foo)#%d (1) { + ["bar"]=> + array(1) { + [0]=> + string(3) "bar" + } +} diff --git a/Zend/tests/property_hooks/cache.phpt b/Zend/tests/property_hooks/cache.phpt new file mode 100644 index 0000000000000..dedbfa48f4ac9 --- /dev/null +++ b/Zend/tests/property_hooks/cache.phpt @@ -0,0 +1,64 @@ +--TEST-- +Test caching of hooked property +--FILE-- +prop; } + set { echo __METHOD__, "\n"; $this->prop = $value; } + } +} + +function doTest(Test $test) { + $test->prop = null; + $test->prop; + $test->prop = 1; + $test->prop += 1; + $test->prop = []; + try { + $test->prop[] = 1; + } catch (\Error $e) { + echo $e->getMessage(), "\n"; + } + isset($test->prop); + isset($test->prop[0]); + try { + unset($test->prop); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +} + +$test = new Test; +$test->dyn = 1; +doTest($test); +echo "\n"; +doTest($test); + +?> +--EXPECTF-- +Deprecated: Creation of dynamic property Test::$dyn is deprecated in %s on line %d +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::set +Test::$prop::get +Indirect modification of Test::$prop is not allowed +Test::$prop::get +Test::$prop::get +Cannot unset hooked property Test::$prop + +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::set +Test::$prop::get +Indirect modification of Test::$prop is not allowed +Test::$prop::get +Test::$prop::get +Cannot unset hooked property Test::$prop diff --git a/Zend/tests/property_hooks/cpp.phpt b/Zend/tests/property_hooks/cpp.phpt new file mode 100644 index 0000000000000..082c182467bcf --- /dev/null +++ b/Zend/tests/property_hooks/cpp.phpt @@ -0,0 +1,34 @@ +--TEST-- +Constructor property promotion +--FILE-- + print("Getting\n"); + set { print("Setting\n"); } + } + ) { + echo "Constructor\n"; + } +} + +echo "Pre-test\n"; +$test = new Test; +$test->prop; +$test->prop = 42; + +$r = (new ReflectionProperty(Test::class, 'prop')); +var_dump($r->hasDefaultValue()); +var_dump($r->getDefaultValue()); + +?> +--EXPECT-- +Pre-test +Setting +Constructor +Getting +Setting +bool(false) +NULL diff --git a/Zend/tests/property_hooks/default_on_hooks.phpt b/Zend/tests/property_hooks/default_on_hooks.phpt new file mode 100644 index 0000000000000..f828e8198c773 --- /dev/null +++ b/Zend/tests/property_hooks/default_on_hooks.phpt @@ -0,0 +1,35 @@ +--TEST-- +Backed property may have default value +--FILE-- +prop; + } + set { + echo __METHOD__, "\n"; + $this->prop = $value; + } + } +} + +$a = new A(); +var_dump($a); +var_dump($a->prop); +$a->prop = 43; +var_dump($a->prop); + +?> +--EXPECT-- +object(A)#1 (1) { + ["prop"]=> + int(42) +} +A::$prop::get +int(42) +A::$prop::set +A::$prop::get +int(43) diff --git a/Zend/tests/property_hooks/default_on_virtual.phpt b/Zend/tests/property_hooks/default_on_virtual.phpt new file mode 100644 index 0000000000000..3451946e91270 --- /dev/null +++ b/Zend/tests/property_hooks/default_on_virtual.phpt @@ -0,0 +1,15 @@ +--TEST-- +Virtual properties cannot have default value +--FILE-- + +--EXPECTF-- +Fatal error: Cannot specify default value for virtual hooked property Test::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/default_on_virtual_with_inheritance.phpt b/Zend/tests/property_hooks/default_on_virtual_with_inheritance.phpt new file mode 100644 index 0000000000000..e7c0155489bd6 --- /dev/null +++ b/Zend/tests/property_hooks/default_on_virtual_with_inheritance.phpt @@ -0,0 +1,19 @@ +--TEST-- +Virtual property cannot have default value +--FILE-- + +--EXPECTF-- +Fatal error: Cannot specify default value for virtual hooked property B::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/direct_hook_call.phpt b/Zend/tests/property_hooks/direct_hook_call.phpt new file mode 100644 index 0000000000000..d97ef4c469a4b --- /dev/null +++ b/Zend/tests/property_hooks/direct_hook_call.phpt @@ -0,0 +1,28 @@ +--TEST-- +Call property hooks by name +--FILE-- +{'$prop::get'}(); +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->{'$prop::set'}('foo'); +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Call to undefined method Test::$prop::get() +Call to undefined method Test::$prop::set() diff --git a/Zend/tests/property_hooks/dump.phpt b/Zend/tests/property_hooks/dump.phpt new file mode 100644 index 0000000000000..f05805e8861c9 --- /dev/null +++ b/Zend/tests/property_hooks/dump.phpt @@ -0,0 +1,245 @@ +--TEST-- +Dumping object with property hooks +--FILE-- +backed); } + set { $this->backed = $value; } + } + public $writeOnly { + set {} + } + private $private = 'private' { + get { return strtoupper($this->private); } + set { $this->private = $value; } + } + private $changed = 'changed Test' { + get { return strtoupper($this->changed); } + } + public function dumpTest() { + var_dump($this); + var_dump(get_object_vars($this)); + var_dump(get_mangled_object_vars($this)); + var_export($this); + echo "\n"; + echo json_encode($this), "\n"; + var_dump((array) $this); + } +} + +class Child extends Test { + public $addedHooks { + get { return strtoupper(parent::$addedHooks::get()); } + } + private $changed = 'changed Child' { + get { return strtoupper($this->changed); } + } + public function dumpChild() { + var_dump($this); + var_dump(get_object_vars($this)); + var_export($this); + echo "\n"; + echo json_encode($this), "\n"; + var_dump((array) $this); + } +} + +function dump($test) { + var_dump($test); + var_dump(get_object_vars($test)); + var_export($test); + echo "\n"; + echo json_encode($test), "\n"; + var_dump((array) $test); +} + +dump(new Test); +dump(new Child); +(new Child)->dumpTest(); +(new Child)->dumpChild(); + +?> +--EXPECTF-- +object(Test)#%d (4) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["private":"Test":private]=> + string(7) "private" + ["changed":"Test":private]=> + string(12) "changed Test" +} +array(3) { + ["addedHooks"]=> + string(10) "addedHooks" + ["virtual"]=> + string(7) "VIRTUAL" + ["backed"]=> + string(6) "BACKED" +} +\Test::__set_state(array( + 'addedHooks' => 'addedHooks', + 'virtual' => 'VIRTUAL', + 'backed' => 'BACKED', + 'private' => 'PRIVATE', + 'changed' => 'CHANGED TEST', +)) +{"addedHooks":"addedHooks","virtual":"VIRTUAL","backed":"BACKED"} +array(4) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["%0Test%0private"]=> + string(7) "private" + ["%0Test%0changed"]=> + string(12) "changed Test" +} +object(Child)#%d (5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["private":"Test":private]=> + string(7) "private" + ["changed":"Test":private]=> + string(12) "changed Test" + ["changed":"Child":private]=> + string(13) "changed Child" +} +array(3) { + ["addedHooks"]=> + string(10) "ADDEDHOOKS" + ["virtual"]=> + string(7) "VIRTUAL" + ["backed"]=> + string(6) "BACKED" +} +\Child::__set_state(array( + 'addedHooks' => 'ADDEDHOOKS', + 'changed' => 'CHANGED CHILD', + 'virtual' => 'VIRTUAL', + 'backed' => 'BACKED', + 'private' => 'PRIVATE', + 'changed' => 'changed Child', +)) +{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"} +array(5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["%0Test%0private"]=> + string(7) "private" + ["%0Test%0changed"]=> + string(12) "changed Test" + ["%0Child%0changed"]=> + string(13) "changed Child" +} +object(Child)#%d (5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["private":"Test":private]=> + string(7) "private" + ["changed":"Test":private]=> + string(12) "changed Test" + ["changed":"Child":private]=> + string(13) "changed Child" +} +array(4) { + ["addedHooks"]=> + string(10) "ADDEDHOOKS" + ["virtual"]=> + string(7) "VIRTUAL" + ["backed"]=> + string(6) "BACKED" + ["private"]=> + string(7) "PRIVATE" +} +array(5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["%0Test%0private"]=> + string(7) "private" + ["%0Test%0changed"]=> + string(12) "changed Test" + ["%0Child%0changed"]=> + string(13) "changed Child" +} +\Child::__set_state(array( + 'addedHooks' => 'ADDEDHOOKS', + 'changed' => 'CHANGED CHILD', + 'virtual' => 'VIRTUAL', + 'backed' => 'BACKED', + 'private' => 'PRIVATE', + 'changed' => 'changed Child', +)) +{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"} +array(5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["%0Test%0private"]=> + string(7) "private" + ["%0Test%0changed"]=> + string(12) "changed Test" + ["%0Child%0changed"]=> + string(13) "changed Child" +} +object(Child)#%d (5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["private":"Test":private]=> + string(7) "private" + ["changed":"Test":private]=> + string(12) "changed Test" + ["changed":"Child":private]=> + string(13) "changed Child" +} +array(5) { + ["addedHooks"]=> + string(10) "ADDEDHOOKS" + ["changed"]=> + string(13) "CHANGED CHILD" + ["virtual"]=> + string(7) "VIRTUAL" + ["backed"]=> + string(6) "BACKED" + ["changed"]=> + string(13) "changed Child" +} +\Child::__set_state(array( + 'addedHooks' => 'ADDEDHOOKS', + 'changed' => 'CHANGED CHILD', + 'virtual' => 'VIRTUAL', + 'backed' => 'BACKED', + 'private' => 'PRIVATE', + 'changed' => 'changed Child', +)) +{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"} +array(5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["%0Test%0private"]=> + string(7) "private" + ["%0Test%0changed"]=> + string(12) "changed Test" + ["%0Child%0changed"]=> + string(13) "changed Child" +} diff --git a/Zend/tests/property_hooks/duplicate_hook.phpt b/Zend/tests/property_hooks/duplicate_hook.phpt new file mode 100644 index 0000000000000..43dffcdb91183 --- /dev/null +++ b/Zend/tests/property_hooks/duplicate_hook.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot declare same property hook twice +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare property hook "get" in %s on line %d diff --git a/Zend/tests/property_hooks/explicit_iter.phpt b/Zend/tests/property_hooks/explicit_iter.phpt new file mode 100644 index 0000000000000..ac5a04915e5e4 --- /dev/null +++ b/Zend/tests/property_hooks/explicit_iter.phpt @@ -0,0 +1,48 @@ +--TEST-- +Explicit iterator implementation +--FILE-- + 'this is not the correct value'; + } + + private $x = ['foo', 'BAR']; + private $cursor = 0; + + public function current(): string { return $this->x[$this->cursor]; } + public function key(): int { return $this->cursor; } + public function next(): void { ++$this->cursor; } + public function rewind(): void { $this->cursor = 0; } + public function valid(): bool { return isset($this->x[$this->cursor]); } +} + +class Bar implements IteratorAggregate { + public string $hook { + get => 'this is not the correct value'; + } + + public function getIterator(): Traversable { + yield 1; + yield 2; + } +} + +var_dump(iterator_to_array(new Foo())); +var_dump(iterator_to_array(new Bar())); + +?> +--EXPECT-- +array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "BAR" +} +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} diff --git a/Zend/tests/property_hooks/explicit_set_value_parameter.phpt b/Zend/tests/property_hooks/explicit_set_value_parameter.phpt new file mode 100644 index 0000000000000..59297130051df --- /dev/null +++ b/Zend/tests/property_hooks/explicit_set_value_parameter.phpt @@ -0,0 +1,19 @@ +--TEST-- +Explicit set property hook $value parameter +--FILE-- +prop = 42; + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/explicit_set_value_parameter_type.phpt b/Zend/tests/property_hooks/explicit_set_value_parameter_type.phpt new file mode 100644 index 0000000000000..b20beab57eb4a --- /dev/null +++ b/Zend/tests/property_hooks/explicit_set_value_parameter_type.phpt @@ -0,0 +1,28 @@ +--TEST-- +Explicit set property hook $value parameter +--FILE-- +prop = is_array($prop) ? join(', ', $prop) : $prop; + } + } +} + +$test = new Test(); +var_dump($test->prop = 'prop'); +var_dump($test->prop = ['prop1', 'prop2']); +var_dump($test->prop); + +?> +--EXPECT-- +string(4) "prop" +array(2) { + [0]=> + string(5) "prop1" + [1]=> + string(5) "prop2" +} +string(12) "prop1, prop2" diff --git a/Zend/tests/property_hooks/field_assign.phpt b/Zend/tests/property_hooks/field_assign.phpt new file mode 100644 index 0000000000000..16e79dad57c83 --- /dev/null +++ b/Zend/tests/property_hooks/field_assign.phpt @@ -0,0 +1,35 @@ +--TEST-- +$field in different assignments +--FILE-- +prop = null; + +?> +--EXPECT-- +int(42) +int(43) +int(41) +int(123) +int(124) +int(123) diff --git a/Zend/tests/property_hooks/field_guard.phpt b/Zend/tests/property_hooks/field_guard.phpt new file mode 100644 index 0000000000000..18a0ffeebb2ad --- /dev/null +++ b/Zend/tests/property_hooks/field_guard.phpt @@ -0,0 +1,27 @@ +--TEST-- +$this->prop refers to backing store from either hook +--FILE-- +prop = 'prop'; + } + set { + echo "set\n"; + var_dump($this->prop); + } + } +} + +$test = new Test; +$test->prop; +$test->prop = 42; + +?> +--EXPECT-- +get +set +string(4) "prop" diff --git a/Zend/tests/property_hooks/final.phpt b/Zend/tests/property_hooks/final.phpt new file mode 100644 index 0000000000000..7829630ce6bc3 --- /dev/null +++ b/Zend/tests/property_hooks/final.phpt @@ -0,0 +1,20 @@ +--TEST-- +Final hooks +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property hook A::$prop::get() in %s on line %d diff --git a/Zend/tests/property_hooks/final_private_prop.phpt b/Zend/tests/property_hooks/final_private_prop.phpt new file mode 100644 index 0000000000000..2687d57531f11 --- /dev/null +++ b/Zend/tests/property_hooks/final_private_prop.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property cannot be both final and private +--FILE-- + +--EXPECTF-- +Fatal error: Property cannot be both final and private in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop.phpt b/Zend/tests/property_hooks/final_prop.phpt new file mode 100644 index 0000000000000..c371cb075b507 --- /dev/null +++ b/Zend/tests/property_hooks/final_prop.phpt @@ -0,0 +1,16 @@ +--TEST-- +Property itself may be marked final (hook) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop_2.phpt b/Zend/tests/property_hooks/final_prop_2.phpt new file mode 100644 index 0000000000000..f605dfdc8a593 --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_2.phpt @@ -0,0 +1,16 @@ +--TEST-- +Property itself may be marked final (normal) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop_final_hook.phpt b/Zend/tests/property_hooks/final_prop_final_hook.phpt new file mode 100644 index 0000000000000..3d5183b8b0b0f --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_final_hook.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot make hook explicitly final in final property +--FILE-- + $field; + } +} + +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/find_property_usage.phpt b/Zend/tests/property_hooks/find_property_usage.phpt new file mode 100644 index 0000000000000..4c4a240afde40 --- /dev/null +++ b/Zend/tests/property_hooks/find_property_usage.phpt @@ -0,0 +1,28 @@ +--TEST-- +Usage of property inside hook adds backing store +--FILE-- + $this->prop1; } + public $prop2 { get => fn () => $this->prop2; } + public $prop3 { get => function () { return $this->prop3; }; } + public $prop4 { get => $this->prop1; } + public $prop5 { get {} } + public $prop6 { get { var_dump($this->prop6); } } + public $prop7 { get => new class { function test() { $this->prop7; } }; } +} + +foreach ((new ReflectionClass(Test::class))->getProperties() as $prop) { + var_dump($prop->isVirtual()); +} + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(true) diff --git a/Zend/tests/property_hooks/foreach.phpt b/Zend/tests/property_hooks/foreach.phpt new file mode 100644 index 0000000000000..c217bf6ac0e28 --- /dev/null +++ b/Zend/tests/property_hooks/foreach.phpt @@ -0,0 +1,136 @@ +--TEST-- +foreach over hooked properties +--FILE-- +_virtualByRef; + } + set { + echo __METHOD__, "\n"; + $this->_virtualByRef = $value; + } + } + public function __construct() { + $this->dynamic = 'dynamic'; + } +} + +#[AllowDynamicProperties] +class ByVal extends ByRef { + private $_virtualByVal = 'virtualByVal'; + public $virtualByVal { + get { + echo __METHOD__, "\n"; + return $this->_virtualByVal; + } + set { + echo __METHOD__, "\n"; + $this->_virtualByVal = $value; + } + } + public $backed = 'backed' { + get { + echo __METHOD__, "\n"; + return $this->backed; + } + set { + echo __METHOD__, "\n"; + $this->backed = $value; + } + } + public string $backedUninitialized { + get { + echo __METHOD__, "\n"; + $this->backedUninitialized ??= 'backedUninitialized'; + return $this->backedUninitialized; + } + set { + echo __METHOD__, "\n"; + $this->backedUninitialized = $value; + } + } +} + +function testByRef($object) { + foreach ($object as $prop => &$value) { + echo "$prop => $value\n"; + $value = strtoupper($value); + } + unset($value); + var_dump($object); +} + +function testByVal($object) { + foreach ($object as $prop => $value) { + echo "$prop => $value\n"; + $object->{$prop} = strtoupper($value); + } + var_dump($object); +} + +testByVal(new ByVal); +testByVal(new ByRef); +testByRef(new ByRef); + +?> +--EXPECTF-- +ByVal::$virtualByVal::get +virtualByVal => virtualByVal +ByVal::$virtualByVal::set +ByVal::$backed::get +backed => backed +ByVal::$backed::set +ByVal::$backedUninitialized::get +backedUninitialized => backedUninitialized +ByVal::$backedUninitialized::set +plain => plain +ByRef::$virtualByRef::get +virtualByRef => virtualByRef +ByRef::$virtualByRef::set +dynamic => dynamic +object(ByVal)#%d (6) { + ["plain"]=> + string(5) "PLAIN" + ["_virtualByRef":"ByRef":private]=> + string(12) "VIRTUALBYREF" + ["_virtualByVal":"ByVal":private]=> + string(12) "VIRTUALBYVAL" + ["backed"]=> + string(6) "BACKED" + ["backedUninitialized"]=> + string(19) "BACKEDUNINITIALIZED" + ["dynamic"]=> + string(7) "DYNAMIC" +} +plain => plain +ByRef::$virtualByRef::get +virtualByRef => virtualByRef +ByRef::$virtualByRef::set +dynamic => dynamic +object(ByRef)#%d (3) { + ["plain"]=> + string(5) "PLAIN" + ["_virtualByRef":"ByRef":private]=> + string(12) "VIRTUALBYREF" + ["dynamic"]=> + string(7) "DYNAMIC" +} +plain => plain +ByRef::$virtualByRef::get +virtualByRef => virtualByRef +dynamic => dynamic +object(ByRef)#%d (3) { + ["plain"]=> + string(5) "PLAIN" + ["_virtualByRef":"ByRef":private]=> + string(12) "VIRTUALBYREF" + ["dynamic"]=> + string(7) "DYNAMIC" +} diff --git a/Zend/tests/property_hooks/foreach_002.phpt b/Zend/tests/property_hooks/foreach_002.phpt new file mode 100644 index 0000000000000..663e7d4bbf699 --- /dev/null +++ b/Zend/tests/property_hooks/foreach_002.phpt @@ -0,0 +1,23 @@ +--TEST-- +foreach over hooked properties +--FILE-- + $this->foo; + } +} + +$a = new A; +foreach ($a as $k => &$v) { + if ($k == "foo") { + $a->bar = "baz"; + } + var_dump($k); +} + +?> +--EXPECT-- +string(3) "foo" +string(3) "bar" diff --git a/Zend/tests/property_hooks/foreach_val_to_ref.phpt b/Zend/tests/property_hooks/foreach_val_to_ref.phpt new file mode 100644 index 0000000000000..dba71412579ff --- /dev/null +++ b/Zend/tests/property_hooks/foreach_val_to_ref.phpt @@ -0,0 +1,34 @@ +--TEST-- +foreach by-ref on object with by-val hooked property +--FILE-- + $this->byVal; + set => $value; + } +} + +function test($object) { + foreach ($object as $prop => &$value) { + var_dump($value); + } +} + +try { + test(new ByVal); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +int(42) +Cannot create reference to property ByVal::$byVal diff --git a/Zend/tests/property_hooks/generator_hook.phpt b/Zend/tests/property_hooks/generator_hook.phpt new file mode 100644 index 0000000000000..8fe3e095590ed --- /dev/null +++ b/Zend/tests/property_hooks/generator_hook.phpt @@ -0,0 +1,45 @@ +--TEST-- +Generator hook +--FILE-- +backed; + yield 3; + } + } + + public $virtual { + get { + yield 1; + yield 2; + yield 3; + } + } +} + +$a = new A(); +var_dump(iterator_to_array($a->backed)); +var_dump(iterator_to_array($a->virtual)); + +?> +--EXPECT-- +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} diff --git a/Zend/tests/property_hooks/generator_hook_002.phpt b/Zend/tests/property_hooks/generator_hook_002.phpt new file mode 100644 index 0000000000000..0eeb99c8cf9b4 --- /dev/null +++ b/Zend/tests/property_hooks/generator_hook_002.phpt @@ -0,0 +1,29 @@ +--TEST-- +Allow calling parent get in generator property hooks +--FILE-- +prop as $value) { + var_dump($value); +} + +?> +--EXPECT-- +int(43) +int(44) +int(45) diff --git a/Zend/tests/property_hooks/get.phpt b/Zend/tests/property_hooks/get.phpt new file mode 100644 index 0000000000000..0cb0a6b7c76be --- /dev/null +++ b/Zend/tests/property_hooks/get.phpt @@ -0,0 +1,24 @@ +--TEST-- +Basic get only property hook +--FILE-- +prop); + +try { + $test->prop = 0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +int(42) +Property Test::$prop is read-only diff --git a/Zend/tests/property_hooks/get_by_ref.phpt b/Zend/tests/property_hooks/get_by_ref.phpt new file mode 100644 index 0000000000000..f602721a791dd --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref.phpt @@ -0,0 +1,34 @@ +--TEST-- +Get property hook by ref and indirect modification +--FILE-- +byVal; } + set { $this->byVal = $value; } + } +} + +$test = new Test; + +try { + $test->byVal = []; + $test->byVal[] = 42; +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->byVal); + +try { + $test->byVal =& $ref; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Indirect modification of Test::$byVal is not allowed +array(0) { +} +Cannot assign by reference to overloaded object diff --git a/Zend/tests/property_hooks/get_by_ref_auto.phpt b/Zend/tests/property_hooks/get_by_ref_auto.phpt new file mode 100644 index 0000000000000..0dabe4c45211a --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref_auto.phpt @@ -0,0 +1,30 @@ +--TEST-- +Get by reference with hooked property +--FILE-- +byVal[] = 42; +} catch (\Error $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; +} +var_dump($test->byVal); + +try { + $test->byVal =& $ref; +} catch (Error $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Error: Indirect modification of Test::$byVal is not allowed +array(0) { +} +Error: Cannot assign by reference to overloaded object diff --git a/Zend/tests/property_hooks/get_by_ref_backed.phpt b/Zend/tests/property_hooks/get_by_ref_backed.phpt new file mode 100644 index 0000000000000..5264db8854c02 --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref_backed.phpt @@ -0,0 +1,20 @@ +--TEST-- +Virtual get hook allows returning by reference +--FILE-- + $this->_prop; + set { $this->_prop = $value; } + } +} + +?> +--EXPECTF-- +Fatal error: Get hook of backed property B::prop with set hook may not return by reference in %s on line %d diff --git a/Zend/tests/property_hooks/get_by_ref_implemented_by_plain.phpt b/Zend/tests/property_hooks/get_by_ref_implemented_by_plain.phpt new file mode 100644 index 0000000000000..fe5405eba4002 --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref_implemented_by_plain.phpt @@ -0,0 +1,28 @@ +--TEST-- +By-reference get may be implemented as plain +--FILE-- +prop; + $ref = 42; +} + +$a = new A(); +test($a); +var_dump($a); + +?> +--EXPECT-- +object(A)#1 (1) { + ["prop"]=> + int(42) +} diff --git a/Zend/tests/property_hooks/get_by_ref_implemented_by_val.phpt b/Zend/tests/property_hooks/get_by_ref_implemented_by_val.phpt new file mode 100644 index 0000000000000..84eb968263546 --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref_implemented_by_val.phpt @@ -0,0 +1,18 @@ +--TEST-- +By-reference get may be not implemented as by-value +--FILE-- + $this->prop; + } +} + +?> +--EXPECTF-- +Fatal error: Declaration of A::$prop::get() must be compatible with & I::$prop::get() in %s on line %d diff --git a/Zend/tests/property_hooks/get_by_ref_virtual.phpt b/Zend/tests/property_hooks/get_by_ref_virtual.phpt new file mode 100644 index 0000000000000..dd6a4bc0caffb --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref_virtual.phpt @@ -0,0 +1,42 @@ +--TEST-- +Virtual get hook allows returning by reference +--FILE-- + $this->_prop; + set { $this->_prop = $value; } + } +} + +function inc(&$ref) { + $ref++; +} + +$test = new Test(); +$test->prop = 42; + +$prop = &$test->prop; +$prop++; +var_dump($test); +var_dump($test->prop); +unset($prop); + +inc($test->prop); +var_dump($test); +var_dump($test->prop); + +?> +--EXPECT-- +object(Test)#1 (1) { + ["_prop":"Test":private]=> + &int(43) +} +int(43) +object(Test)#1 (1) { + ["_prop":"Test":private]=> + int(44) +} +int(44) diff --git a/Zend/tests/property_hooks/get_type_check.phpt b/Zend/tests/property_hooks/get_type_check.phpt new file mode 100644 index 0000000000000..2e0e9b9fcf6ea --- /dev/null +++ b/Zend/tests/property_hooks/get_type_check.phpt @@ -0,0 +1,26 @@ +--TEST-- +Get property hook must respect property type +--FILE-- +prop1); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->prop2); + +?> +--EXPECT-- +Test::$prop1::get(): Return value must be of type int, string returned +int(42) diff --git a/Zend/tests/property_hooks/hooked_with_magic_method.phpt b/Zend/tests/property_hooks/hooked_with_magic_method.phpt new file mode 100644 index 0000000000000..8e5a1ca87b23c --- /dev/null +++ b/Zend/tests/property_hooks/hooked_with_magic_method.phpt @@ -0,0 +1,28 @@ +--TEST-- +Access hooked property from magic method +--FILE-- +{$name}; + } + + public function __set($name, $value) { + $this->{$name} = $value; + } +} + +$test = new Test; +$test->prop; +$test->prop = 42; + +?> +--EXPECT-- +Test::$prop::get +Test::$prop::set diff --git a/Zend/tests/property_hooks/indirect_modification.phpt b/Zend/tests/property_hooks/indirect_modification.phpt new file mode 100644 index 0000000000000..2fb8bda77a24e --- /dev/null +++ b/Zend/tests/property_hooks/indirect_modification.phpt @@ -0,0 +1,51 @@ +--TEST-- +Different kinds of indirect modification with by-val and by-ref getters +--FILE-- +byVal = $value; + } + } +} + +$test = new Test; + +$test->byVal = 0; +$test->byVal++; +++$test->byVal; +$test->byVal += 1; +var_dump($test->byVal); +$test->byVal = []; +try { + $test->byVal[] = 1; +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->byVal); +try { + $ref =& $test->byVal; +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +$ref = 42; +var_dump($test->byVal); + +?> +--EXPECT-- +Test::$byVal::set +Test::$byVal::set +Test::$byVal::set +Test::$byVal::set +int(3) +Test::$byVal::set +Indirect modification of Test::$byVal is not allowed +array(0) { +} +Indirect modification of Test::$byVal is not allowed +array(0) { +} diff --git a/Zend/tests/property_hooks/inheritance.phpt b/Zend/tests/property_hooks/inheritance.phpt new file mode 100644 index 0000000000000..ef6f7bac9c480 --- /dev/null +++ b/Zend/tests/property_hooks/inheritance.phpt @@ -0,0 +1,32 @@ +--TEST-- +Basic property hook inheritance +--FILE-- +prop); +$a->prop = 1; + +$b = new B; +var_dump($b->prop); +$b->prop = 1; + +?> +--EXPECT-- +string(1) "A" +A::$prop::set +string(1) "B" +A::$prop::set diff --git a/Zend/tests/property_hooks/interface.phpt b/Zend/tests/property_hooks/interface.phpt new file mode 100644 index 0000000000000..8cd0aa12ed4d0 --- /dev/null +++ b/Zend/tests/property_hooks/interface.phpt @@ -0,0 +1,15 @@ +--TEST-- +Property hooks in interfaces +--FILE-- + +--EXPECTF-- +Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (I::$prop::get, I::$prop::set) in %s on line %d diff --git a/Zend/tests/property_hooks/interface_explicit_abstract.phpt b/Zend/tests/property_hooks/interface_explicit_abstract.phpt new file mode 100644 index 0000000000000..fa7a7337260a5 --- /dev/null +++ b/Zend/tests/property_hooks/interface_explicit_abstract.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot have explicitly abstract property in interface +--FILE-- + +--EXPECTF-- +Fatal error: Property in interface cannot be explicitly abstract. All interface members are implicitly abstract in %s on line %d diff --git a/Zend/tests/property_hooks/interface_final_hook.phpt b/Zend/tests/property_hooks/interface_final_hook.phpt new file mode 100644 index 0000000000000..0cf2693c1c5cf --- /dev/null +++ b/Zend/tests/property_hooks/interface_final_hook.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cannot declare final hook in interface +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both abstract and final in %s on line %d diff --git a/Zend/tests/property_hooks/interface_final_prop.phpt b/Zend/tests/property_hooks/interface_final_prop.phpt new file mode 100644 index 0000000000000..ef664da7ab71a --- /dev/null +++ b/Zend/tests/property_hooks/interface_final_prop.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cannot declare final property in interface +--FILE-- + +--EXPECTF-- +Fatal error: Property in interface cannot be final in %s on line %d diff --git a/Zend/tests/property_hooks/interface_get_by_ref_backed.phpt b/Zend/tests/property_hooks/interface_get_by_ref_backed.phpt new file mode 100644 index 0000000000000..5d1f90d1afe51 --- /dev/null +++ b/Zend/tests/property_hooks/interface_get_by_ref_backed.phpt @@ -0,0 +1,24 @@ +--TEST-- +Backed prop satisfies interface get hook by-reference +--FILE-- + $this->prop; + } +} + +$a = new A(); +var_dump($a); + +?> +--EXPECT-- +object(A)#1 (1) { + ["prop"]=> + int(42) +} diff --git a/Zend/tests/property_hooks/interface_get_by_ref_plain.phpt b/Zend/tests/property_hooks/interface_get_by_ref_plain.phpt new file mode 100644 index 0000000000000..5080dd6e2e413 --- /dev/null +++ b/Zend/tests/property_hooks/interface_get_by_ref_plain.phpt @@ -0,0 +1,24 @@ +--TEST-- +Plain prop satisfies interface get hook by-reference +--FILE-- + $this->prop; + } +} + +$a = new A(); +var_dump($a); + +?> +--EXPECT-- +object(A)#1 (1) { + ["prop"]=> + int(42) +} diff --git a/Zend/tests/property_hooks/interface_get_by_ref_virtual.phpt b/Zend/tests/property_hooks/interface_get_by_ref_virtual.phpt new file mode 100644 index 0000000000000..040249b35e81b --- /dev/null +++ b/Zend/tests/property_hooks/interface_get_by_ref_virtual.phpt @@ -0,0 +1,31 @@ +--TEST-- +Virtual prop satisfies interface get hook by-reference +--FILE-- + $this->_prop; + } +} + +function test(I $i) { + $ref = &$i->prop; + $ref = 42; +} + +$a = new A(); +test($a); +var_dump($a); + +?> +--EXPECT-- +object(A)#1 (1) { + ["_prop":"A":private]=> + int(42) +} diff --git a/Zend/tests/property_hooks/interface_get_only.phpt b/Zend/tests/property_hooks/interface_get_only.phpt new file mode 100644 index 0000000000000..c6bc45fc87528 --- /dev/null +++ b/Zend/tests/property_hooks/interface_get_only.phpt @@ -0,0 +1,15 @@ +--TEST-- +Interface may contain only set with no implementation +--FILE-- + +--EXPECTF-- diff --git a/Zend/tests/property_hooks/interface_get_only_readonly.phpt b/Zend/tests/property_hooks/interface_get_only_readonly.phpt new file mode 100644 index 0000000000000..38ac375a4957c --- /dev/null +++ b/Zend/tests/property_hooks/interface_get_only_readonly.phpt @@ -0,0 +1,15 @@ +--TEST-- +readonly property satisfies get only interface property +--FILE-- +prop); +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/interface_get_set_readonly.phpt b/Zend/tests/property_hooks/interface_get_set_readonly.phpt new file mode 100644 index 0000000000000..d957f30360efa --- /dev/null +++ b/Zend/tests/property_hooks/interface_get_set_readonly.phpt @@ -0,0 +1,13 @@ +--TEST-- +readonly property does not satisfy get/set interface property +--FILE-- + +--EXPECTF-- +Fatal error: Readonly property C::$prop does not satisfy abstract read-write property I::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/interface_get_value_as_ref.phpt b/Zend/tests/property_hooks/interface_get_value_as_ref.phpt new file mode 100644 index 0000000000000..c81793f2b1dc3 --- /dev/null +++ b/Zend/tests/property_hooks/interface_get_value_as_ref.phpt @@ -0,0 +1,31 @@ +--TEST-- +By-value get may be implemented as by-reference +--FILE-- + $this->_prop; + } +} + +function test(I $i) { + $ref = &$i->prop; + $ref = 42; +} + +$a = new A(); +test($a); +var_dump($a); + +?> +--EXPECT-- +object(A)#1 (1) { + ["_prop":"A":private]=> + int(42) +} diff --git a/Zend/tests/property_hooks/interface_invalid_explicitly_abstract.phpt b/Zend/tests/property_hooks/interface_invalid_explicitly_abstract.phpt new file mode 100644 index 0000000000000..ed8a6562e0750 --- /dev/null +++ b/Zend/tests/property_hooks/interface_invalid_explicitly_abstract.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hooks in interfaces cannot be explicitly abstract +--FILE-- + +--EXPECTF-- +Fatal error: Property in interface cannot be explicitly abstract. All interface members are implicitly abstract in %s on line %d diff --git a/Zend/tests/property_hooks/interface_not_implemented.phpt b/Zend/tests/property_hooks/interface_not_implemented.phpt new file mode 100644 index 0000000000000..3a9803039b97d --- /dev/null +++ b/Zend/tests/property_hooks/interface_not_implemented.phpt @@ -0,0 +1,15 @@ +--TEST-- +Property hook in interfaces not implemented +--FILE-- + +--EXPECTF-- +Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (I::$prop::get, I::$prop::set) in %s on line %d diff --git a/Zend/tests/property_hooks/interface_not_public.phpt b/Zend/tests/property_hooks/interface_not_public.phpt new file mode 100644 index 0000000000000..c652766f3314d --- /dev/null +++ b/Zend/tests/property_hooks/interface_not_public.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use non-public property hook in interface (whole property) +--FILE-- + +--EXPECTF-- +Fatal error: Property in interface cannot be protected or private in %s on line %d diff --git a/Zend/tests/property_hooks/interface_set_only.phpt b/Zend/tests/property_hooks/interface_set_only.phpt new file mode 100644 index 0000000000000..62423842b34f2 --- /dev/null +++ b/Zend/tests/property_hooks/interface_set_only.phpt @@ -0,0 +1,15 @@ +--TEST-- +Interface may contain only get with no implementation +--FILE-- + +--EXPECTF-- diff --git a/Zend/tests/property_hooks/invalid_abstract.phpt b/Zend/tests/property_hooks/invalid_abstract.phpt new file mode 100644 index 0000000000000..cd53089c77e4b --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract.phpt @@ -0,0 +1,41 @@ +--TEST-- +Implementing abstract property hooks +--FILE-- +prop1; +$b->prop1 = 1; +$b->prop2; +$b->prop2 = 1; +$b->prop3; +$b->prop3 = 1; + +?> +--EXPECT-- +B::$prop1::get +A::$prop1::set +A::$prop2::get +B::$prop2::set diff --git a/Zend/tests/property_hooks/invalid_abstract_body.phpt b/Zend/tests/property_hooks/invalid_abstract_body.phpt new file mode 100644 index 0000000000000..e11ad4790b2d6 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_body.phpt @@ -0,0 +1,14 @@ +--TEST-- +Abstract property hook cannot have body +--FILE-- + +--EXPECTF-- +Fatal error: Abstract property Test::$prop must specify at least one abstract hook in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_abstract_final.phpt b/Zend/tests/property_hooks/invalid_abstract_final.phpt new file mode 100644 index 0000000000000..dd1c6482b0f54 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_final.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hook cannot be both abstract and final +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both abstract and final in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_abstract_indirect.phpt b/Zend/tests/property_hooks/invalid_abstract_indirect.phpt new file mode 100644 index 0000000000000..834440a7f6885 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_indirect.phpt @@ -0,0 +1,16 @@ +--TEST-- +Class with abstract property hook not declared abstract (inherited 1) +--FILE-- + +--EXPECTF-- +Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_abstract_indirect_2.phpt b/Zend/tests/property_hooks/invalid_abstract_indirect_2.phpt new file mode 100644 index 0000000000000..28938c17540b5 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_indirect_2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Class with abstract property hook not declared abstract (inherited 2) +--FILE-- + +--EXPECTF-- +Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_abstract_private.phpt b/Zend/tests/property_hooks/invalid_abstract_private.phpt new file mode 100644 index 0000000000000..358477a6a9b0b --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_private.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hook cannot be both abstract and private +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both abstract and private in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_empty_hooks.phpt b/Zend/tests/property_hooks/invalid_empty_hooks.phpt new file mode 100644 index 0000000000000..c549536b4d61e --- /dev/null +++ b/Zend/tests/property_hooks/invalid_empty_hooks.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hook list cannot be empty +--FILE-- + +--EXPECTF-- +Fatal error: Property hook list cannot be empty in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_final_private.phpt b/Zend/tests/property_hooks/invalid_final_private.phpt new file mode 100644 index 0000000000000..f71a4fe7d53e4 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_final_private.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hook cannot be both final and private +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both final and private in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_hook_visibility.phpt b/Zend/tests/property_hooks/invalid_hook_visibility.phpt new file mode 100644 index 0000000000000..a5b1e721ff8d4 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_hook_visibility.phpt @@ -0,0 +1,14 @@ +--TEST-- +Property hooks cannot have explicity visibility +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the public modifier on a property hook in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_static.phpt b/Zend/tests/property_hooks/invalid_static.phpt new file mode 100644 index 0000000000000..a694269cd90fe --- /dev/null +++ b/Zend/tests/property_hooks/invalid_static.phpt @@ -0,0 +1,14 @@ +--TEST-- +Property hook cannot be static +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the static modifier on a property hook in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_static_prop.phpt b/Zend/tests/property_hooks/invalid_static_prop.phpt new file mode 100644 index 0000000000000..c7e4b10055390 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_static_prop.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use hooks for static property (for now) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot declare hooks for static property in %s on line %d diff --git a/Zend/tests/property_hooks/isset.phpt b/Zend/tests/property_hooks/isset.phpt new file mode 100644 index 0000000000000..1669d3616247d --- /dev/null +++ b/Zend/tests/property_hooks/isset.phpt @@ -0,0 +1,37 @@ +--TEST-- +isset() and empty() call get property hook +--FILE-- +prop1; } + set { $this->prop1 = $value; } + } +} + +$test = new Test; + +$test->prop1 = true; +var_dump(isset($test->prop1)); +var_dump(!empty($test->prop1)); +echo "\n", +$test->prop1 = false; +var_dump(isset($test->prop1)); +var_dump(!empty($test->prop1)); +echo "\n", +$test->prop1 = null; +var_dump(isset($test->prop1)); +var_dump(!empty($test->prop1)); +echo "\n"; + +?> +--EXPECT-- +bool(true) +bool(true) + +bool(true) +bool(false) + +bool(false) +bool(false) diff --git a/Zend/tests/property_hooks/magic_consts.phpt b/Zend/tests/property_hooks/magic_consts.phpt new file mode 100644 index 0000000000000..4865927c27109 --- /dev/null +++ b/Zend/tests/property_hooks/magic_consts.phpt @@ -0,0 +1,24 @@ +--TEST-- +Magic constants in property hooks +--FILE-- +prop; + +?> +--EXPECT-- +string(10) "$prop::get" +string(16) "Test::$prop::get" +string(4) "Test" diff --git a/Zend/tests/property_hooks/magic_interaction.phpt b/Zend/tests/property_hooks/magic_interaction.phpt new file mode 100644 index 0000000000000..7ea9e60f89691 --- /dev/null +++ b/Zend/tests/property_hooks/magic_interaction.phpt @@ -0,0 +1,62 @@ +--TEST-- +Interaction of inaccessible property hooks with magic methods +--FILE-- +$name; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } + public function __set($name, $value) { + echo __METHOD__, "($name, $value)\n"; + try { + $this->$name = $value; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } + public function __isset($name) { + echo __METHOD__, "($name)\n"; + try { + var_dump(isset($this->$name)); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } + public function __unset($name) { + echo "Never reached\n"; + } +} + +$b = new B; +$b->prop; +var_dump(isset($b->prop)); +$b->prop = 1; +try { + unset($b->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +A::$prop::get +A::$prop::get +bool(true) +A::$prop::set +Cannot unset hooked property B::$prop diff --git a/Zend/tests/property_hooks/magic_method_from_hooked.phpt b/Zend/tests/property_hooks/magic_method_from_hooked.phpt new file mode 100644 index 0000000000000..41ce4bd3846e8 --- /dev/null +++ b/Zend/tests/property_hooks/magic_method_from_hooked.phpt @@ -0,0 +1,30 @@ +--TEST-- +Accessing property from hook does not call magic method +--FILE-- + $this->prop; + set => $value; + } + + public function __get($name) { + echo __METHOD__, "\n"; + return 42; + } + + public function __set($name, $value) { + echo __METHOD__, "\n"; + } +} + +$test = new Test; +var_dump($test->prop); +$test->prop = 42; +var_dump($test->prop); + +?> +--EXPECT-- +NULL +int(42) diff --git a/Zend/tests/property_hooks/no_default_value_untyped_001.phpt b/Zend/tests/property_hooks/no_default_value_untyped_001.phpt new file mode 100644 index 0000000000000..51a113891027e --- /dev/null +++ b/Zend/tests/property_hooks/no_default_value_untyped_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +Hooked properties with no default value are initialized to null +--FILE-- + $this->prop; + set => $value; + } +} + +$test = new Test; +var_dump($test->prop); + +?> +--EXPECT-- +NULL diff --git a/Zend/tests/property_hooks/no_default_value_untyped_002.phpt b/Zend/tests/property_hooks/no_default_value_untyped_002.phpt new file mode 100644 index 0000000000000..184a47a67059f --- /dev/null +++ b/Zend/tests/property_hooks/no_default_value_untyped_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +Hooked properties with no default value are initialized to null +--FILE-- +prop); + +?> +--EXPECT-- +NULL diff --git a/Zend/tests/property_hooks/no_get_parameters.phpt b/Zend/tests/property_hooks/no_get_parameters.phpt new file mode 100644 index 0000000000000..6755ec3c95cf7 --- /dev/null +++ b/Zend/tests/property_hooks/no_get_parameters.phpt @@ -0,0 +1,19 @@ +--TEST-- +No get property hook parameters +--FILE-- +prop = 42; + +?> +--EXPECTF-- +Fatal error: get hook of property Test::$prop must not have a parameter list in %s on line %d diff --git a/Zend/tests/property_hooks/object_in_hook.phpt b/Zend/tests/property_hooks/object_in_hook.phpt new file mode 100644 index 0000000000000..79ab5719e40ba --- /dev/null +++ b/Zend/tests/property_hooks/object_in_hook.phpt @@ -0,0 +1,41 @@ +--TEST-- +Assign on object inside property hook is ok +--FILE-- + $value\n"; + } + public function offsetUnset(mixed $offset): void { + echo "Unsetting $offset\n"; + } +} + +class C { + public $a { + get { return $this->a; } + set { $this->a = $value; } + } +} + +$c = new C; +$c->a = new A(); + +$c->a->b = 'b'; +var_dump($c->a->b); + +var_dump($c->a['foo']); +$c->a['foo'] = 'foo'; +unset($c->a['foo']); + +?> +--EXPECT-- +string(1) "b" +string(3) "foo" +Setting foo => foo +Unsetting foo diff --git a/Zend/tests/property_hooks/override_add_get.phpt b/Zend/tests/property_hooks/override_add_get.phpt new file mode 100644 index 0000000000000..28dbd047c0718 --- /dev/null +++ b/Zend/tests/property_hooks/override_add_get.phpt @@ -0,0 +1,37 @@ +--TEST-- +Overridden hooked property can add get to set only property +--FILE-- +prop = 1; +try { + var_dump($a->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$b = new B; +$b->prop = 1; +var_dump($b->prop); + +?> +--EXPECT-- +A::A::$prop::set +Property A::$prop is write-only +B::B::$prop::set +B::B::$prop::get +int(42) diff --git a/Zend/tests/property_hooks/override_add_get_contravariant.phpt b/Zend/tests/property_hooks/override_add_get_contravariant.phpt new file mode 100644 index 0000000000000..24cfec57c5852 --- /dev/null +++ b/Zend/tests/property_hooks/override_add_get_contravariant.phpt @@ -0,0 +1,22 @@ +--TEST-- +Overridden hooked property that adds get to set only property becomes invariant +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/override_add_set.phpt b/Zend/tests/property_hooks/override_add_set.phpt new file mode 100644 index 0000000000000..6b490d4d87f94 --- /dev/null +++ b/Zend/tests/property_hooks/override_add_set.phpt @@ -0,0 +1,38 @@ +--TEST-- +Overridden hooked property can add set to get only property +--FILE-- +prop = 1; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($a->prop); + +$b = new B; +$b->prop = 1; +var_dump($b->prop); + +?> +--EXPECT-- +Property A::$prop is read-only +A::A::$prop::get +int(42) +B::B::$prop::set +B::B::$prop::get +int(42) diff --git a/Zend/tests/property_hooks/override_add_set_covariant.phpt b/Zend/tests/property_hooks/override_add_set_covariant.phpt new file mode 100644 index 0000000000000..856f7f2645ac2 --- /dev/null +++ b/Zend/tests/property_hooks/override_add_set_covariant.phpt @@ -0,0 +1,22 @@ +--TEST-- +Overridden hooked property that adds set to get only property becomes invariant +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/override_attribute_backed.phpt b/Zend/tests/property_hooks/override_attribute_backed.phpt new file mode 100644 index 0000000000000..008f5a4240bcf --- /dev/null +++ b/Zend/tests/property_hooks/override_attribute_backed.phpt @@ -0,0 +1,24 @@ +--TEST-- +Override attribute is satisfied by backed property +--FILE-- +prop = 42; + } + } +} + +class B extends A { + public $prop { + #[Override] + get => parent::$prop::get(); + } +} + +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/override_attribute_fail.phpt b/Zend/tests/property_hooks/override_attribute_fail.phpt new file mode 100644 index 0000000000000..ecd5cc60901fb --- /dev/null +++ b/Zend/tests/property_hooks/override_attribute_fail.phpt @@ -0,0 +1,21 @@ +--TEST-- +Override attribute is not satisfied by unilateral virtual property +--FILE-- + parent::$prop::get(); + } +} + +?> +--EXPECTF-- +Fatal error: B::$prop::get() has #[\Override] attribute, but no matching parent method exists in %s on line %d diff --git a/Zend/tests/property_hooks/override_attribute_plain.phpt b/Zend/tests/property_hooks/override_attribute_plain.phpt new file mode 100644 index 0000000000000..303a7447d528f --- /dev/null +++ b/Zend/tests/property_hooks/override_attribute_plain.phpt @@ -0,0 +1,20 @@ +--TEST-- +Override attribute is satisfied by plain property +--FILE-- + parent::$prop::get(); + } +} + +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/override_attribute_virtual.phpt b/Zend/tests/property_hooks/override_attribute_virtual.phpt new file mode 100644 index 0000000000000..23a85baedb4e2 --- /dev/null +++ b/Zend/tests/property_hooks/override_attribute_virtual.phpt @@ -0,0 +1,22 @@ +--TEST-- +Override attribute is satisfied by virtual hook +--FILE-- + 42; + } +} + +class B extends A { + public $prop { + #[Override] + get => parent::$prop::get(); + } +} + +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/override_by_plain_prop.phpt b/Zend/tests/property_hooks/override_by_plain_prop.phpt new file mode 100644 index 0000000000000..82d342d20a2dc --- /dev/null +++ b/Zend/tests/property_hooks/override_by_plain_prop.phpt @@ -0,0 +1,30 @@ +--TEST-- +Hooks must not be overridden by a plain property +--FILE-- +prop = 43; +var_dump($a->prop); + +$b = new B(); +$b->prop = 43; +var_dump($b->prop); + +?> +--EXPECT-- +A::$prop::set +A::$prop::get +int(42) +int(43) diff --git a/Zend/tests/property_hooks/override_default_value.phpt b/Zend/tests/property_hooks/override_default_value.phpt new file mode 100644 index 0000000000000..8ae6502580281 --- /dev/null +++ b/Zend/tests/property_hooks/override_default_value.phpt @@ -0,0 +1,36 @@ +--TEST-- +Property with hook may override default value +--FILE-- +prop); +$b->prop = 44; +var_dump($b); +var_dump($b->prop); + +?> +--EXPECT-- +object(B)#1 (1) { + ["prop"]=> + int(42) +} +int(43) +object(B)#1 (1) { + ["prop"]=> + int(44) +} +int(43) diff --git a/Zend/tests/property_hooks/override_implicit_with_explicit.phpt b/Zend/tests/property_hooks/override_implicit_with_explicit.phpt new file mode 100644 index 0000000000000..068f1d5341204 --- /dev/null +++ b/Zend/tests/property_hooks/override_implicit_with_explicit.phpt @@ -0,0 +1,30 @@ +--TEST-- +Property can be overridden by hooked property +--FILE-- +prop = 1; +var_dump($a->prop); + +$b = new B; +$b->prop = 2; +var_dump($b->prop); + +?> +--EXPECT-- +int(1) +B::B::$prop::set +B::B::$prop::get +int(3) diff --git a/Zend/tests/property_hooks/override_plain_set.phpt b/Zend/tests/property_hooks/override_plain_set.phpt new file mode 100644 index 0000000000000..b92d09b810b9c --- /dev/null +++ b/Zend/tests/property_hooks/override_plain_set.phpt @@ -0,0 +1,23 @@ +--TEST-- +Overridden set of plain property +--FILE-- +prop = 1; +var_dump($b->prop); + +?> +--EXPECT-- +B::B::$prop::set +NULL diff --git a/Zend/tests/property_hooks/parameter_attributes.phpt b/Zend/tests/property_hooks/parameter_attributes.phpt new file mode 100644 index 0000000000000..209966841b28f --- /dev/null +++ b/Zend/tests/property_hooks/parameter_attributes.phpt @@ -0,0 +1,26 @@ +--TEST-- +Hook parameters accept parameter-targeted attributes +--FILE-- +prop = 'secret'; +} catch (Exception $e) { + echo $e; +} + +?> +--EXPECTF-- +Exception: Exception from $prop in %s:%d +Stack trace: +#0 %s(%d): C->$prop::set(Object(SensitiveParameterValue)) +#1 {main} diff --git a/Zend/tests/property_hooks/parent_get.phpt b/Zend/tests/property_hooks/parent_get.phpt new file mode 100644 index 0000000000000..b42bd10f24007 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get.phpt @@ -0,0 +1,31 @@ +--TEST-- +Allow calling parent get in property hooks +--FILE-- +prop); + +$b = new B; +var_dump($b->prop); + +?> +--EXPECT-- +int(41) +int(42) diff --git a/Zend/tests/property_hooks/parent_get_ci.phpt b/Zend/tests/property_hooks/parent_get_ci.phpt new file mode 100644 index 0000000000000..30178a2f32def --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_ci.phpt @@ -0,0 +1,31 @@ +--TEST-- +parent::$prop::hook() syntax is case-insensitive +--FILE-- +prop); + +$b = new B; +var_dump($b->prop); + +?> +--EXPECT-- +int(41) +int(42) diff --git a/Zend/tests/property_hooks/parent_get_in_class_with_no_parent.phpt b/Zend/tests/property_hooks/parent_get_in_class_with_no_parent.phpt new file mode 100644 index 0000000000000..767e585f91999 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_in_class_with_no_parent.phpt @@ -0,0 +1,21 @@ +--TEST-- +Using parent::$prop::get() in class with no parent +--FILE-- + parent::$prop::get(); + } +} + +$foo = new Foo(); +try { + var_dump($foo->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot use "parent" when current class scope has no parent diff --git a/Zend/tests/property_hooks/parent_get_not_in_class.phpt b/Zend/tests/property_hooks/parent_get_not_in_class.phpt new file mode 100644 index 0000000000000..56a079b8b33ed --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_not_in_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +Using parent::$prop::get() outside of class context +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "parent" when no class scope is active in %s on line %d diff --git a/Zend/tests/property_hooks/parent_get_plain.phpt b/Zend/tests/property_hooks/parent_get_plain.phpt new file mode 100644 index 0000000000000..f461b39a8a372 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_plain.phpt @@ -0,0 +1,21 @@ +--TEST-- +Using parent::$prop::get() on plain property +--FILE-- + parent::$prop::get(); + } +} + +$c = new C(); +var_dump($c->prop); + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/parent_get_plain_typed_uninitialized.phpt b/Zend/tests/property_hooks/parent_get_plain_typed_uninitialized.phpt new file mode 100644 index 0000000000000..0edd8ff074efd --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_plain_typed_uninitialized.phpt @@ -0,0 +1,25 @@ +--TEST-- +Using parent::$prop::get() on plain uninitialized typed property +--FILE-- + parent::$prop::get(); + } +} + +$c = new C(); +try { + var_dump($c->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Typed property C::$prop must not be accessed before initialization diff --git a/Zend/tests/property_hooks/parent_get_plain_untyped_uninitialized.phpt b/Zend/tests/property_hooks/parent_get_plain_untyped_uninitialized.phpt new file mode 100644 index 0000000000000..c1f8a9a5571e1 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_plain_untyped_uninitialized.phpt @@ -0,0 +1,21 @@ +--TEST-- +Using parent::$prop::get() on plain untyped uninitialized property +--FILE-- + parent::$prop::get(); + } +} + +$c = new C(); +var_dump($c->prop); + +?> +--EXPECT-- +NULL diff --git a/Zend/tests/property_hooks/parent_get_plain_zpp.phpt b/Zend/tests/property_hooks/parent_get_plain_zpp.phpt new file mode 100644 index 0000000000000..04ac779f607b3 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_plain_zpp.phpt @@ -0,0 +1,27 @@ +--TEST-- +parent::$prop::get() ZPP +--FILE-- +prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +A::$prop::get() expects exactly 0 arguments, 1 given diff --git a/Zend/tests/property_hooks/parent_get_rw.phpt b/Zend/tests/property_hooks/parent_get_rw.phpt new file mode 100644 index 0000000000000..cce53ed4a445e --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_rw.phpt @@ -0,0 +1,27 @@ +--TEST-- +RW on parent get is forbidden +--FILE-- +prop); + +?> +--EXPECTF-- +Fatal error: Can't use method return value in write context in %s on line %d diff --git a/Zend/tests/property_hooks/parent_get_undefined_property.phpt b/Zend/tests/property_hooks/parent_get_undefined_property.phpt new file mode 100644 index 0000000000000..388448963e3e8 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_undefined_property.phpt @@ -0,0 +1,25 @@ +--TEST-- +Using parent::$prop::get() with undefined property +--FILE-- +prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Undefined property P::$prop diff --git a/Zend/tests/property_hooks/parent_in_different_hook.phpt b/Zend/tests/property_hooks/parent_in_different_hook.phpt new file mode 100644 index 0000000000000..5a9d872abe84f --- /dev/null +++ b/Zend/tests/property_hooks/parent_in_different_hook.phpt @@ -0,0 +1,16 @@ +--TEST-- +Using parent::$prop::get() in a different hook +--FILE-- + +--EXPECTF-- +Fatal error: Must not use parent::$foo::get() in a different property hook (set) in %s on line %d diff --git a/Zend/tests/property_hooks/parent_in_different_property.phpt b/Zend/tests/property_hooks/parent_in_different_property.phpt new file mode 100644 index 0000000000000..82a6dbcee3cd5 --- /dev/null +++ b/Zend/tests/property_hooks/parent_in_different_property.phpt @@ -0,0 +1,16 @@ +--TEST-- +Using parent::$prop::get() in a different property +--FILE-- + +--EXPECTF-- +Fatal error: Must not use parent::$bar::get() in a different property ($foo) in %s on line %d diff --git a/Zend/tests/property_hooks/parent_outside_property.phpt b/Zend/tests/property_hooks/parent_outside_property.phpt new file mode 100644 index 0000000000000..ce1d04245a1bf --- /dev/null +++ b/Zend/tests/property_hooks/parent_outside_property.phpt @@ -0,0 +1,14 @@ +--TEST-- +Using parent::$prop::get() outside a property hook +--FILE-- + +--EXPECTF-- +Fatal error: Must not use parent::$prop::get() outside a property hook in %s on line %d diff --git a/Zend/tests/property_hooks/parent_set.phpt b/Zend/tests/property_hooks/parent_set.phpt new file mode 100644 index 0000000000000..f6eeac89e6834 --- /dev/null +++ b/Zend/tests/property_hooks/parent_set.phpt @@ -0,0 +1,31 @@ +--TEST-- +Allow calling parent set in property hooks +--FILE-- +prop = 41; + +$b = new B; +$b->prop = 41; + +?> +--EXPECT-- +int(41) +int(42) diff --git a/Zend/tests/property_hooks/parent_set_plain.phpt b/Zend/tests/property_hooks/parent_set_plain.phpt new file mode 100644 index 0000000000000..a3e92ed460201 --- /dev/null +++ b/Zend/tests/property_hooks/parent_set_plain.phpt @@ -0,0 +1,23 @@ +--TEST-- +Using parent::$prop::set() on plain property +--FILE-- +prop = 42; + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/parent_set_plain_zpp.phpt b/Zend/tests/property_hooks/parent_set_plain_zpp.phpt new file mode 100644 index 0000000000000..aa95552af563e --- /dev/null +++ b/Zend/tests/property_hooks/parent_set_plain_zpp.phpt @@ -0,0 +1,27 @@ +--TEST-- +parent::$prop::set() ZPP +--FILE-- +prop = 42; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +A::$prop::set() expects exactly 1 argument, 2 given diff --git a/Zend/tests/property_hooks/parent_superfluous_args.phpt b/Zend/tests/property_hooks/parent_superfluous_args.phpt new file mode 100644 index 0000000000000..2abcd7e7d10df --- /dev/null +++ b/Zend/tests/property_hooks/parent_superfluous_args.phpt @@ -0,0 +1,51 @@ +--TEST-- +Extra args to parent hook call +--FILE-- +virtual = 42); +var_dump($b->virtual); +// Calling the implicit parent hook with extra args is not ok, since it is an internal function. +try { + var_dump($b->backed = 42); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($b->backed); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +int(42) +NULL +A::$backed::set() expects exactly 1 argument, 2 given +A::$backed::get() expects exactly 0 arguments, 1 given diff --git a/Zend/tests/property_hooks/parent_syntax.phpt b/Zend/tests/property_hooks/parent_syntax.phpt new file mode 100644 index 0000000000000..03e071e3c185c --- /dev/null +++ b/Zend/tests/property_hooks/parent_syntax.phpt @@ -0,0 +1,49 @@ +--TEST-- +Avoid spaces between parent hook call nodes +--FILE-- +prop); +var_dump(D::test()); + +?> +--EXPECT-- +int(41) +int(41) +int(42) +int(43) diff --git a/Zend/tests/property_hooks/parent_wrong_property_info.phpt b/Zend/tests/property_hooks/parent_wrong_property_info.phpt new file mode 100644 index 0000000000000..c18aad7f11f9f --- /dev/null +++ b/Zend/tests/property_hooks/parent_wrong_property_info.phpt @@ -0,0 +1,25 @@ +--TEST-- +Allow calling parent set in property hooks +--FILE-- + parent::$prop::get(); + } +} + +$b = new B; +try { + var_dump($b->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot access private property A::$prop diff --git a/Zend/tests/property_hooks/plain_to_hook.phpt b/Zend/tests/property_hooks/plain_to_hook.phpt new file mode 100644 index 0000000000000..46fef3d6bdf9c --- /dev/null +++ b/Zend/tests/property_hooks/plain_to_hook.phpt @@ -0,0 +1,33 @@ +--TEST-- +Override plain property with hooked property +--FILE-- +prop = 42; +echo $a->prop, "\n"; + +$b = new B(); +$b->prop = 42; +echo $b->prop, "\n"; + +?> +--EXPECT-- +42 +B::$prop::set(42) +B::$prop::get() diff --git a/Zend/tests/property_hooks/private_override.phpt b/Zend/tests/property_hooks/private_override.phpt new file mode 100644 index 0000000000000..1bd94403dfe85 --- /dev/null +++ b/Zend/tests/property_hooks/private_override.phpt @@ -0,0 +1,56 @@ +--TEST-- +Overriding private hooks +--FILE-- +prop1; + $this->prop1 = 1; + $this->prop2; + $this->prop2 = 1; + } +} + +class B extends A { + public $prop1 { + get { echo __METHOD__, "\n"; } + set { echo __METHOD__, "\n"; } + } + public $prop2 { + get { echo __METHOD__, "\n"; } + set { echo __METHOD__, "\n"; } + } +} + +$a = new A; +$a->testPrivate(); +echo "\n"; + +$b = new B; +$b->testPrivate(); +echo "\n"; + +$b->prop1; +$b->prop1 = 1; +$b->prop2; +$b->prop2 = 1; + +?> +--EXPECT-- +A::$prop2::get +A::$prop2::set + +A::$prop2::get +A::$prop2::set + +B::$prop1::get +B::$prop1::set +B::$prop2::get +B::$prop2::set diff --git a/Zend/tests/property_hooks/private_prop_final_hook.phpt b/Zend/tests/property_hooks/private_prop_final_hook.phpt new file mode 100644 index 0000000000000..09653b612c3e7 --- /dev/null +++ b/Zend/tests/property_hooks/private_prop_final_hook.phpt @@ -0,0 +1,12 @@ +--TEST-- +Private property with final hook +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both final and private in %s on line %d diff --git a/Zend/tests/property_hooks/property_access_within_closure.phpt b/Zend/tests/property_hooks/property_access_within_closure.phpt new file mode 100644 index 0000000000000..e7715e6749b4a --- /dev/null +++ b/Zend/tests/property_hooks/property_access_within_closure.phpt @@ -0,0 +1,25 @@ +--TEST-- +Property access within closure +--FILE-- +prop; + }; + } + } +} + +$a = new A(); +$c = $a->prop; +var_dump(get_class($c)); +$d = $c(); +var_dump(get_class($d)); + +?> +--EXPECT-- +string(7) "Closure" +string(7) "Closure" diff --git a/Zend/tests/property_hooks/property_const.phpt b/Zend/tests/property_hooks/property_const.phpt new file mode 100644 index 0000000000000..75018c0a1b5ff --- /dev/null +++ b/Zend/tests/property_hooks/property_const.phpt @@ -0,0 +1,34 @@ +--TEST-- +__PROPERTY__ magic constant +--FILE-- + __PROPERTY__; + set { var_dump(__PROPERTY__); } + } + + private $privProp { + get => __PROPERTY__; + } + + public function test() { + var_dump(__PROPERTY__); + var_dump($this->privProp); + } +} + +$test = new Test; +var_dump($test->prop); +$test->prop = 'foo'; +$test->test(); +var_dump(__PROPERTY__); + +?> +--EXPECT-- +string(4) "prop" +string(4) "prop" +string(0) "" +string(8) "privProp" +string(0) "" diff --git a/Zend/tests/property_hooks/property_const_nested.phpt b/Zend/tests/property_hooks/property_const_nested.phpt new file mode 100644 index 0000000000000..00a5095c284bb --- /dev/null +++ b/Zend/tests/property_hooks/property_const_nested.phpt @@ -0,0 +1,19 @@ +--TEST-- +__PROPERTY__ magic constant in nested function +--FILE-- + (function () { + return __PROPERTY__; + })(); + } +} + +$test = new Test; +var_dump($test->prop); + +?> +--EXPECT-- +string(0) "" diff --git a/Zend/tests/property_hooks/property_promotion.phpt b/Zend/tests/property_hooks/property_promotion.phpt new file mode 100644 index 0000000000000..e9fbf116db400 --- /dev/null +++ b/Zend/tests/property_hooks/property_promotion.phpt @@ -0,0 +1,21 @@ +--TEST-- +Generated hooks in property promotion +--FILE-- +prop; + +?> +--EXPECT-- +set(42) +get diff --git a/Zend/tests/property_hooks/protected_to_public.phpt b/Zend/tests/property_hooks/protected_to_public.phpt new file mode 100644 index 0000000000000..afa1a80128d49 --- /dev/null +++ b/Zend/tests/property_hooks/protected_to_public.phpt @@ -0,0 +1,23 @@ +--TEST-- +Inherited hooks change visibility with property +--FILE-- + 42; + } +} + +class B extends A { + public $prop { + set {} + } +} + +$b = new B(); +var_dump($b->prop); + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/read_sibling_backing_value.phpt b/Zend/tests/property_hooks/read_sibling_backing_value.phpt new file mode 100644 index 0000000000000..65fe28fed32ce --- /dev/null +++ b/Zend/tests/property_hooks/read_sibling_backing_value.phpt @@ -0,0 +1,34 @@ +--TEST-- +Attempted read/write of backing value in sibling property hook fails +--SKIPIF-- + +--EXTENSIONS-- +zend_test +--INI-- +; The test may use a large amount of memory on systems with a large stack limit +memory_limit=2G +--FILE-- + $this->a + $this->b; + } + public $b = 2 { + get => $this->b + $this->a; + } +} + +$test = new Test; + +try { + var_dump($test->a); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? diff --git a/Zend/tests/property_hooks/readonly.phpt b/Zend/tests/property_hooks/readonly.phpt new file mode 100644 index 0000000000000..be68bc800576e --- /dev/null +++ b/Zend/tests/property_hooks/readonly.phpt @@ -0,0 +1,12 @@ +--TEST-- +Hooked properties cannot be readonly +--FILE-- + +--EXPECTF-- +Fatal error: Hooked properties cannot be readonly in %s on line %d diff --git a/Zend/tests/property_hooks/recursion.phpt b/Zend/tests/property_hooks/recursion.phpt new file mode 100644 index 0000000000000..0a070f9ba96ca --- /dev/null +++ b/Zend/tests/property_hooks/recursion.phpt @@ -0,0 +1,36 @@ +--TEST-- +Recursion behavior of property hooks +--FILE-- +prop * 2; } + set { $this->prop = $value * 2; } + } + + // Edge-case where recursion happens via isset(). + public int $prop2 { + get { return isset($this->prop2); } + set { } + } +} + +$test = new Test; +$test->prop = 10; +var_dump($test->prop); +var_dump(isset($test->prop)); +var_dump(isset($test->prop2)); +var_dump($test); + +?> +--EXPECT-- +int(40) +bool(true) +bool(true) +object(Test)#1 (1) { + ["prop"]=> + int(20) + ["prop2"]=> + uninitialized(int) +} diff --git a/Zend/tests/property_hooks/set.phpt b/Zend/tests/property_hooks/set.phpt new file mode 100644 index 0000000000000..04e36ae85411f --- /dev/null +++ b/Zend/tests/property_hooks/set.phpt @@ -0,0 +1,32 @@ +--TEST-- +Basic set only property hook +--FILE-- +_prop = $value; } + } +} + +$test = new Test; +$test->prop = 42; +var_dump($test->_prop); + +try { + var_dump($test->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(isset($test->prop)); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +int(42) +Property Test::$prop is write-only +Property Test::$prop is write-only diff --git a/Zend/tests/property_hooks/set_by_ref.phpt b/Zend/tests/property_hooks/set_by_ref.phpt new file mode 100644 index 0000000000000..e8d6fc4b395dc --- /dev/null +++ b/Zend/tests/property_hooks/set_by_ref.phpt @@ -0,0 +1,14 @@ +--TEST-- +set parameter must not be by-reference +--FILE-- + +--EXPECTF-- +Fatal error: Parameter $value of set hook Test::$prop must not be pass-by-reference in %s on line %d diff --git a/Zend/tests/property_hooks/set_shorthand.phpt b/Zend/tests/property_hooks/set_shorthand.phpt new file mode 100644 index 0000000000000..02ff5a18c6806 --- /dev/null +++ b/Zend/tests/property_hooks/set_shorthand.phpt @@ -0,0 +1,21 @@ +--TEST-- +set shorthand +--FILE-- + strtoupper($value); + } +} + +$test = new Test(); +$test->prop = 'foo'; +var_dump($test); + +?> +--EXPECT-- +object(Test)#1 (1) { + ["prop"]=> + string(3) "FOO" +} diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_001.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_001.phpt new file mode 100644 index 0000000000000..d6090645abfae --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_001.phpt @@ -0,0 +1,14 @@ +--TEST-- +set $value parameter variance +--FILE-- + +--EXPECTF-- +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_002.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_002.phpt new file mode 100644 index 0000000000000..7e2d45aa3c464 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_002.phpt @@ -0,0 +1,14 @@ +--TEST-- +set $value parameter variance +--FILE-- + +--EXPECTF-- +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_003.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_003.phpt new file mode 100644 index 0000000000000..fde736277d127 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_003.phpt @@ -0,0 +1,17 @@ +--TEST-- +set $value parameter variance +--FILE-- + +--EXPECTF-- +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_004.inc b/Zend/tests/property_hooks/set_value_parameter_type_variance_004.inc new file mode 100644 index 0000000000000..714792d3dc085 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_004.inc @@ -0,0 +1,7 @@ + +--EXPECTF-- +Autoloading X +Autoloading Y + +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_005.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_005.phpt new file mode 100644 index 0000000000000..40eca0424dec8 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_005.phpt @@ -0,0 +1,14 @@ +--TEST-- +set $value parameter variance +--FILE-- + +--EXPECTF-- +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_006.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_006.phpt new file mode 100644 index 0000000000000..3650767346c29 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_006.phpt @@ -0,0 +1,18 @@ +--TEST-- +set $value parameter variance +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_007.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_007.phpt new file mode 100644 index 0000000000000..42031e4f4cd05 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_007.phpt @@ -0,0 +1,23 @@ +--TEST-- +set $value parameter variance +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::$prop::set(Y $prop): void must be compatible with A::$prop::set(X $prop): void in %s on line %d diff --git a/Zend/tests/property_hooks/set_variadic.phpt b/Zend/tests/property_hooks/set_variadic.phpt new file mode 100644 index 0000000000000..bb47173a402f7 --- /dev/null +++ b/Zend/tests/property_hooks/set_variadic.phpt @@ -0,0 +1,14 @@ +--TEST-- +set parameter must not be variadic +--FILE-- + +--EXPECTF-- +Fatal error: Parameter $value of set hook Test::$prop must not be variadic in %s on line %d diff --git a/Zend/tests/property_hooks/static_variables.phpt b/Zend/tests/property_hooks/static_variables.phpt new file mode 100644 index 0000000000000..2269f00eefef4 --- /dev/null +++ b/Zend/tests/property_hooks/static_variables.phpt @@ -0,0 +1,41 @@ +--TEST-- +Hooks containing static variables +--FILE-- +prop); +var_dump($test->prop); +var_dump($test->prop); + +?> +--EXPECT-- +array(1) { + [0]=> + int(1) +} +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} diff --git a/Zend/tests/property_hooks/syntax.phpt b/Zend/tests/property_hooks/syntax.phpt new file mode 100644 index 0000000000000..32280546db6e9 --- /dev/null +++ b/Zend/tests/property_hooks/syntax.phpt @@ -0,0 +1,14 @@ +--TEST-- +Basic property hook syntax +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/property_hooks/trait_scope.phpt b/Zend/tests/property_hooks/trait_scope.phpt new file mode 100644 index 0000000000000..4e20cec04cb17 --- /dev/null +++ b/Zend/tests/property_hooks/trait_scope.phpt @@ -0,0 +1,29 @@ +--TEST-- +Trait hook scope is changed +--FILE-- +x; + } + + public string $bar { + get => $this->x; + } +} + +class A { + use Foo; +} + +$a = new A(); +var_dump($a->getBar()); +var_dump($a->bar); + +?> +--EXPECT-- +string(3) "bar" +string(3) "bar" diff --git a/Zend/tests/property_hooks/traits.phpt b/Zend/tests/property_hooks/traits.phpt new file mode 100644 index 0000000000000..782825faaa67f --- /dev/null +++ b/Zend/tests/property_hooks/traits.phpt @@ -0,0 +1,24 @@ +--TEST-- +Hooked properties in traits +--FILE-- +prop; +$test->prop = 1; + +?> +--EXPECT-- +T::$prop::get +T::$prop::set diff --git a/Zend/tests/property_hooks/traits_abstract.phpt b/Zend/tests/property_hooks/traits_abstract.phpt new file mode 100644 index 0000000000000..fdfd638aba4f0 --- /dev/null +++ b/Zend/tests/property_hooks/traits_abstract.phpt @@ -0,0 +1,16 @@ +--TEST-- +Abstract property hooks from trait +--FILE-- + +--EXPECTF-- +Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (C::$prop::get, C::$prop::set) in %s on line %d diff --git a/Zend/tests/property_hooks/traits_conflict.phpt b/Zend/tests/property_hooks/traits_conflict.phpt new file mode 100644 index 0000000000000..71f591703f8aa --- /dev/null +++ b/Zend/tests/property_hooks/traits_conflict.phpt @@ -0,0 +1,24 @@ +--TEST-- +Trait property hook conflict +--FILE-- + +--EXPECTF-- +Fatal error: C and T define the same hooked property ($prop) in the composition of C. Conflict resolution between hooked properties is currently not supported. Class was composed in %s on line %d diff --git a/Zend/tests/property_hooks/type_compatibility.phpt b/Zend/tests/property_hooks/type_compatibility.phpt new file mode 100644 index 0000000000000..8bfb7b0cadd59 --- /dev/null +++ b/Zend/tests/property_hooks/type_compatibility.phpt @@ -0,0 +1,18 @@ +--TEST-- +Relaxed type compatibility for read-only and write-only properties +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/type_compatibility_invalid.phpt b/Zend/tests/property_hooks/type_compatibility_invalid.phpt new file mode 100644 index 0000000000000..216abaf4068cb --- /dev/null +++ b/Zend/tests/property_hooks/type_compatibility_invalid.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid type compatibility for read-only property +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::$a::get(): int|float must be compatible with A::$a::get(): int in %s on line %d diff --git a/Zend/tests/property_hooks/type_compatibility_invalid_2.phpt b/Zend/tests/property_hooks/type_compatibility_invalid_2.phpt new file mode 100644 index 0000000000000..6e0ce405d6d97 --- /dev/null +++ b/Zend/tests/property_hooks/type_compatibility_invalid_2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid type compatibility for write-only property +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::$a::set(int $value): void must be compatible with A::$a::set(int|float $value): void in %s on line %d diff --git a/Zend/tests/property_hooks/unknown_hook.phpt b/Zend/tests/property_hooks/unknown_hook.phpt new file mode 100644 index 0000000000000..188d7f0560505 --- /dev/null +++ b/Zend/tests/property_hooks/unknown_hook.phpt @@ -0,0 +1,14 @@ +--TEST-- +Unknown property hook +--FILE-- + +--EXPECTF-- +Fatal error: Unknown hook "foobar" for property Test::$prop, expected "get" or "set" in %s on line %d diff --git a/Zend/tests/property_hooks/unknown_hook_private.phpt b/Zend/tests/property_hooks/unknown_hook_private.phpt new file mode 100644 index 0000000000000..a2c25f6360454 --- /dev/null +++ b/Zend/tests/property_hooks/unknown_hook_private.phpt @@ -0,0 +1,14 @@ +--TEST-- +Unknown property hook (private property) +--FILE-- + +--EXPECTF-- +Fatal error: Unknown hook "foobar" for property Test::$prop, expected "get" or "set" in %s on line %d diff --git a/Zend/tests/property_hooks/unserialize.phpt b/Zend/tests/property_hooks/unserialize.phpt new file mode 100644 index 0000000000000..2461817a08814 --- /dev/null +++ b/Zend/tests/property_hooks/unserialize.phpt @@ -0,0 +1,71 @@ +--TEST-- +unserialize() with hooks +--FILE-- +prop3; +$test_u->prop3 = 42; + +$s = 'O:4:"Test":1:{s:5:"prop3";i:42;}'; +var_dump(unserialize($s)); +echo "\n"; + +// Override implicit hook with explicit. +class Test2 extends Test { + public $prop1 { + get { echo __METHOD__, "\n"; } + } +} + +$test2 = new Test2(1, 2); +var_dump($s = serialize($test2)); +var_dump($test2_u = unserialize($s)); +$test2_u->prop1; +$test2_u->prop1 = 42; + +$s = 'O:5:"Test2":1:{s:5:"prop1";i:42;}'; +var_dump(unserialize($s)); + +?> +--EXPECTF-- +string(31) "O:4:"Test":1:{s:5:"prop1";i:1;}" +object(Test)#2 (1) { + ["prop1"]=> + int(1) +} +Test::$prop3::get +Test::$prop3::set + +Warning: unserialize(): Cannot unserialize value for hooked property Test::$prop3 in %s on line %d + +Warning: unserialize(): Error at offset 26 of 32 bytes in %s on line %d +bool(false) + +string(32) "O:5:"Test2":1:{s:5:"prop1";i:1;}" +object(Test2)#4 (1) { + ["prop1"]=> + int(1) +} +Test2::$prop1::get +object(Test2)#5 (1) { + ["prop1"]=> + int(42) +} diff --git a/Zend/tests/property_hooks/unset.phpt b/Zend/tests/property_hooks/unset.phpt new file mode 100644 index 0000000000000..49edd56831a0b --- /dev/null +++ b/Zend/tests/property_hooks/unset.phpt @@ -0,0 +1,29 @@ +--TEST-- +Hooked properties cannot be unset +--FILE-- +prop; } + set { $this->prop = $value; } + } + + public function __unset($name) { + echo "Never reached\n"; + } +} + +$test = new Test; +$test->prop = 42; +try { + unset($test->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->prop); + +?> +--EXPECT-- +Cannot unset hooked property Test::$prop +int(42) diff --git a/Zend/tests/property_hooks/update_constants_virtual_prop.phpt b/Zend/tests/property_hooks/update_constants_virtual_prop.phpt new file mode 100644 index 0000000000000..fcead21f4fd27 --- /dev/null +++ b/Zend/tests/property_hooks/update_constants_virtual_prop.phpt @@ -0,0 +1,16 @@ +--TEST-- +Make sure constant updating works in the presence of virtual properties +--FILE-- +prop2); + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/var_property.phpt b/Zend/tests/property_hooks/var_property.phpt new file mode 100644 index 0000000000000..c275cdf739124 --- /dev/null +++ b/Zend/tests/property_hooks/var_property.phpt @@ -0,0 +1,15 @@ +--TEST-- +Var hooked property is implicitly public +--FILE-- + 42; } +} + +$test = new Test(); +var_dump($test->prop); + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/virtual_read_write.phpt b/Zend/tests/property_hooks/virtual_read_write.phpt new file mode 100644 index 0000000000000..e5451ee1ceb50 --- /dev/null +++ b/Zend/tests/property_hooks/virtual_read_write.phpt @@ -0,0 +1,49 @@ +--TEST-- +Attempted read/write of virtual property backing value throws +--SKIPIF-- + +--EXTENSIONS-- +zend_test +--INI-- +; The test may use a large amount of memory on systems with a large stack limit +memory_limit=2G +--FILE-- + $this->getProp(); + set { + $this->setProp($value); + } + } + + private function getProp() { + return $this->prop; + } + + private function setProp($value) { + $this->prop = $value; + } +} + +$test = new Test; + +try { + $test->prop = 0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump($test->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? diff --git a/Zend/tests/type_declarations/typed_properties_002.phpt b/Zend/tests/type_declarations/typed_properties_002.phpt index b0d1e1c0f2795..b10aaf95bf807 100644 --- a/Zend/tests/type_declarations/typed_properties_002.phpt +++ b/Zend/tests/type_declarations/typed_properties_002.phpt @@ -9,7 +9,7 @@ $thing = new class() { var_dump($thing->int); ?> --EXPECTF-- -Fatal error: Uncaught Error: Typed property class@anonymous::$int must not be accessed before initialization in %s:6 +Fatal error: Uncaught Error: Typed property class@anonymous::$int must not be accessed before initialization in %s:%d Stack trace: #0 {main} thrown in %s on line 6 diff --git a/Zend/tests/type_declarations/typed_properties_026.phpt b/Zend/tests/type_declarations/typed_properties_026.phpt index 7d01e927a0289..6d2174e99c778 100644 --- a/Zend/tests/type_declarations/typed_properties_026.phpt +++ b/Zend/tests/type_declarations/typed_properties_026.phpt @@ -17,7 +17,7 @@ class Baz{ var_dump((new Baz)->get()); ?> --EXPECTF-- -Fatal error: Uncaught Error: Typed property Baz::$baz must not be accessed before initialization in %s:10 +Fatal error: Uncaught Error: Typed property Baz::$baz must not be accessed before initialization in %s:%d Stack trace: #0 %s(14): Baz->get() #1 {main} diff --git a/Zend/zend.h b/Zend/zend.h index dd805e0e7638f..4fe0703d42f69 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -204,6 +204,8 @@ struct _zend_class_entry { uint32_t num_interfaces; uint32_t num_traits; + uint32_t num_hooked_props; + uint32_t num_hooked_prop_variance_checks; /* class_entry or string(s) depending on ZEND_ACC_LINKED */ union { diff --git a/Zend/zend_API.c b/Zend/zend_API.c index f702270dbc117..6a07e99202d0d 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2951,6 +2951,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend internal_function->function_name = zend_string_init_interned(ptr->fname, fname_len, 1); internal_function->scope = scope; internal_function->prototype = NULL; + internal_function->prop_info = NULL; internal_function->attributes = NULL; internal_function->frameless_function_infos = ptr->frameless_function_infos; if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime @@ -4509,6 +4510,16 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z if (!(access_type & ZEND_ACC_PPP_MASK)) { access_type |= ZEND_ACC_PUBLIC; } + + /* Virtual properties have no backing storage, the offset should never be used. However, the + * virtual flag cannot be definitively determined at compile time. Allow using default values + * anyway, and assert after inheritance that the property is not actually virtual. */ + if (access_type & ZEND_ACC_VIRTUAL) { + if (Z_TYPE_P(property) == IS_UNDEF) { + property_info->offset = (uint32_t)-1; + goto skip_property_storage; + } + } if (access_type & ZEND_ACC_STATIC) { if ((property_info_ptr = zend_hash_find_ptr(&ce->properties_info, name)) != NULL) { ZEND_ASSERT(property_info_ptr->flags & ZEND_ACC_STATIC); @@ -4558,6 +4569,7 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z ZVAL_COPY_VALUE(property_default_ptr, property); Z_PROP_FLAG_P(property_default_ptr) = Z_ISUNDEF_P(property) ? IS_PROP_UNINIT : 0; } +skip_property_storage: if (ce->type & ZEND_INTERNAL_CLASS) { /* Must be interned to avoid ZTS data races */ if (is_persistent_class(ce)) { @@ -4582,6 +4594,8 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z property_info->flags = access_type; property_info->doc_comment = doc_comment; property_info->attributes = NULL; + property_info->prototype = property_info; + property_info->hooks = NULL; property_info->ce = ce; property_info->type = type; diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index e6c348ebf21dc..81562411e6b0c 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -270,6 +270,43 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_5(zend_ast_kind kind, zend_ast return ast; } +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_va( + zend_ast_kind kind, zend_ast_attr attr, va_list *va) { + uint32_t lineno = (uint32_t)-1; + uint32_t children = kind >> ZEND_AST_NUM_CHILDREN_SHIFT; + zend_ast *ast = zend_ast_alloc(zend_ast_size(children)); + ast->kind = kind; + ast->attr = attr; + for (uint32_t i = 0; i < children; i++) { + ast->child[i] = va_arg(*va, zend_ast *); + if (lineno != (uint32_t)-1 && ast->child[i]) { + lineno = zend_ast_get_lineno(ast->child[i]); + } + } + if (lineno == (uint32_t)-1) { + lineno = CG(zend_lineno); + } + ast->lineno = lineno; + return ast; +} + +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_n(unsigned kind, ...) { + va_list va; + va_start(va, kind); + zend_ast *ast = zend_ast_create_va(kind, 0, &va); + va_end(va); + return ast; +} + +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_ex_n( + zend_ast_kind kind, unsigned attr, ...) { + va_list va; + va_start(va, attr); + zend_ast *ast = zend_ast_create_va(kind, attr, &va); + va_end(va); + return ast; +} + ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind) { zend_ast *ast; zend_ast_list *list; @@ -1499,6 +1536,14 @@ static ZEND_COLD void zend_ast_export_stmt(smart_str *str, zend_ast *ast, int in case ZEND_AST_NAMESPACE: case ZEND_AST_DECLARE: break; + case ZEND_AST_PROP_GROUP: { + zend_ast *first_prop = zend_ast_get_list(ast->child[1])->child[0]; + zend_ast *hook_list = first_prop->child[3]; + if (hook_list == NULL) { + smart_str_appendc(str, ';'); + } + break; + } default: smart_str_appendc(str, ';'); break; @@ -1953,6 +1998,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio case T_TRAIT_C: APPEND_STR("__TRAIT__"); case T_METHOD_C: APPEND_STR("__METHOD__"); case T_FUNC_C: APPEND_STR("__FUNCTION__"); + case T_PROPERTY_C: APPEND_STR("__PROPERTY__"); case T_NS_C: APPEND_STR("__NAMESPACE__"); case T_CLASS_C: APPEND_STR("__CLASS__"); EMPTY_SWITCH_DEFAULT_CASE(); @@ -2105,6 +2151,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_ex(str, ast->child[1], 0, indent); smart_str_appendc(str, ')'); break; + case ZEND_AST_PARENT_PROPERTY_HOOK_CALL: + smart_str_append(str, Z_STR_P(zend_ast_get_zval(ast->child[0]))); + smart_str_appendc(str, '('); + zend_ast_export_ex(str, ast->child[1], 0, indent); + smart_str_appendc(str, ')'); + break; case ZEND_AST_CALLABLE_CONVERT: smart_str_appends(str, "..."); break; @@ -2318,7 +2370,60 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio break; case ZEND_AST_PROP_ELEM: smart_str_appendc(str, '$'); - ZEND_FALLTHROUGH; + zend_ast_export_name(str, ast->child[0], 0, indent); + + zend_ast *default_value = ast->child[1]; + if (default_value) { + smart_str_appends(str, " = "); + zend_ast_export_ex(str, default_value, 0, indent + 1); + } + + if (ast->child[3]) { + zend_ast_list *hook_list = zend_ast_get_list(ast->child[3]); + + smart_str_appends(str, " {"); + smart_str_appendc(str, '\n'); + indent++; + zend_ast_export_indent(str, indent); + + for (uint32_t i = 0; i < hook_list->children; i++) { + zend_ast_decl *hook = (zend_ast_decl *)hook_list->child[i]; + zend_ast_export_visibility(str, hook->flags); + if (hook->flags & ZEND_ACC_FINAL) { + smart_str_appends(str, "final "); + } + switch (i) { + case ZEND_PROPERTY_HOOK_GET: + smart_str_appends(str, "get"); + break; + case ZEND_PROPERTY_HOOK_SET: + smart_str_appends(str, "set"); + break; + } + zend_ast *body = hook->child[2]; + if (body == NULL) { + smart_str_appendc(str, ';'); + } else if (body->kind == ZEND_AST_PROPERTY_HOOK_SHORT_BODY) { + smart_str_appends(str, " => "); + zend_ast_export_ex(str, body->child[0], 0, indent); + smart_str_appendc(str, ';'); + } else { + smart_str_appends(str, " {\n"); + zend_ast_export_stmt(str, body, indent + 1); + zend_ast_export_indent(str, indent); + smart_str_appendc(str, '}'); + } + if (i < (hook_list->children - 1)) { + smart_str_appendc(str, '\n'); + zend_ast_export_indent(str, indent); + } + } + smart_str_appendc(str, '\n'); + indent--; + zend_ast_export_indent(str, indent); + smart_str_appendc(str, '}'); + } + break; case ZEND_AST_CONST_ELEM: zend_ast_export_name(str, ast->child[0], 0, indent); APPEND_DEFAULT_VALUE(1); @@ -2576,6 +2681,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr) case ZEND_AST_CLOSURE: case ZEND_AST_METHOD: case ZEND_AST_ARROW_FUNC: + case ZEND_AST_PROPERTY_HOOK: ((zend_ast_decl *) ast)->child[4] = attr; break; case ZEND_AST_CLASS: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 963b81e533084..df33d5c35ef05 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -43,6 +43,7 @@ enum _zend_ast_kind { ZEND_AST_METHOD, ZEND_AST_CLASS, ZEND_AST_ARROW_FUNC, + ZEND_AST_PROPERTY_HOOK, /* list nodes */ ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT, @@ -108,6 +109,7 @@ enum _zend_ast_kind { ZEND_AST_GOTO, ZEND_AST_BREAK, ZEND_AST_CONTINUE, + ZEND_AST_PROPERTY_HOOK_SHORT_BODY, /* 2 child nodes */ ZEND_AST_DIM = 2 << ZEND_AST_NUM_CHILDREN_SHIFT, @@ -149,6 +151,7 @@ enum _zend_ast_kind { ZEND_AST_MATCH, ZEND_AST_MATCH_ARM, ZEND_AST_NAMED_ARG, + ZEND_AST_PARENT_PROPERTY_HOOK_CALL, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, @@ -159,7 +162,6 @@ enum _zend_ast_kind { ZEND_AST_TRY, ZEND_AST_CATCH, ZEND_AST_PROP_GROUP, - ZEND_AST_PROP_ELEM, ZEND_AST_CONST_ELEM, ZEND_AST_CLASS_CONST_GROUP, @@ -170,9 +172,12 @@ enum _zend_ast_kind { ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, ZEND_AST_FOREACH, ZEND_AST_ENUM_CASE, + ZEND_AST_PROP_ELEM, /* 5 child nodes */ - ZEND_AST_PARAM = 5 << ZEND_AST_NUM_CHILDREN_SHIFT, + + /* 6 child nodes */ + ZEND_AST_PARAM = 6 << ZEND_AST_NUM_CHILDREN_SHIFT, }; typedef uint16_t zend_ast_kind; @@ -227,12 +232,12 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_class_const_or_name(zend_ast * #if ZEND_AST_SPEC # define ZEND_AST_SPEC_CALL(name, ...) \ - ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_(name, __VA_ARGS__, _5, _4, _3, _2, _1, _0)(__VA_ARGS__)) -# define ZEND_AST_SPEC_CALL_(name, _, _5, _4, _3, _2, _1, suffix, ...) \ + ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_(name, __VA_ARGS__, _n, _5, _4, _3, _2, _1, _0)(__VA_ARGS__)) +# define ZEND_AST_SPEC_CALL_(name, _, _6, _5, _4, _3, _2, _1, suffix, ...) \ name ## suffix # define ZEND_AST_SPEC_CALL_EX(name, ...) \ - ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_EX_(name, __VA_ARGS__, _5, _4, _3, _2, _1, _0)(__VA_ARGS__)) -# define ZEND_AST_SPEC_CALL_EX_(name, _, _6, _5, _4, _3, _2, _1, suffix, ...) \ + ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_EX_(name, __VA_ARGS__, _n, _5, _4, _3, _2, _1, _0)(__VA_ARGS__)) +# define ZEND_AST_SPEC_CALL_EX_(name, _, _7, _6, _5, _4, _3, _2, _1, suffix, ...) \ name ## suffix ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_0(zend_ast_kind kind); @@ -242,6 +247,11 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_3(zend_ast_kind kind, zend_ast ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_4(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4); ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_5(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4, zend_ast *child5); +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_va(zend_ast_kind kind, zend_ast_attr attr, va_list *va); +/* Need to use unsigned args to avoid va promotion UB. */ +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_n(unsigned kind, ...); +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_ex_n(zend_ast_kind kind, unsigned attr, ...); + static zend_always_inline zend_ast * zend_ast_create_ex_0(zend_ast_kind kind, zend_ast_attr attr) { zend_ast *ast = zend_ast_create_0(kind); ast->attr = attr; diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index ce74253b1def3..9d2751a4a5856 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -768,28 +768,32 @@ ZEND_FUNCTION(get_object_vars) Z_PARAM_OBJ(zobj) ZEND_PARSE_PARAMETERS_END(); - properties = zobj->handlers->get_properties(zobj); + zval obj_zv; + ZVAL_OBJ(&obj_zv, zobj); + properties = zend_get_properties_for(&obj_zv, ZEND_PROP_PURPOSE_GET_OBJECT_VARS); if (properties == NULL) { RETURN_EMPTY_ARRAY(); } if (!zobj->ce->default_properties_count && properties == zobj->properties && !GC_IS_RECURSIVE(properties)) { /* fast copy */ - if (EXPECTED(zobj->handlers == &std_object_handlers)) { - RETURN_ARR(zend_proptable_to_symtable(properties, 0)); - } - RETURN_ARR(zend_proptable_to_symtable(properties, 1)); + bool always_duplicate = zobj->handlers != &std_object_handlers; + RETVAL_ARR(zend_proptable_to_symtable(properties, always_duplicate)); } else { array_init_size(return_value, zend_hash_num_elements(properties)); ZEND_HASH_FOREACH_KEY_VAL(properties, num_key, key, value) { bool is_dynamic = 1; + zval tmp; + ZVAL_UNDEF(&tmp); if (Z_TYPE_P(value) == IS_INDIRECT) { value = Z_INDIRECT_P(value); if (UNEXPECTED(Z_ISUNDEF_P(value))) { continue; } + is_dynamic = 0; + } else if (Z_TYPE_P(value) == IS_PTR) { is_dynamic = 0; } @@ -800,6 +804,24 @@ ZEND_FUNCTION(get_object_vars) if (Z_ISREF_P(value) && Z_REFCOUNT_P(value) == 1) { value = Z_REFVAL_P(value); } + if (Z_TYPE_P(value) == IS_PTR) { + /* value is IS_PTR for properties with hooks. */ + zend_property_info *prop_info = Z_PTR_P(value); + if ((prop_info->flags & ZEND_ACC_VIRTUAL) && !prop_info->hooks[ZEND_PROPERTY_HOOK_GET]) { + continue; + } + const char *unmangled_name_cstr = zend_get_unmangled_property_name(prop_info->name); + zend_string *unmangled_name = zend_string_init(unmangled_name_cstr, strlen(unmangled_name_cstr), false); + zend_read_property_ex(prop_info->ce, zobj, unmangled_name, /* silent */ true, &tmp); + zend_string_release_ex(unmangled_name, false); + if (EG(exception)) { + zend_release_properties(properties); + zval_ptr_dtor(return_value); + ZVAL_UNDEF(return_value); + RETURN_THROWS(); + } + value = &tmp; + } Z_TRY_ADDREF_P(value); if (UNEXPECTED(!key)) { @@ -818,8 +840,10 @@ ZEND_FUNCTION(get_object_vars) } else { zend_symtable_add_new(Z_ARRVAL_P(return_value), key, value); } + zval_ptr_dtor(&tmp); } ZEND_HASH_FOREACH_END(); } + zend_release_properties(properties); } /* }}} */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 79a65d2d5aded..474cbea3eb309 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -20,6 +20,7 @@ #include #include "zend.h" +#include "zend_ast.h" #include "zend_attributes.h" #include "zend_compile.h" #include "zend_constants.h" @@ -36,6 +37,7 @@ #include "zend_observer.h" #include "zend_call_stack.h" #include "zend_frameless_function.h" +#include "zend_property_hooks.h" #define SET_NODE(target, src) do { \ target ## _type = (src)->op_type; \ @@ -333,6 +335,8 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context, zend_op_arra CG(context).brk_cont_array = NULL; CG(context).labels = NULL; CG(context).in_jmp_frameless_branch = false; + CG(context).active_property_info = NULL; + CG(context).active_property_hook_kind = (zend_property_hook_kind)-1; } /* }}} */ @@ -859,23 +863,35 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token { switch (token) { case T_PUBLIC: - return ZEND_ACC_PUBLIC; + if (target != ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + return ZEND_ACC_PUBLIC; + } + break; case T_PROTECTED: - return ZEND_ACC_PROTECTED; + if (target != ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + return ZEND_ACC_PROTECTED; + } + break; case T_PRIVATE: - return ZEND_ACC_PRIVATE; + if (target != ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + return ZEND_ACC_PRIVATE; + } + break; case T_READONLY: if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) { return ZEND_ACC_READONLY; } break; case T_ABSTRACT: - if (target == ZEND_MODIFIER_TARGET_METHOD) { + if (target == ZEND_MODIFIER_TARGET_METHOD || target == ZEND_MODIFIER_TARGET_PROPERTY) { return ZEND_ACC_ABSTRACT; } break; case T_FINAL: - if (target == ZEND_MODIFIER_TARGET_METHOD || target == ZEND_MODIFIER_TARGET_CONSTANT) { + if (target == ZEND_MODIFIER_TARGET_METHOD + || target == ZEND_MODIFIER_TARGET_CONSTANT + || target == ZEND_MODIFIER_TARGET_PROPERTY + || target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { return ZEND_ACC_FINAL; } break; @@ -895,6 +911,8 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token member = "class constant"; } else if (target == ZEND_MODIFIER_TARGET_CPP) { member = "parameter"; + } else if (target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + member = "property hook"; } else { ZEND_UNREACHABLE(); } @@ -2047,6 +2065,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->num_interfaces = 0; ce->interfaces = NULL; ce->num_traits = 0; + ce->num_hooked_props = 0; + ce->num_hooked_prop_variance_checks = 0; ce->trait_names = NULL; ce->trait_aliases = NULL; ce->trait_precedences = NULL; @@ -4997,6 +5017,91 @@ static zend_result zend_try_compile_special_func(znode *result, zend_string *lcn return zend_compile_frameless_icall(result, args, fbc, type) != (uint32_t)-1 ? SUCCESS : FAILURE; } +static const char *zend_get_cstring_from_property_hook_kind(zend_property_hook_kind kind) { + switch (kind) { + case ZEND_PROPERTY_HOOK_GET: + return "get"; + case ZEND_PROPERTY_HOOK_SET: + return "set"; + EMPTY_SWITCH_DEFAULT_CASE() + } +} + +static zend_string *zend_copy_unmangled_prop_name(zend_string *prop_name) +{ + if (ZSTR_VAL(prop_name)[0] != '\0') { + return zend_string_copy(prop_name); + } else { + const char *unmangled = zend_get_unmangled_property_name(prop_name); + return zend_string_init(unmangled, strlen(unmangled), /* persistent */ false); + } +} + +static bool zend_compile_parent_property_hook_call(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ +{ + ZEND_ASSERT(ast->kind == ZEND_AST_STATIC_CALL); + + zend_ast *class_ast = ast->child[0]; + zend_ast *method_ast = ast->child[1]; + + /* Recognize parent::$prop::get() pattern. */ + if (class_ast->kind != ZEND_AST_STATIC_PROP + || (class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP) + || class_ast->child[0]->kind != ZEND_AST_ZVAL + || Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) != IS_STRING + || zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) != ZEND_FETCH_CLASS_PARENT + || class_ast->child[1]->kind != ZEND_AST_ZVAL + || method_ast->kind != ZEND_AST_ZVAL + || Z_TYPE_P(zend_ast_get_zval(method_ast)) != IS_STRING + || (!zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get") + && !zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set"))) { + return false; + } + + zend_class_entry *ce = CG(active_class_entry); + if (!ce) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use \"parent\" when no class scope is active"); + } + + zend_ast *args_ast = ast->child[2]; + if (args_ast->kind == ZEND_AST_CALLABLE_CONVERT) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot create Closure for parent property hook call"); + } + + zend_string *property_name = zend_ast_get_str(class_ast->child[1]); + zend_string *hook_name = zend_ast_get_str(method_ast); + zend_property_hook_kind hook_kind = zend_get_property_hook_kind_from_name(hook_name); + ZEND_ASSERT(hook_kind != (uint32_t)-1); + + const zend_property_info *prop_info = CG(context).active_property_info; + if (!prop_info) { + zend_error_noreturn(E_COMPILE_ERROR, "Must not use parent::$%s::%s() outside a property hook", + ZSTR_VAL(property_name), ZSTR_VAL(hook_name)); + } + + const char *unmangled_prop_name = zend_get_unmangled_property_name(prop_info->name); + if (!zend_string_equals_cstr(property_name, unmangled_prop_name, strlen(unmangled_prop_name))) { + zend_error_noreturn(E_COMPILE_ERROR, "Must not use parent::$%s::%s() in a different property ($%s)", + ZSTR_VAL(property_name), ZSTR_VAL(hook_name), unmangled_prop_name); + } + if (hook_kind != CG(context).active_property_hook_kind) { + zend_error_noreturn(E_COMPILE_ERROR, "Must not use parent::$%s::%s() in a different property hook (%s)", + ZSTR_VAL(property_name), ZSTR_VAL(hook_name), zend_get_cstring_from_property_hook_kind(CG(context).active_property_hook_kind)); + } + + zend_op *opline = get_next_op(); + opline->opcode = ZEND_INIT_PARENT_PROPERTY_HOOK_CALL; + opline->op1_type = IS_CONST; + zend_string_copy(property_name); + opline->op1.constant = zend_add_literal_string(&property_name); + opline->op2.num = hook_kind; + + zend_function *fbc = NULL; + zend_compile_call_common(result, args_ast, fbc, zend_ast_get_lineno(method_ast)); + + return true; +} + static void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ { zend_ast *name_ast = ast->child[0]; @@ -5178,6 +5283,10 @@ static void zend_compile_static_call(znode *result, zend_ast *ast, uint32_t type zend_op *opline; zend_function *fbc = NULL; + if (zend_compile_parent_property_hook_call(result, ast, type)) { + return; + } + zend_short_circuiting_mark_inner(class_ast); zend_compile_class_ref(&class_node, class_ast, ZEND_FETCH_CLASS_EXCEPTION); @@ -7326,6 +7435,83 @@ static void zend_compile_attributes( } /* }}} */ +static void zend_compile_property_hooks( + zend_property_info *prop_info, zend_string *prop_name, + zend_ast *prop_type_ast, zend_ast_list *hooks); + +typedef struct { + zend_string *property_name; + bool uses_property; +} find_property_usage_context; + +static void zend_property_hook_find_property_usage(zend_ast **ast_ptr, void *_context) /* {{{ */ +{ + zend_ast *ast = *ast_ptr; + find_property_usage_context *context = (find_property_usage_context *) _context; + + if (ast == NULL) { + return; + } else if (ast->kind == ZEND_AST_PROP || ast->kind == ZEND_AST_NULLSAFE_PROP) { + zend_ast *object_ast = ast->child[0]; + zend_ast *property_ast = ast->child[1]; + + if (object_ast->kind == ZEND_AST_VAR + && object_ast->child[0]->kind == ZEND_AST_ZVAL + && property_ast->kind == ZEND_AST_ZVAL) { + zval *object = zend_ast_get_zval(object_ast->child[0]); + zval *property = zend_ast_get_zval(property_ast); + if (Z_TYPE_P(object) == IS_STRING + && Z_TYPE_P(property) == IS_STRING + && zend_string_equals_literal(Z_STR_P(object), "this") + && zend_string_equals(Z_STR_P(property), context->property_name)) { + context->uses_property = true; + /* No need to look for references in this branch. */ + return; + } + } + } + + /* Don't search across function/class boundaries. */ + if (!zend_ast_is_special(ast)) { + zend_ast_apply(ast, zend_property_hook_find_property_usage, context); + } +} + +static bool zend_property_hook_uses_property(zend_string *property_name, zend_string *hook_name, zend_ast *hook_ast) +{ + if (zend_string_equals_literal_ci(hook_name, "set") + && hook_ast->kind == ZEND_AST_PROPERTY_HOOK_SHORT_BODY) { + return true; + } + + find_property_usage_context context = { property_name, false }; + zend_property_hook_find_property_usage(&hook_ast, &context); + return context.uses_property; +} + +static bool zend_property_is_virtual(zend_class_entry *ce, zend_string *property_name, zend_ast *hooks_ast, uint32_t flags) +{ + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + return true; + } + if (!hooks_ast) { + return false; + } + + bool is_virtual = true; + + zend_ast_list *hooks = zend_ast_get_list(hooks_ast); + for (uint32_t i = 0; i < hooks->children; i++) { + zend_ast_decl *hook = (zend_ast_decl *) hooks->child[i]; + zend_ast *body = hook->child[2]; + if (body && zend_property_hook_uses_property(property_name, hook->name, body)) { + is_virtual = false; + } + } + + return is_virtual; +} + static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fallback_return_type) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); @@ -7378,6 +7564,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zend_ast **default_ast_ptr = ¶m_ast->child[2]; zend_ast *attributes_ast = param_ast->child[3]; zend_ast *doc_comment_ast = param_ast->child[4]; + zend_ast *hooks_ast = param_ast->child[5]; zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast)); bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0; @@ -7556,9 +7743,13 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 } /* Don't give the property an explicit default value. For typed properties this means - * uninitialized, for untyped properties it means an implicit null default value. */ + * uninitialized, for untyped properties it means an implicit null default value. + * Properties with hooks get an implicit default value of undefined until inheritance, + * where it is changed to null only once we know it is not virtual. If we were to set it + * here, we couldn't verify that a true virtual property must not have an explicit + * default value. */ zval default_value; - if (ZEND_TYPE_IS_SET(type)) { + if (ZEND_TYPE_IS_SET(type) || hooks_ast) { ZVAL_UNDEF(&default_value); } else { if (property_flags & ZEND_ACC_READONLY) { @@ -7572,7 +7763,13 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zend_string *doc_comment = doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL; zend_property_info *prop = zend_declare_typed_property( - scope, name, &default_value, property_flags | ZEND_ACC_PROMOTED, doc_comment, type); + scope, name, &default_value, + property_flags | (zend_property_is_virtual(scope, name, hooks_ast, property_flags) ? ZEND_ACC_VIRTUAL : 0) | ZEND_ACC_PROMOTED, + doc_comment, type); + if (hooks_ast) { + zend_ast_list *hooks = zend_ast_get_list(hooks_ast); + zend_compile_property_hooks(prop, name, type_ast, hooks); + } if (attributes_ast) { zend_compile_attributes( &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, ZEND_ATTRIBUTE_TARGET_PARAMETER); @@ -7988,15 +8185,19 @@ static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, } /* }}} */ -static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ -{ +static zend_op_array *zend_compile_func_decl_ex( + znode *result, zend_ast *ast, bool toplevel, + const zend_property_info *property_info, + zend_property_hook_kind hook_kind +) { zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *params_ast = decl->child[0]; zend_ast *uses_ast = decl->child[1]; zend_ast *stmt_ast = decl->child[2]; zend_ast *return_type_ast = decl->child[3]; bool is_method = decl->kind == ZEND_AST_METHOD; - zend_string *lcname; + zend_string *lcname = NULL; + bool is_hook = decl->kind == ZEND_AST_PROPERTY_HOOK; zend_class_entry *orig_class_entry = CG(active_class_entry); zend_op_array *orig_op_array = CG(active_op_array); @@ -8023,7 +8224,11 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) op_array->fn_flags |= ZEND_ACC_CLOSURE; } - if (is_method) { + if (is_hook) { + zend_class_entry *ce = CG(active_class_entry); + op_array->scope = ce; + op_array->function_name = zend_string_copy(decl->name); + } else if (is_method) { bool has_body = stmt_ast != NULL; lcname = zend_begin_method_decl(op_array, decl->name, has_body); } else { @@ -8041,7 +8246,7 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) if (decl->child[4]) { int target = ZEND_ATTRIBUTE_TARGET_FUNCTION; - if (is_method) { + if (is_method || is_hook) { target = ZEND_ATTRIBUTE_TARGET_METHOD; } @@ -8080,6 +8285,8 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) } zend_oparray_context_begin(&orig_oparray_context, op_array); + CG(context).active_property_info = property_info; + CG(context).active_property_hook_kind = hook_kind; { /* Push a separator to the loop variable stack */ @@ -8143,12 +8350,199 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) zend_observer_function_declared_notify(op_array, lcname); } - zend_string_release_ex(lcname, 0); + if (lcname != NULL) { + zend_string_release_ex(lcname, 0); + } CG(active_op_array) = orig_op_array; CG(active_class_entry) = orig_class_entry; + + return op_array; +} + +static zend_op_array *zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) +{ + return zend_compile_func_decl_ex(result, ast, toplevel, /* property_info */ NULL, (zend_property_hook_kind)-1); +} + +zend_property_hook_kind zend_get_property_hook_kind_from_name(zend_string *name) { + if (zend_string_equals_literal_ci(name, "get")) { + return ZEND_PROPERTY_HOOK_GET; + } else if (zend_string_equals_literal_ci(name, "set")) { + return ZEND_PROPERTY_HOOK_SET; + } else { + return (zend_property_hook_kind)-1; + } +} + +static void zend_compile_property_hooks( + zend_property_info *prop_info, zend_string *prop_name, + zend_ast *prop_type_ast, zend_ast_list *hooks) +{ + zend_class_entry *ce = CG(active_class_entry); + + if (hooks->children == 0) { + zend_error_noreturn(E_COMPILE_ERROR, "Property hook list cannot be empty"); + } + + for (uint32_t i = 0; i < hooks->children; i++) { + zend_ast_decl *hook = (zend_ast_decl *) hooks->child[i]; + zend_string *name = hook->name; + zend_ast *stmt_ast = hook->child[2]; + zend_ast **return_type_ast_ptr = NULL; + zend_ast **value_type_ast_ptr = NULL; + CG(zend_lineno) = hook->start_lineno; + + /* Non-private hooks are always public. This avoids having to copy the hook when inheriting + * hooks from protected properties to public ones. */ + uint32_t hook_visibility = (prop_info->flags & ZEND_ACC_PPP_MASK) != ZEND_ACC_PRIVATE ? ZEND_ACC_PUBLIC : ZEND_ACC_PRIVATE; + hook->flags |= hook_visibility; + + if (prop_info->flags & ZEND_ACC_STATIC) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare hooks for static property"); + } + if ((hook->flags & ZEND_ACC_FINAL) && (hook->flags & ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, "Property hook cannot be both final and private"); + } + if ((ce->ce_flags & ZEND_ACC_INTERFACE) + || ((prop_info->flags & ZEND_ACC_ABSTRACT) && !stmt_ast)) { + hook->flags |= ZEND_ACC_ABSTRACT; + + if (stmt_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Abstract property hook cannot have body"); + } + if (hook->flags & ZEND_ACC_PRIVATE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Property hook cannot be both abstract and private"); + } + if (hook->flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, "Property hook cannot be both abstract and final"); + } + } else if (!stmt_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Non-abstract property hook must have a body"); + } + + zend_property_hook_kind hook_kind = zend_get_property_hook_kind_from_name(name); + if (hook_kind == (zend_property_hook_kind)-1) { + zend_error_noreturn(E_COMPILE_ERROR, + "Unknown hook \"%s\" for property %s::$%s, expected \"get\" or \"set\"", + ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + + if (stmt_ast && stmt_ast->kind == ZEND_AST_PROPERTY_HOOK_SHORT_BODY) { + stmt_ast = stmt_ast->child[0]; + if (hook_kind == ZEND_PROPERTY_HOOK_GET) { + stmt_ast = zend_ast_create(ZEND_AST_RETURN, stmt_ast); + } else { + ZEND_ASSERT(hook_kind == ZEND_PROPERTY_HOOK_SET); + stmt_ast = zend_ast_create(ZEND_AST_ASSIGN, + zend_ast_create(ZEND_AST_PROP, + zend_ast_create(ZEND_AST_VAR, zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS))), + zend_ast_create_zval_from_str(zend_copy_unmangled_prop_name(prop_info->name))), + stmt_ast); + } + stmt_ast = zend_ast_create_list(1, ZEND_AST_STMT_LIST, stmt_ast); + hook->child[2] = stmt_ast; + } + + if (hook_kind == ZEND_PROPERTY_HOOK_GET) { + if (hook->child[0]) { + zend_error_noreturn(E_COMPILE_ERROR, "get hook of property %s::$%s must not have a parameter list", + ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + + hook->child[0] = zend_ast_create_list(0, ZEND_AST_PARAM_LIST); + + return_type_ast_ptr = &hook->child[3]; + *return_type_ast_ptr = prop_type_ast; + } else if (hook_kind == ZEND_PROPERTY_HOOK_SET) { + if (hook->child[0]) { + zend_ast_list *param_list = zend_ast_get_list(hook->child[0]); + if (param_list->children != 1) { + zend_error_noreturn(E_COMPILE_ERROR, "%s hook of property %s::$%s must accept exactly one parameters", + ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + zend_ast *value_param_ast = param_list->child[0]; + if (value_param_ast->attr & ZEND_PARAM_REF) { + zend_error_noreturn(E_COMPILE_ERROR, "Parameter $%s of %s hook %s::$%s must not be pass-by-reference", + ZSTR_VAL(zend_ast_get_str(value_param_ast->child[1])), ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + if (value_param_ast->attr & ZEND_PARAM_VARIADIC) { + zend_error_noreturn(E_COMPILE_ERROR, "Parameter $%s of %s hook %s::$%s must not be variadic", + ZSTR_VAL(zend_ast_get_str(value_param_ast->child[1])), ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + if (value_param_ast->child[2]) { + zend_error_noreturn(E_COMPILE_ERROR, "Parameter $%s of %s hook %s::$%s must not have a default value", + ZSTR_VAL(zend_ast_get_str(value_param_ast->child[1])), ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + if ((prop_type_ast != NULL) != (value_param_ast->child[0] != NULL)) { + zend_hooked_property_variance_error_ex(zend_ast_get_str(value_param_ast->child[1]), ce->name, prop_info->name); + } + } else { + zend_ast *param_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_VALUE)); + zend_ast *param = zend_ast_create( + ZEND_AST_PARAM, prop_type_ast, param_name_ast, + /* expr */ NULL, /* doc_comment */ NULL, /* attributes */ NULL, + /* hooks */ NULL); + value_type_ast_ptr = ¶m->child[0]; + hook->child[0] = zend_ast_create_list(1, ZEND_AST_PARAM_LIST, param); + } + zend_ast *return_type = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_VOID)); + return_type->attr = ZEND_NAME_NOT_FQ; + hook->child[3] = return_type; + } else { + ZEND_UNREACHABLE(); + } + + hook->name = zend_strpprintf(0, "$%s::%s", ZSTR_VAL(prop_name), ZSTR_VAL(name)); + + zend_function *func = (zend_function *) zend_compile_func_decl_ex( + NULL, (zend_ast *) hook, /* toplevel */ false, prop_info, hook_kind); + + func->common.prop_info = prop_info; + + if (!prop_info->hooks) { + prop_info->hooks = zend_arena_alloc(&CG(arena), ZEND_PROPERTY_HOOK_STRUCT_SIZE); + memset(prop_info->hooks, 0, ZEND_PROPERTY_HOOK_STRUCT_SIZE); + } + + if (prop_info->hooks[hook_kind]) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot redeclare property hook \"%s\"", ZSTR_VAL(name)); + } + prop_info->hooks[hook_kind] = func; + + if (hook_kind == ZEND_PROPERTY_HOOK_SET) { + switch (zend_verify_property_hook_variance(prop_info, func)) { + case INHERITANCE_SUCCESS: + break; + case INHERITANCE_UNRESOLVED: + ce->num_hooked_prop_variance_checks++; + break; + case INHERITANCE_ERROR: + zend_hooked_property_variance_error(prop_info); + case INHERITANCE_WARNING: + ZEND_UNREACHABLE(); + } + } + + zend_string_release(name); + /* Un-share type ASTs to avoid double-frees of zval nodes. */ + if (return_type_ast_ptr) { + *return_type_ast_ptr = NULL; + } + if (value_type_ast_ptr) { + *value_type_ast_ptr = NULL; + } + } + + ce->num_hooked_props++; + + if (!ce->get_iterator) { + /* Will be removed again, in case of Iterator or IteratorAggregate. */ + ce->get_iterator = zend_hooked_object_get_iterator; + } } -/* }}} */ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */ { @@ -8156,24 +8550,60 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f zend_class_entry *ce = CG(active_class_entry); uint32_t i, children = list->children; - if (ce->ce_flags & ZEND_ACC_INTERFACE) { - zend_error_noreturn(E_COMPILE_ERROR, "Interfaces may not include properties"); - } - if (ce->ce_flags & ZEND_ACC_ENUM) { zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include properties", ZSTR_VAL(ce->name)); } + if ((flags & ZEND_ACC_FINAL) && (flags & ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, "Property cannot be both final and private"); + } + + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + if (flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, "Property in interface cannot be final"); + } + if (flags & (ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, "Property in interface cannot be protected or private"); + } + if (flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Property in interface cannot be explicitly abstract. " + "All interface members are implicitly abstract"); + } + flags |= ZEND_ACC_ABSTRACT; + } + for (i = 0; i < children; ++i) { zend_property_info *info; zend_ast *prop_ast = list->child[i]; zend_ast *name_ast = prop_ast->child[0]; zend_ast **value_ast_ptr = &prop_ast->child[1]; zend_ast *doc_comment_ast = prop_ast->child[2]; + zend_ast *hooks_ast = prop_ast->child[3]; zend_string *name = zval_make_interned_string(zend_ast_get_zval(name_ast)); zend_string *doc_comment = NULL; zval value_zv; zend_type type = ZEND_TYPE_INIT_NONE(0); + flags |= zend_property_is_virtual(ce, name, hooks_ast, flags) ? ZEND_ACC_VIRTUAL : 0; + + if (!hooks_ast) { + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Interfaces may only include hooked properties"); + } + if (flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Only hooked properties may be declared abstract"); + } + } + if ((flags & ZEND_ACC_ABSTRACT)) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } + + if (hooks_ast && (flags & ZEND_ACC_READONLY)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Hooked properties cannot be readonly"); + } if (type_ast) { type = zend_compile_typename(type_ast); @@ -8217,7 +8647,7 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(str)); } } - } else if (!ZEND_TYPE_IS_SET(type)) { + } else if (!ZEND_TYPE_IS_SET(type) && !hooks_ast) { ZVAL_NULL(&value_zv); } else { ZVAL_UNDEF(&value_zv); @@ -8246,6 +8676,14 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type); + if (hooks_ast) { + zend_compile_property_hooks(info, name, type_ast, zend_ast_get_list(hooks_ast)); + + if (!ce->parent_name) { + zend_verify_hooked_property(ce, info, name); + } + } + if (attr_ast) { zend_compile_attributes(&info->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, 0); } @@ -8621,7 +9059,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) } /* We currently don't early-bind classes that implement interfaces or use traits */ - if (!ce->num_interfaces && !ce->num_traits + if (!ce->num_interfaces && !ce->num_traits && !ce->num_hooked_prop_variance_checks && !(CG(compiler_options) & ZEND_COMPILE_WITHOUT_EXECUTION)) { if (toplevel) { if (extends_ast) { @@ -8690,7 +9128,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) if (toplevel && (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) /* We currently don't early-bind classes that implement interfaces or use traits */ - && !ce->num_interfaces && !ce->num_traits + && !ce->num_interfaces && !ce->num_traits && !ce->num_hooked_prop_variance_checks ) { if (!extends_ast) { /* Use empty string for classes without parents to avoid new handler, and special @@ -9085,6 +9523,15 @@ static bool zend_try_ct_eval_magic_const(zval *zv, zend_ast *ast) /* {{{ */ ZVAL_EMPTY_STRING(zv); } break; + case T_PROPERTY_C: { + const zend_property_info *prop_info = CG(context).active_property_info; + if (prop_info) { + ZVAL_STR(zv, zend_copy_unmangled_prop_name(prop_info->name)); + } else { + ZVAL_EMPTY_STRING(zv); + } + break; + } case T_METHOD_C: /* Detect whether we are directly inside a class (e.g. a class constant) and treat * this as not being inside a function. */ @@ -10962,6 +11409,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: case ZEND_AST_STATIC_CALL: + case ZEND_AST_PARENT_PROPERTY_HOOK_CALL: zend_compile_var(result, ast, BP_VAR_R, 0); return; case ZEND_AST_ASSIGN: @@ -11117,6 +11565,9 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty case ZEND_AST_CALL: zend_compile_call(result, ast, type); return NULL; + case ZEND_AST_PARENT_PROPERTY_HOOK_CALL: + zend_compile_parent_property_hook_call(result, ast, type); + return NULL; case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: zend_compile_method_call(result, ast, type); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 6a5626492ec73..32eed2b9d6c92 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -30,6 +30,7 @@ #include "zend_llist.h" #include "zend_frameless_function.h" +#include "zend_property_hooks.h" #define SET_UNUSED(op) do { \ op ## _type = IS_UNUSED; \ @@ -188,6 +189,8 @@ typedef struct _zend_live_range { uint32_t end; } zend_live_range; +typedef struct _zend_property_info zend_property_info; + /* Compilation context that is different for each op array. */ typedef struct _zend_oparray_context { struct _zend_oparray_context *prev; @@ -201,6 +204,8 @@ typedef struct _zend_oparray_context { int last_brk_cont; zend_brk_cont_element *brk_cont_array; HashTable *labels; + const zend_property_info *active_property_info; + zend_property_hook_kind active_property_hook_kind; bool in_jmp_frameless_branch; } zend_oparray_context; @@ -220,14 +225,11 @@ typedef struct _zend_oparray_context { /* Static method or property | | | */ #define ZEND_ACC_STATIC (1 << 4) /* | X | X | */ /* | | | */ -/* Promoted property / parameter | | | */ -#define ZEND_ACC_PROMOTED (1 << 5) /* | | X | X */ -/* | | | */ /* Final class or method | | | */ -#define ZEND_ACC_FINAL (1 << 5) /* X | X | | */ +#define ZEND_ACC_FINAL (1 << 5) /* X | X | X | */ /* | | | */ /* Abstract method | | | */ -#define ZEND_ACC_ABSTRACT (1 << 6) /* X | X | | */ +#define ZEND_ACC_ABSTRACT (1 << 6) /* X | X | X | */ #define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS (1 << 6) /* X | | | */ /* | | | */ /* Readonly property | | | */ @@ -251,6 +253,15 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ +/* Property Flags (unused: 10...) | | | */ +/* =========== | | | */ +/* | | | */ +/* Promoted property / parameter | | | */ +#define ZEND_ACC_PROMOTED (1 << 8) /* | | X | */ +/* | | | */ +/* Virtual property without backing storage | | | */ +#define ZEND_ACC_VIRTUAL (1 << 9) /* | | X | */ +/* | | | */ /* Class Flags (unused: 30,31) | | | */ /* =========== | | | */ /* | | | */ @@ -398,6 +409,14 @@ typedef struct _zend_oparray_context { char *zend_visibility_string(uint32_t fn_flags); +#define ZEND_PROPERTY_HOOK_COUNT 2 +#define ZEND_PROPERTY_HOOK_STRUCT_SIZE (sizeof(zend_function*) * ZEND_PROPERTY_HOOK_COUNT) + +/* Stored in zend_property_info.offset, not returned by zend_get_property_offset(). */ +#define ZEND_VIRTUAL_PROPERTY_OFFSET ((uint32_t)-1) + +zend_property_hook_kind zend_get_property_hook_kind_from_name(zend_string *name); + typedef struct _zend_property_info { uint32_t offset; /* property offset for object properties or property index for static properties */ @@ -407,6 +426,8 @@ typedef struct _zend_property_info { HashTable *attributes; zend_class_entry *ce; zend_type type; + const zend_property_info *prototype; + zend_function **hooks; } zend_property_info; #define OBJ_PROP(obj, offset) \ @@ -468,6 +489,7 @@ struct _zend_op_array { ZEND_MAP_PTR_DEF(void **, run_time_cache); zend_string *doc_comment; uint32_t T; /* number of temporary variables */ + const zend_property_info *prop_info; /* The corresponding prop_info if this is a hook. */ /* END of common elements */ int cache_size; /* number of run_time_cache_slots * sizeof(void*) */ @@ -526,6 +548,7 @@ typedef struct _zend_internal_function { ZEND_MAP_PTR_DEF(void **, run_time_cache); zend_string *doc_comment; uint32_t T; /* number of temporary variables */ + const zend_property_info *prop_info; /* The corresponding prop_info if this is a hook. */ /* END of common elements */ zif_handler handler; @@ -554,6 +577,7 @@ union _zend_function { ZEND_MAP_PTR_DEF(void **, run_time_cache); zend_string *doc_comment; uint32_t T; /* number of temporary variables */ + const zend_property_info *prop_info; /* The corresponding prop_info if this is a hook. */ } common; zend_op_array op_array; @@ -843,6 +867,7 @@ typedef enum { ZEND_MODIFIER_TARGET_METHOD, ZEND_MODIFIER_TARGET_CONSTANT, ZEND_MODIFIER_TARGET_CPP, + ZEND_MODIFIER_TARGET_PROPERTY_HOOK, } zend_modifier_target; /* Used during AST construction */ @@ -1142,6 +1167,9 @@ static zend_always_inline bool zend_check_arg_send_type(const zend_function *zf, /* Attribute for ternary inside parentheses */ #define ZEND_PARENTHESIZED_CONDITIONAL 1 +/* Used to distinguish (parent::$prop)::get() from parent hook call. */ +#define ZEND_PARENTHESIZED_STATIC_PROP 1 + /* For "use" AST nodes and the seen symbol table */ #define ZEND_SYMBOL_CLASS (1<<0) #define ZEND_SYMBOL_FUNCTION (1<<1) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index b92c4c1174cdc..084625ba0c4d2 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -152,6 +152,7 @@ ZEND_API const zend_internal_function zend_pass_function = { NULL, /* run_time_cache */ NULL, /* doc_comment */ 0, /* T */ + NULL, /* prop_info */ ZEND_FN(pass), /* handler */ NULL, /* module */ NULL, /* frameless_function_infos */ @@ -3373,7 +3374,10 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c } return; } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { GC_DELREF(zobj->properties); @@ -3421,7 +3425,7 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c if (prop_op_type == IS_CONST) { prop_info = CACHED_PTR_EX(cache_slot + 2); - if (prop_info) { + if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { if (UNEXPECTED(!zend_handle_fetch_obj_flags(result, ptr, NULL, prop_info, flags))) { goto end; } @@ -4505,6 +4509,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o opline->opcode == ZEND_INIT_USER_CALL || opline->opcode == ZEND_INIT_METHOD_CALL || opline->opcode == ZEND_INIT_STATIC_METHOD_CALL || + opline->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL || opline->opcode == ZEND_NEW)) { ZEND_ASSERT(op_num); opline--; @@ -4533,6 +4538,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o case ZEND_INIT_USER_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_NEW: if (level == 0) { ZEND_CALL_NUM_ARGS(call) = 0; @@ -4589,6 +4595,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o case ZEND_INIT_USER_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_NEW: if (level == 0) { do_exit = 1; diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 9aafeb70f13b5..f1f5bfc84516f 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -371,6 +371,29 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) } } } ZEND_HASH_FOREACH_END(); + + if (ce->num_hooked_props) { + zend_property_info *prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop_info) { + if (prop_info->ce == ce) { + if (prop_info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop_info->hooks[i]) { + ZEND_ASSERT(ZEND_USER_CODE(prop_info->hooks[i]->type)); + op_array = &prop_info->hooks[i]->op_array; + if (ZEND_MAP_PTR(op_array->static_variables_ptr)) { + HashTable *ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr); + if (ht) { + zend_array_destroy(ht); + ZEND_MAP_PTR_SET(op_array->static_variables_ptr, NULL); + } + } + } + } + } + } + } ZEND_HASH_FOREACH_END(); + } } } ZEND_HASH_FOREACH_END(); diff --git a/Zend/zend_highlight.c b/Zend/zend_highlight.c index 167ee27192ed4..5c3cd136d5807 100644 --- a/Zend/zend_highlight.c +++ b/Zend/zend_highlight.c @@ -102,6 +102,7 @@ ZEND_API void zend_highlight(zend_syntax_highlighter_ini *syntax_highlighter_ini case T_TRAIT_C: case T_METHOD_C: case T_FUNC_C: + case T_PROPERTY_C: case T_NS_C: case T_CLASS_C: next_color = syntax_highlighter_ini->highlight_default; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 8aa4a346a00a9..d33ad2500fb5b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -37,12 +37,13 @@ ZEND_API zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, z /* Unresolved means that class declarations that are currently not available are needed to * determine whether the inheritance is valid or not. At runtime UNRESOLVED should be treated * as an ERROR. */ +typedef zend_inheritance_status inheritance_status; + typedef enum { - INHERITANCE_UNRESOLVED = -1, - INHERITANCE_ERROR = 0, - INHERITANCE_WARNING = 1, - INHERITANCE_SUCCESS = 2, -} inheritance_status; + PROP_INVARIANT, + PROP_COVARIANT, + PROP_CONTRAVARIANT, +} prop_variance; static void add_dependency_obligation(zend_class_entry *ce, zend_class_entry *dependency_ce); static void add_compatibility_obligation( @@ -50,10 +51,12 @@ static void add_compatibility_obligation( const zend_function *parent_fn, zend_class_entry *parent_scope); static void add_property_compatibility_obligation( zend_class_entry *ce, const zend_property_info *child_prop, - const zend_property_info *parent_prop); + const zend_property_info *parent_prop, prop_variance variance); static void add_class_constant_compatibility_obligation( zend_class_entry *ce, const zend_class_constant *child_const, const zend_class_constant *parent_const, const zend_string *const_name); +static void add_property_hook_obligation( + zend_class_entry *ce, const zend_property_info *hooked_prop, const zend_function *hook_func); static void ZEND_COLD emit_incompatible_method_error( const zend_function *child, zend_class_entry *child_scope, @@ -633,7 +636,7 @@ static inheritance_status zend_is_intersection_subtype_of_type( return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } -static inheritance_status zend_perform_covariant_type_check( +ZEND_API inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, zend_type fe_type, zend_class_entry *proto_scope, zend_type proto_type) { @@ -1240,7 +1243,8 @@ static void do_inherit_method(zend_string *key, zend_function *parent, zend_clas /* }}} */ static inheritance_status property_types_compatible( - const zend_property_info *parent_info, const zend_property_info *child_info) { + const zend_property_info *parent_info, const zend_property_info *child_info, + prop_variance variance) { if (ZEND_TYPE_PURE_MASK(parent_info->type) == ZEND_TYPE_PURE_MASK(child_info->type) && ZEND_TYPE_NAME(parent_info->type) == ZEND_TYPE_NAME(child_info->type)) { return INHERITANCE_SUCCESS; @@ -1251,10 +1255,12 @@ static inheritance_status property_types_compatible( } /* Perform a covariant type check in both directions to determined invariance. */ - inheritance_status status1 = zend_perform_covariant_type_check( - child_info->ce, child_info->type, parent_info->ce, parent_info->type); - inheritance_status status2 = zend_perform_covariant_type_check( - parent_info->ce, parent_info->type, child_info->ce, child_info->type); + inheritance_status status1 = variance == PROP_CONTRAVARIANT ? INHERITANCE_SUCCESS : + zend_perform_covariant_type_check( + child_info->ce, child_info->type, parent_info->ce, parent_info->type); + inheritance_status status2 = variance == PROP_COVARIANT ? INHERITANCE_SUCCESS : + zend_perform_covariant_type_check( + parent_info->ce, parent_info->type, child_info->ce, child_info->type); if (status1 == INHERITANCE_SUCCESS && status2 == INHERITANCE_SUCCESS) { return INHERITANCE_SUCCESS; } @@ -1266,16 +1272,84 @@ static inheritance_status property_types_compatible( } static void emit_incompatible_property_error( - const zend_property_info *child, const zend_property_info *parent) { + const zend_property_info *child, const zend_property_info *parent, prop_variance variance) { zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce); zend_error_noreturn(E_COMPILE_ERROR, - "Type of %s::$%s must be %s (as in class %s)", + "Type of %s::$%s must be %s%s (as in class %s)", ZSTR_VAL(child->ce->name), zend_get_unmangled_property_name(child->name), + variance == PROP_INVARIANT ? "" : + variance == PROP_COVARIANT ? "subtype of " : "supertype of ", ZSTR_VAL(type_str), ZSTR_VAL(parent->ce->name)); } +static void inherit_property_hook( + zend_class_entry *ce, + zend_property_info *parent_info, zend_function **parent_ptr, + zend_property_info *child_info, zend_function **child_ptr +) { + zend_function *parent = *parent_ptr; + zend_function *child = *child_ptr; + + if (child && (child->common.fn_flags & ZEND_ACC_OVERRIDE)) { + if (!(parent_info->flags & ZEND_ACC_VIRTUAL) || parent) { + child->common.fn_flags &= ~ZEND_ACC_OVERRIDE; + } + } + + if (!parent) { + return; + } + + if (!child) { + if (parent->common.fn_flags & ZEND_ACC_ABSTRACT) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } + + *child_ptr = zend_duplicate_function(parent, ce); + return; + } + + child->common.prototype = parent->common.prototype ? parent->common.prototype : parent; + + uint32_t parent_flags = parent->common.fn_flags; + if (parent_flags & ZEND_ACC_PRIVATE) { + child->common.fn_flags |= ZEND_ACC_CHANGED; + return; + } + + if (parent_flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot override final property hook %s::%s()", + ZSTR_VAL(parent->common.scope->name), + ZSTR_VAL(parent->common.function_name)); + } + + do_inheritance_check_on_method( + child, child->common.scope, parent, parent->common.scope, ce, /* child */ NULL, + ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY + | ZEND_INHERITANCE_SET_CHILD_CHANGED | ZEND_INHERITANCE_SET_CHILD_PROTO + | ZEND_INHERITANCE_RESET_CHILD_OVERRIDE); + + /* Other signature compatibility issues should already be covered either by the + * properties being compatible (types), or certain signatures being forbidden by the + * compiler (variadic and by-ref args, etc). */ +} + +static prop_variance prop_get_variance(zend_property_info *prop_info) { + bool unbacked = prop_info->flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_VIRTUAL); + if (unbacked && prop_info->hooks) { + if (!prop_info->hooks[ZEND_PROPERTY_HOOK_SET]) { + return PROP_COVARIANT; + } + if (!prop_info->hooks[ZEND_PROPERTY_HOOK_GET]) { + return PROP_CONTRAVARIANT; + } + } + return PROP_INVARIANT; +} + static void do_inherit_property(zend_property_info *parent_info, zend_string *key, zend_class_entry *ce) /* {{{ */ { zval *child = zend_hash_find_known_hash(&ce->properties_info, key); @@ -1286,41 +1360,82 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke if (parent_info->flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED)) { child_info->flags |= ZEND_ACC_CHANGED; } + if (parent_info->flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot override final property %s::$%s", + ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key)); + } if (!(parent_info->flags & ZEND_ACC_PRIVATE)) { + if (!(parent_info->ce->ce_flags & ZEND_ACC_INTERFACE)) { + child_info->prototype = parent_info; + } + if (UNEXPECTED((parent_info->flags & ZEND_ACC_STATIC) != (child_info->flags & ZEND_ACC_STATIC))) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s%s::$%s as %s%s::$%s", (parent_info->flags & ZEND_ACC_STATIC) ? "static " : "non static ", ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key), (child_info->flags & ZEND_ACC_STATIC) ? "static " : "non static ", ZSTR_VAL(ce->name), ZSTR_VAL(key)); } if (UNEXPECTED((child_info->flags & ZEND_ACC_READONLY) != (parent_info->flags & ZEND_ACC_READONLY))) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot redeclare %s property %s::$%s as %s %s::$%s", - parent_info->flags & ZEND_ACC_READONLY ? "readonly" : "non-readonly", - ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key), - child_info->flags & ZEND_ACC_READONLY ? "readonly" : "non-readonly", - ZSTR_VAL(ce->name), ZSTR_VAL(key)); + /* If the parent property is abstract, the child may be readonly if the parent only requires read. */ + if (parent_info->flags & ZEND_ACC_ABSTRACT) { + ZEND_ASSERT(!(parent_info->flags & ZEND_ACC_READONLY)); + ZEND_ASSERT(parent_info->hooks); + if (UNEXPECTED(parent_info->hooks[ZEND_PROPERTY_HOOK_SET])) { + zend_error_noreturn(E_COMPILE_ERROR, + "Readonly property %s::$%s does not satisfy abstract read-write property %s::$%s", + ZSTR_VAL(ce->name), ZSTR_VAL(key), + ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key)); + } + } else { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot redeclare %s property %s::$%s as %s %s::$%s", + parent_info->flags & ZEND_ACC_READONLY ? "readonly" : "non-readonly", + ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key), + child_info->flags & ZEND_ACC_READONLY ? "readonly" : "non-readonly", + ZSTR_VAL(ce->name), ZSTR_VAL(key)); + } } if (UNEXPECTED((child_info->flags & ZEND_ACC_PPP_MASK) > (parent_info->flags & ZEND_ACC_PPP_MASK))) { zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::$%s must be %s (as in class %s)%s", ZSTR_VAL(ce->name), ZSTR_VAL(key), zend_visibility_string(parent_info->flags), ZSTR_VAL(parent_info->ce->name), (parent_info->flags&ZEND_ACC_PUBLIC) ? "" : " or weaker"); - } else if ((child_info->flags & ZEND_ACC_STATIC) == 0) { - int parent_num = OBJ_PROP_TO_NUM(parent_info->offset); - int child_num = OBJ_PROP_TO_NUM(child_info->offset); - - /* Don't keep default properties in GC (they may be freed by opcache) */ - zval_ptr_dtor_nogc(&(ce->default_properties_table[parent_num])); - ce->default_properties_table[parent_num] = ce->default_properties_table[child_num]; - ZVAL_UNDEF(&ce->default_properties_table[child_num]); + } + if (!(child_info->flags & ZEND_ACC_STATIC) && !(parent_info->flags & ZEND_ACC_VIRTUAL)) { + if (child_info->offset != ZEND_VIRTUAL_PROPERTY_OFFSET) { + int parent_num = OBJ_PROP_TO_NUM(parent_info->offset); + int child_num = OBJ_PROP_TO_NUM(child_info->offset); + + /* Don't keep default properties in GC (they may be freed by opcache) */ + zval_ptr_dtor_nogc(&(ce->default_properties_table[parent_num])); + ce->default_properties_table[parent_num] = ce->default_properties_table[child_num]; + ZVAL_UNDEF(&ce->default_properties_table[child_num]); + } + child_info->offset = parent_info->offset; + child_info->flags &= ~ZEND_ACC_VIRTUAL; + } + + zend_function **parent_hooks = parent_info->hooks; + zend_function **child_hooks = child_info->hooks; + if (child_hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (parent_hooks) { + inherit_property_hook(ce, parent_info, &parent_hooks[i], child_info, &child_hooks[i]); + } else if (child_hooks[i]) { + child_hooks[i]->common.fn_flags &= ~ZEND_ACC_OVERRIDE; + } + } + } else if (parent_hooks) { + ce->num_hooked_props++; } + prop_variance variance = prop_get_variance(parent_info); if (ZEND_TYPE_IS_SET(parent_info->type)) { - inheritance_status status = property_types_compatible(parent_info, child_info); + inheritance_status status = property_types_compatible( + parent_info, child_info, variance); if (status == INHERITANCE_ERROR) { - emit_incompatible_property_error(child_info, parent_info); + emit_incompatible_property_error(child_info, parent_info, variance); } if (status == INHERITANCE_UNRESOLVED) { - add_property_compatibility_obligation(ce, child_info, parent_info); + add_property_compatibility_obligation(ce, child_info, parent_info, variance); } } else if (UNEXPECTED(ZEND_TYPE_IS_SET(child_info->type) && !ZEND_TYPE_IS_SET(parent_info->type))) { zend_error_noreturn(E_COMPILE_ERROR, @@ -1331,6 +1446,14 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } } } else { + zend_function **hooks = parent_info->hooks; + if (hooks) { + ce->num_hooked_props++; + if (parent_info->flags & ZEND_ACC_ABSTRACT) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } + } + _zend_hash_append_ptr(&ce->properties_info, key, parent_info); } } @@ -1470,12 +1593,82 @@ void zend_build_properties_info_table(zend_class_entry *ce) } ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) { - if (prop->ce == ce && (prop->flags & ZEND_ACC_STATIC) == 0) { + if (prop->ce == ce && (prop->flags & ZEND_ACC_STATIC) == 0 + && !(prop->flags & ZEND_ACC_VIRTUAL)) { table[OBJ_PROP_TO_NUM(prop->offset)] = prop; } } ZEND_HASH_FOREACH_END(); } +ZEND_API void zend_verify_hooked_property(zend_class_entry *ce, zend_property_info *prop_info, zend_string *prop_name) +{ + if (!prop_info->hooks) { + return; + } + bool abstract_error = prop_info->flags & ZEND_ACC_ABSTRACT; + /* We specified a default value (otherwise offset would be -1), but the virtual flag wasn't + * removed during inheritance. */ + if ((prop_info->flags & ZEND_ACC_VIRTUAL) && prop_info->offset != ZEND_VIRTUAL_PROPERTY_OFFSET) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot specify default value for virtual hooked property %s::$%s", ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + /* If the property turns backed during inheritance and no type and default value are set, we want + * the default value to be null. */ + if (!(prop_info->flags & ZEND_ACC_VIRTUAL) + && !ZEND_TYPE_IS_SET(prop_info->type) + && Z_TYPE(ce->default_properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]) == IS_UNDEF) { + ZVAL_NULL(&ce->default_properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]); + } + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + zend_function *func = prop_info->hooks[i]; + if (func) { + if ((zend_property_hook_kind)i == ZEND_PROPERTY_HOOK_GET + && (func->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) + && !(prop_info->flags & ZEND_ACC_VIRTUAL) + && prop_info->hooks[ZEND_PROPERTY_HOOK_SET]) { + zend_error_noreturn(E_COMPILE_ERROR, "Get hook of backed property %s::%s with set hook may not return by reference", + ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + if (func->common.fn_flags & ZEND_ACC_ABSTRACT) { + abstract_error = false; + } + } + } + if (abstract_error) { + zend_error_noreturn(E_COMPILE_ERROR, + "Abstract property %s::$%s must specify at least one abstract hook", ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } +} + +ZEND_API ZEND_COLD ZEND_NORETURN void zend_hooked_property_variance_error_ex(zend_string *value_param_name, zend_string *class_name, zend_string *prop_name) +{ + zend_error_noreturn(E_COMPILE_ERROR, "Type of parameter $%s of hook %s::$%s::set must be compatible with property type", + ZSTR_VAL(value_param_name), ZSTR_VAL(class_name), zend_get_unmangled_property_name(prop_name)); +} + +ZEND_API ZEND_COLD ZEND_NORETURN void zend_hooked_property_variance_error(const zend_property_info *prop_info) +{ + zend_string *value_param_name = prop_info->hooks[ZEND_PROPERTY_HOOK_SET]->op_array.arg_info[0].name; + zend_hooked_property_variance_error_ex(value_param_name, prop_info->ce->name, prop_info->name); +} + +ZEND_API inheritance_status zend_verify_property_hook_variance(const zend_property_info *prop_info, const zend_function *func) +{ + ZEND_ASSERT(prop_info->hooks && prop_info->hooks[ZEND_PROPERTY_HOOK_SET] == func); + + zend_arg_info *value_arg_info = &func->op_array.arg_info[0]; + if (!ZEND_TYPE_IS_SET(value_arg_info->type)) { + return INHERITANCE_SUCCESS; + } + + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + return INHERITANCE_ERROR; + } + + zend_class_entry *ce = prop_info->ce; + return zend_perform_covariant_type_check(ce, prop_info->type, ce, value_arg_info->type); +} + ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, bool checked) /* {{{ */ { zend_property_info *property_info; @@ -1616,7 +1809,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par if (property_info->ce == ce) { if (property_info->flags & ZEND_ACC_STATIC) { property_info->offset += parent_ce->default_static_members_count; - } else { + } else if (property_info->offset != ZEND_VIRTUAL_PROPERTY_OFFSET) { property_info->offset += parent_ce->default_properties_count * sizeof(zval); } } @@ -1632,6 +1825,12 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par } ZEND_HASH_FOREACH_END(); } + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, key, property_info) { + if (property_info->ce == ce && property_info->hooks) { + zend_verify_hooked_property(ce, property_info, key); + } + } ZEND_HASH_FOREACH_END(); + if (zend_hash_num_elements(&parent_ce->constants_table)) { zend_class_constant *c; @@ -1814,6 +2013,15 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * do_inherit_method(key, func, ce, 1, flags); } ZEND_HASH_FOREACH_END(); + zend_hash_extend(&ce->properties_info, + zend_hash_num_elements(&ce->properties_info) + + zend_hash_num_elements(&iface->properties_info), 0); + + zend_property_info *prop; + ZEND_HASH_FOREACH_STR_KEY_PTR(&iface->properties_info, key, prop) { + do_inherit_property(prop, key, ce); + } ZEND_HASH_FOREACH_END(); + do_implement_interface(ce, iface); if (iface->num_interfaces) { zend_do_inherit_interfaces(ce, iface); @@ -1949,6 +2157,26 @@ void zend_inheritance_check_override(zend_class_entry *ce) ZEND_FN_SCOPE_NAME(f), ZSTR_VAL(f->common.function_name)); } } ZEND_HASH_FOREACH_END(); + + if (ce->num_hooked_props) { + zend_property_info *prop; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) { + if (!prop->hooks) { + continue; + } + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + f = prop->hooks[i]; + if (f && f->common.fn_flags & ZEND_ACC_OVERRIDE) { + ZEND_ASSERT(f->type != ZEND_INTERNAL_FUNCTION); + + zend_error_at_noreturn( + E_COMPILE_ERROR, f->op_array.filename, f->op_array.line_start, + "%s::%s() has #[\\Override] attribute, but no matching parent method exists", + ZEND_FN_SCOPE_NAME(f), ZSTR_VAL(f->common.function_name)); + } + } + } ZEND_HASH_FOREACH_END(); + } } @@ -2511,8 +2739,17 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent bool is_compatible = false; uint32_t flags_mask = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY; + if (colliding_prop->hooks || property_info->hooks) { + zend_error_noreturn(E_COMPILE_ERROR, + "%s and %s define the same hooked property ($%s) in the composition of %s. Conflict resolution between hooked properties is currently not supported. Class was composed", + ZSTR_VAL(find_first_property_definition(ce, traits, i, prop_name, colliding_prop->ce)->name), + ZSTR_VAL(property_info->ce->name), + ZSTR_VAL(prop_name), + ZSTR_VAL(ce->name)); + } + if ((colliding_prop->flags & flags_mask) == (flags & flags_mask) && - property_types_compatible(property_info, colliding_prop) == INHERITANCE_SUCCESS + property_types_compatible(property_info, colliding_prop, PROP_INVARIANT) == INHERITANCE_SUCCESS ) { /* the flags are identical, thus, the properties may be compatible */ zval *op1, *op2; @@ -2553,14 +2790,19 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } /* property not found, so lets add it */ - if (flags & ZEND_ACC_STATIC) { - prop_value = &traits[i]->default_static_members_table[property_info->offset]; - ZEND_ASSERT(Z_TYPE_P(prop_value) != IS_INDIRECT); + zval tmp_prop_value; + if (!(flags & ZEND_ACC_VIRTUAL)) { + if (flags & ZEND_ACC_STATIC) { + prop_value = &traits[i]->default_static_members_table[property_info->offset]; + ZEND_ASSERT(Z_TYPE_P(prop_value) != IS_INDIRECT); + } else { + prop_value = &traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; + } + Z_TRY_ADDREF_P(prop_value); } else { - prop_value = &traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; + prop_value = &tmp_prop_value; + ZVAL_UNDEF(&tmp_prop_value); } - - Z_TRY_ADDREF_P(prop_value); doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL; zend_type type = property_info->type; @@ -2575,6 +2817,31 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent GC_ADDREF(new_prop->attributes); } } + if (property_info->hooks) { + zend_function **hooks = new_prop->hooks = + zend_arena_alloc(&CG(arena), ZEND_PROPERTY_HOOK_STRUCT_SIZE); + memcpy(hooks, property_info->hooks, ZEND_PROPERTY_HOOK_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (hooks[i]) { + zend_function *old_fn = hooks[i]; + + /* Hooks are not yet supported for internal properties. */ + ZEND_ASSERT(ZEND_USER_CODE(old_fn->type)); + + /* Copy the function, because we need to adjust the scope. */ + zend_function *new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); + memcpy(new_fn, old_fn, sizeof(zend_op_array)); + new_fn->op_array.fn_flags &= ~ZEND_ACC_IMMUTABLE; + new_fn->common.fn_flags |= ZEND_ACC_TRAIT_CLONE; + function_add_ref(new_fn); + + zend_fixup_trait_method(new_fn, ce); + + hooks[i] = new_fn; + } + } + ce->ce_flags |= ZEND_ACC_USE_GUARDS; + } } ZEND_HASH_FOREACH_END(); } } @@ -2647,6 +2914,20 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ } } ZEND_HASH_FOREACH_END(); + if (!is_explicit_abstract) { + zend_property_info *prop_info; + ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) { + if (prop_info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + zend_function *fn = prop_info->hooks[i]; + if (fn && (fn->common.fn_flags & ZEND_ACC_ABSTRACT)) { + zend_verify_abstract_class_function(fn, &ai); + } + } + } + } ZEND_HASH_FOREACH_END(); + } + if (ai.cnt) { zend_error_noreturn(E_ERROR, !is_explicit_abstract && can_be_abstract ? "%s %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")" @@ -2670,7 +2951,8 @@ typedef struct { OBLIGATION_DEPENDENCY, OBLIGATION_COMPATIBILITY, OBLIGATION_PROPERTY_COMPATIBILITY, - OBLIGATION_CLASS_CONSTANT_COMPATIBILITY + OBLIGATION_CLASS_CONSTANT_COMPATIBILITY, + OBLIGATION_PROPERTY_HOOK, } type; union { zend_class_entry *dependency_ce; @@ -2685,12 +2967,17 @@ typedef struct { struct { const zend_property_info *parent_prop; const zend_property_info *child_prop; + prop_variance variance; }; struct { const zend_string *const_name; const zend_class_constant *parent_const; const zend_class_constant *child_const; }; + struct { + const zend_property_info *hooked_prop; + const zend_function *hook_func; + }; }; } variance_obligation; @@ -2757,12 +3044,13 @@ static void add_compatibility_obligation( static void add_property_compatibility_obligation( zend_class_entry *ce, const zend_property_info *child_prop, - const zend_property_info *parent_prop) { + const zend_property_info *parent_prop, prop_variance variance) { HashTable *obligations = get_or_init_obligations_for_class(ce); variance_obligation *obligation = emalloc(sizeof(variance_obligation)); obligation->type = OBLIGATION_PROPERTY_COMPATIBILITY; obligation->child_prop = child_prop; obligation->parent_prop = parent_prop; + obligation->variance = variance; zend_hash_next_index_insert_ptr(obligations, obligation); } @@ -2778,6 +3066,16 @@ static void add_class_constant_compatibility_obligation( zend_hash_next_index_insert_ptr(obligations, obligation); } +static void add_property_hook_obligation( + zend_class_entry *ce, const zend_property_info *hooked_prop, const zend_function *hook_func) { + HashTable *obligations = get_or_init_obligations_for_class(ce); + variance_obligation *obligation = emalloc(sizeof(variance_obligation)); + obligation->type = OBLIGATION_PROPERTY_HOOK; + obligation->hooked_prop = hooked_prop; + obligation->hook_func = hook_func; + zend_hash_next_index_insert_ptr(obligations, obligation); +} + static void resolve_delayed_variance_obligations(zend_class_entry *ce); static void check_variance_obligation(variance_obligation *obligation) { @@ -2803,17 +3101,23 @@ static void check_variance_obligation(variance_obligation *obligation) { /* Either the compatibility check was successful or only threw a warning. */ } else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) { inheritance_status status = - property_types_compatible(obligation->parent_prop, obligation->child_prop); + property_types_compatible(obligation->parent_prop, obligation->child_prop, obligation->variance); if (status != INHERITANCE_SUCCESS) { - emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); + emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop, obligation->variance); } - } else { - ZEND_ASSERT(obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY); + } else if (obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY) { inheritance_status status = class_constant_types_compatible(obligation->parent_const, obligation->child_const); if (status != INHERITANCE_SUCCESS) { emit_incompatible_class_constant_error(obligation->child_const, obligation->parent_const, obligation->const_name); } + } else if (obligation->type == OBLIGATION_PROPERTY_HOOK) { + inheritance_status status = zend_verify_property_hook_variance(obligation->hooked_prop, obligation->hook_func); + if (status != INHERITANCE_SUCCESS) { + zend_hooked_property_variance_error(obligation->hooked_prop); + } + } else { + ZEND_UNREACHABLE(); } } @@ -2883,6 +3187,21 @@ static void check_unrecoverable_load_failure(zend_class_entry *ce) { } \ } while (0) +static zend_op_array *zend_lazy_method_load( + zend_op_array *op_array, zend_class_entry *ce, zend_class_entry *pce) { + ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(op_array->scope == pce); + ZEND_ASSERT(op_array->prototype == NULL); + zend_op_array *new_op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); + memcpy(new_op_array, op_array, sizeof(zend_op_array)); + new_op_array->fn_flags &= ~ZEND_ACC_IMMUTABLE; + new_op_array->scope = ce; + ZEND_MAP_PTR_INIT(new_op_array->run_time_cache, NULL); + ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, NULL); + + return new_op_array; +} + static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) { zend_class_entry *ce; @@ -2920,19 +3239,8 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) p = ce->function_table.arData; end = p + ce->function_table.nNumUsed; for (; p != end; p++) { - zend_op_array *op_array, *new_op_array; - - op_array = Z_PTR(p->val); - ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); - ZEND_ASSERT(op_array->scope == pce); - ZEND_ASSERT(op_array->prototype == NULL); - new_op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); - Z_PTR(p->val) = new_op_array; - memcpy(new_op_array, op_array, sizeof(zend_op_array)); - new_op_array->fn_flags &= ~ZEND_ACC_IMMUTABLE; - new_op_array->scope = ce; - ZEND_MAP_PTR_INIT(new_op_array->run_time_cache, NULL); - ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, NULL); + zend_op_array *op_array = Z_PTR(p->val); + zend_op_array *new_op_array = Z_PTR(p->val) = zend_lazy_method_load(op_array, ce, pce); zend_update_inherited_handler(constructor); zend_update_inherited_handler(destructor); @@ -2975,12 +3283,27 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) prop_info = Z_PTR(p->val); ZEND_ASSERT(prop_info->ce == pce); + ZEND_ASSERT(prop_info->prototype == prop_info); new_prop_info= zend_arena_alloc(&CG(arena), sizeof(zend_property_info)); Z_PTR(p->val) = new_prop_info; memcpy(new_prop_info, prop_info, sizeof(zend_property_info)); new_prop_info->ce = ce; + new_prop_info->prototype = new_prop_info; /* Deep copy the type information */ zend_type_copy_ctor(&new_prop_info->type, /* use_arena */ true, /* persistent */ false); + if (new_prop_info->hooks) { + new_prop_info->hooks = zend_arena_alloc(&CG(arena), ZEND_PROPERTY_HOOK_STRUCT_SIZE); + memcpy(new_prop_info->hooks, prop_info->hooks, ZEND_PROPERTY_HOOK_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (new_prop_info->hooks[i]) { + zend_op_array *hook = zend_lazy_method_load((zend_op_array *) new_prop_info->hooks[i], ce, pce); + ZEND_ASSERT(hook->prop_info == prop_info); + hook->prop_info = new_prop_info; + new_prop_info->ce = ce; + new_prop_info->hooks[i] = (zend_function *) hook; + } + } + } } } @@ -3185,6 +3508,25 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string if (ce->ce_flags & ZEND_ACC_ENUM) { zend_verify_enum(ce); } + if (ce->num_hooked_prop_variance_checks) { + zend_property_info *prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop_info) { + if (prop_info->ce == ce && prop_info->hooks && prop_info->hooks[ZEND_PROPERTY_HOOK_SET]) { + switch (zend_verify_property_hook_variance(prop_info, prop_info->hooks[ZEND_PROPERTY_HOOK_SET])) { + case INHERITANCE_SUCCESS: + break; + case INHERITANCE_ERROR: + zend_hooked_property_variance_error(prop_info); + break; + case INHERITANCE_UNRESOLVED: + add_property_hook_obligation(ce, prop_info, prop_info->hooks[ZEND_PROPERTY_HOOK_SET]); + break; + case INHERITANCE_WARNING: + ZEND_UNREACHABLE(); + } + } + } ZEND_HASH_FOREACH_END(); + } /* Normally Stringable is added during compilation. However, if it is imported from a trait, * we need to explicitly add the interface here. */ @@ -3304,7 +3646,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e if (zv) { zend_property_info *child_info = Z_PTR_P(zv); if (ZEND_TYPE_IS_SET(child_info->type)) { - inheritance_status status = property_types_compatible(parent_info, child_info); + inheritance_status status = property_types_compatible(parent_info, child_info, prop_get_variance(parent_info)); ZEND_ASSERT(status != INHERITANCE_WARNING); if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { return status; diff --git a/Zend/zend_inheritance.h b/Zend/zend_inheritance.h index 96ead3bf3e550..477874181e416 100644 --- a/Zend/zend_inheritance.h +++ b/Zend/zend_inheritance.h @@ -21,6 +21,7 @@ #define ZEND_INHERITANCE_H #include "zend.h" +#include "zend_compile.h" BEGIN_EXTERN_C() @@ -42,6 +43,18 @@ void zend_inheritance_check_override(zend_class_entry *ce); ZEND_API extern zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces); ZEND_API extern zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, zend_class_entry *proto, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, HashTable *dependencies); +typedef enum { + INHERITANCE_UNRESOLVED = -1, + INHERITANCE_ERROR = 0, + INHERITANCE_WARNING = 1, + INHERITANCE_SUCCESS = 2, +} zend_inheritance_status; + +ZEND_API zend_inheritance_status zend_verify_property_hook_variance(const zend_property_info *prop_info, const zend_function *func); +ZEND_API ZEND_COLD ZEND_NORETURN void zend_hooked_property_variance_error(const zend_property_info *prop_info); +ZEND_API ZEND_COLD ZEND_NORETURN void zend_hooked_property_variance_error_ex(zend_string *value_param_name, zend_string *class_name, zend_string *prop_name); +ZEND_API void zend_verify_hooked_property(zend_class_entry *ce, zend_property_info *prop_info, zend_string *prop_name); + END_EXTERN_C() #endif diff --git a/Zend/zend_interfaces.c b/Zend/zend_interfaces.c index ad593fe2b95dd..f3fb4151a5830 100644 --- a/Zend/zend_interfaces.c +++ b/Zend/zend_interfaces.c @@ -21,6 +21,7 @@ #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_interfaces_arginfo.h" +#include "zend_property_hooks.h" ZEND_API zend_class_entry *zend_ce_traversable; ZEND_API zend_class_entry *zend_ce_aggregate; @@ -303,7 +304,9 @@ static int zend_implement_aggregate(zend_class_entry *interface, zend_class_entr funcs_ptr->zf_new_iterator = zend_hash_str_find_ptr( &class_type->function_table, "getiterator", sizeof("getiterator") - 1); - if (class_type->get_iterator && class_type->get_iterator != zend_user_it_get_new_iterator) { + if (class_type->get_iterator + && class_type->get_iterator != zend_user_it_get_new_iterator + && class_type->get_iterator != zend_hooked_object_get_iterator) { /* get_iterator was explicitly assigned for an internal class. */ if (!class_type->parent || class_type->parent->get_iterator != class_type->get_iterator) { ZEND_ASSERT(class_type->type == ZEND_INTERNAL_CLASS); @@ -350,7 +353,9 @@ static int zend_implement_iterator(zend_class_entry *interface, zend_class_entry funcs_ptr->zf_next = zend_hash_str_find_ptr( &class_type->function_table, "next", sizeof("next") - 1); - if (class_type->get_iterator && class_type->get_iterator != zend_user_it_get_iterator) { + if (class_type->get_iterator + && class_type->get_iterator != zend_user_it_get_iterator + && class_type->get_iterator != zend_hooked_object_get_iterator) { if (!class_type->parent || class_type->parent->get_iterator != class_type->get_iterator) { /* get_iterator was explicitly assigned for an internal class. */ ZEND_ASSERT(class_type->type == ZEND_INTERNAL_CLASS); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 748c8a8862791..f7822caa88dc3 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -177,6 +177,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_TRAIT_C "'__TRAIT__'" %token T_METHOD_C "'__METHOD__'" %token T_FUNC_C "'__FUNCTION__'" +%token T_PROPERTY_C "'__PROPERTY__'" %token T_NS_C "'__NAMESPACE__'" %token END 0 "end of file" @@ -279,8 +280,10 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list %type enum_declaration_statement enum_backing_type enum_case enum_case_expr %type function_name non_empty_member_modifiers +%type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body +%type optional_parameter_list -%type returns_ref function fn is_reference is_variadic property_modifiers +%type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers %type class_modifiers class_modifier anonymous_class_modifiers anonymous_class_modifiers_optional use_type backup_fn_flags @@ -303,6 +306,7 @@ reserved_non_modifiers: | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM + | T_PROPERTY_C ; semi_reserved: @@ -803,13 +807,13 @@ optional_cpp_modifiers: parameter: optional_cpp_modifiers optional_type_without_static - is_reference is_variadic T_VARIABLE backup_doc_comment + is_reference is_variadic T_VARIABLE backup_doc_comment optional_property_hook_list { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, NULL, - NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); } + NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL, $7); } | optional_cpp_modifiers optional_type_without_static - is_reference is_variadic T_VARIABLE backup_doc_comment '=' expr + is_reference is_variadic T_VARIABLE backup_doc_comment '=' expr optional_property_hook_list { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, $8, - NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); } + NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL, $9); } ; optional_type_without_static: @@ -939,6 +943,9 @@ attributed_class_statement: property_modifiers optional_type_without_static property_list ';' { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, $3, NULL); $$->attr = $1; } + | property_modifiers optional_type_without_static hooked_property + { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, zend_ast_create_list(1, ZEND_AST_PROP_DECL, $3), NULL); + $$->attr = $1; } | class_const_modifiers T_CONST class_const_list ';' { $$ = zend_ast_create(ZEND_AST_CLASS_CONST_GROUP, $3, NULL, NULL); $$->attr = $1; } @@ -1072,9 +1079,60 @@ property_list: property: T_VARIABLE backup_doc_comment - { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, NULL, ($2 ? zend_ast_create_zval_from_str($2) : NULL)); } + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, NULL, ($2 ? zend_ast_create_zval_from_str($2) : NULL), NULL); } | T_VARIABLE '=' expr backup_doc_comment - { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL)); } + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); } +; + +hooked_property: + T_VARIABLE backup_doc_comment '{' property_hook_list '}' + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, NULL, ($2 ? zend_ast_create_zval_from_str($2) : NULL), $4); } + | T_VARIABLE '=' expr backup_doc_comment '{' property_hook_list '}' + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), $6); } +; + +property_hook_list: + %empty { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); } + | property_hook_list property_hook { $$ = zend_ast_list_add($1, $2); } + | property_hook_list attributes property_hook { + $$ = zend_ast_list_add($1, zend_ast_with_attributes($3, $2)); + } +; + +optional_property_hook_list: + %empty { $$ = NULL; } + | '{' property_hook_list '}' { $$ = $2; } +; + +property_hook_modifiers: + %empty { $$ = 0; } + | non_empty_member_modifiers { + $$ = zend_modifier_list_to_flags(ZEND_MODIFIER_TARGET_PROPERTY_HOOK, $1); + if (!$$) { YYERROR; } + } +; + +property_hook: + property_hook_modifiers returns_ref T_STRING + backup_doc_comment { $$ = CG(zend_lineno); } + optional_parameter_list backup_fn_flags property_hook_body backup_fn_flags { + $$ = zend_ast_create_decl( + ZEND_AST_PROPERTY_HOOK, $1 | $2 | $9, $5, $4, zend_ast_get_str($3), + $6, NULL, $8, NULL, NULL); + CG(extra_fn_flags) = $7; + } +; + +property_hook_body: + ';' { $$ = NULL; } + | '{' inner_statement_list '}' { $$ = $2; } + | T_DOUBLE_ARROW expr ';' + { $$ = zend_ast_create(ZEND_AST_PROPERTY_HOOK_SHORT_BODY, $2); } +; + +optional_parameter_list: + %empty { $$ = NULL; } + | '(' parameter_list ')' { $$ = $2; } ; class_const_list: @@ -1395,6 +1453,7 @@ constant: | T_TRAIT_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_TRAIT_C); } | T_METHOD_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_METHOD_C); } | T_FUNC_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_FUNC_C); } + | T_PROPERTY_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_PROPERTY_C); } | T_NS_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_NS_C); } | T_CLASS_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_CLASS_C); } ; @@ -1421,7 +1480,10 @@ variable_class_name: fully_dereferenceable: variable { $$ = $1; } - | '(' expr ')' { $$ = $2; } + | '(' expr ')' { + $$ = $2; + if ($$->kind == ZEND_AST_STATIC_PROP) $$->attr = ZEND_PARENTHESIZED_STATIC_PROP; + } | dereferenceable_scalar { $$ = $1; } | class_constant { $$ = $1; } | new_dereferenceable { $$ = $1; } @@ -1444,8 +1506,6 @@ callable_variable: { $$ = zend_ast_create(ZEND_AST_VAR, $1); } | array_object_dereferenceable '[' optional_expr ']' { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } - | array_object_dereferenceable '{' expr '}' - { $$ = zend_ast_create_ex(ZEND_AST_DIM, ZEND_DIM_ALTERNATIVE_SYNTAX, $1, $3); } | array_object_dereferenceable T_OBJECT_OPERATOR property_name argument_list { $$ = zend_ast_create(ZEND_AST_METHOD_CALL, $1, $3, $4); } | array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list @@ -1482,8 +1542,6 @@ new_variable: { $$ = zend_ast_create(ZEND_AST_VAR, $1); } | new_variable '[' optional_expr ']' { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } - | new_variable '{' expr '}' - { $$ = zend_ast_create_ex(ZEND_AST_DIM, ZEND_DIM_ALTERNATIVE_SYNTAX, $1, $3); } | new_variable T_OBJECT_OPERATOR property_name { $$ = zend_ast_create(ZEND_AST_PROP, $1, $3); } | new_variable T_NULLSAFE_OBJECT_OPERATOR property_name diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index ae7e91629db72..ba89afdb73114 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -2194,6 +2194,10 @@ string: RETURN_TOKEN_WITH_IDENT(T_FUNC_C); } +"__PROPERTY__" { + RETURN_TOKEN_WITH_IDENT(T_PROPERTY_C); +} + "__METHOD__" { RETURN_TOKEN_WITH_IDENT(T_METHOD_C); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 308be8571d84a..31eb28f984ff4 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -30,16 +30,20 @@ #include "zend_closures.h" #include "zend_compile.h" #include "zend_hash.h" +#include "zend_property_hooks.h" +#include "ext/reflection/php_reflection.h" #define DEBUG_OBJECT_HANDLERS 0 #define ZEND_WRONG_PROPERTY_OFFSET 0 +#define ZEND_HOOKED_PROPERTY_OFFSET 1 /* guard flags */ #define IN_GET ZEND_GUARD_PROPERTY_GET #define IN_SET ZEND_GUARD_PROPERTY_SET #define IN_UNSET ZEND_GUARD_PROPERTY_UNSET #define IN_ISSET ZEND_GUARD_PROPERTY_ISSET +#define IN_HOOK ZEND_GUARD_PROPERTY_HOOK /* __X accessors explanation: @@ -395,6 +399,15 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c return ZEND_DYNAMIC_PROPERTY_OFFSET; } + if (property_info->hooks) { + *info_ptr = property_info; + if (cache_slot) { + CACHE_POLYMORPHIC_PTR_EX(cache_slot, ce, (void*)ZEND_HOOKED_PROPERTY_OFFSET); + CACHE_PTR_EX(cache_slot + 2, property_info); + } + return ZEND_HOOKED_PROPERTY_OFFSET; + } + offset = property_info->offset; if (EXPECTED(!ZEND_TYPE_IS_SET(property_info->type))) { property_info = NULL; @@ -604,6 +617,58 @@ ZEND_API uint32_t *zend_get_recursion_guard(zend_object *zobj) return &Z_GUARD_P(zv); } +ZEND_COLD static void zend_typed_property_uninitialized_access(const zend_property_info *prop_info, zend_string *name) +{ + zend_throw_error(NULL, "Typed property %s::$%s must not be accessed before initialization", + ZSTR_VAL(prop_info->ce->name), + ZSTR_VAL(name)); +} + +static ZEND_FUNCTION(zend_parent_hook_get_trampoline); +static ZEND_FUNCTION(zend_parent_hook_set_trampoline); + +static bool zend_is_in_hook(const zend_property_info *prop_info) +{ + zend_execute_data *execute_data = EG(current_execute_data); + if (!execute_data || !EX(func) || !EX(func)->common.prop_info) { + return false; + } + + const zend_property_info *parent_info = EX(func)->common.prop_info; + ZEND_ASSERT(prop_info->prototype && parent_info->prototype); + return prop_info->prototype == parent_info->prototype; +} + +static bool zend_should_call_hook(const zend_property_info *prop_info, const zend_object *obj) +{ + return !zend_is_in_hook(prop_info) + /* execute_data and This are guaranteed to be set if zend_is_in_hook() returns true. */ + || Z_OBJ(EG(current_execute_data)->This) != obj; +} + +static ZEND_COLD void zend_throw_no_prop_backing_value_access(zend_string *class_name, zend_string *prop_name, bool is_read) +{ + zend_throw_error(NULL, "Must not %s virtual property %s::$%s", + is_read ? "read from" : "write to", + ZSTR_VAL(class_name), ZSTR_VAL(prop_name)); +} + +static bool zend_call_get_hook( + const zend_property_info *prop_info, zend_string *prop_name, + zend_function *get, zend_object *zobj, zval *rv) +{ + if (!zend_should_call_hook(prop_info, zobj)) { + if (UNEXPECTED(prop_info->flags & ZEND_ACC_VIRTUAL)) { + zend_throw_no_prop_backing_value_access(zobj->ce->name, prop_name, /* is_read */ true); + } + return false; + } + + zend_call_known_instance_method_with_0_params(get, zobj, rv); + + return true; +} + ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int type, void **cache_slot, zval *rv) /* {{{ */ { zval *retval; @@ -619,6 +684,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int property_offset = zend_get_property_offset(zobj->ce, name, (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot, &prop_info); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { +try_again: retval = OBJ_PROP(zobj, property_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY) @@ -680,6 +746,80 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto exit; } } + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + if (!get) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Property %s::$%s is write-only", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(uninitialized_zval); + } + /* Cache the fact that this hook has trivial read. This only applies to + * BP_VAR_R and BP_VAR_IS fetches. */ + ZEND_SET_PROPERTY_HOOK_SIMPLE_READ(cache_slot); + + retval = OBJ_PROP(zobj, prop_info->offset); + if (UNEXPECTED(Z_TYPE_P(retval) == IS_UNDEF)) { + /* As hooked properties can't be unset, the only way to end up with an undef + * value is via an uninitialized property. */ + ZEND_ASSERT(Z_PROP_FLAG_P(retval) == IS_PROP_UNINIT); + goto uninit_error; + } + + if (UNEXPECTED(type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)) { + if (UNEXPECTED(Z_TYPE_P(retval) != IS_OBJECT)) { + zend_throw_error(NULL, "Indirect modification of %s::$%s is not allowed", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + goto exit; + } + ZVAL_COPY(rv, retval); + retval = rv; + } + goto exit; + } + + zend_class_entry *ce = zobj->ce; + + if (!zend_call_get_hook(prop_info, name, get, zobj, rv)) { + if (EG(exception)) { + return &EG(uninitialized_zval); + } + + /* Reads from backing store can only occur in hooks, and hence will always remain simple. */ + zend_execute_data *execute_data = EG(current_execute_data); + if (cache_slot && EX(opline) && EX(opline)->opcode == ZEND_FETCH_OBJ_R && EX(opline)->op1_type == IS_UNUSED) { + ZEND_SET_PROPERTY_HOOK_SIMPLE_READ(cache_slot); + } + + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + + if (EXPECTED(cache_slot + && zend_execute_ex == execute_ex + && zobj->ce->default_object_handlers->read_property == zend_std_read_property + && !zobj->ce->create_object + && !zend_is_in_hook(prop_info) + && !(prop_info->hooks[ZEND_PROPERTY_HOOK_GET]->common.fn_flags & ZEND_ACC_RETURN_REFERENCE))) { + ZEND_SET_PROPERTY_HOOK_SIMPLE_GET(cache_slot); + } + + if (Z_TYPE_P(rv) != IS_UNDEF) { + retval = rv; + if (!Z_ISREF_P(rv) + && (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) + && UNEXPECTED(Z_TYPE_P(rv) != IS_OBJECT)) { + zend_throw_error(NULL, "Indirect modification of %s::$%s is not allowed", + ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } + } else { + retval = &EG(uninitialized_zval); + } + + goto exit; } else if (UNEXPECTED(EG(exception))) { retval = &EG(uninitialized_zval); goto exit; @@ -745,7 +885,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto exit; } else if (UNEXPECTED(IS_WRONG_PROPERTY_OFFSET(property_offset))) { /* Trigger the correct error */ - zend_get_property_offset(zobj->ce, name, 0, NULL, &prop_info); + zend_wrong_offset(zobj->ce, name); ZEND_ASSERT(EG(exception)); retval = &EG(uninitialized_zval); goto exit; @@ -755,9 +895,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int uninit_error: if (type != BP_VAR_IS) { if (prop_info) { - zend_throw_error(NULL, "Typed property %s::$%s must not be accessed before initialization", - ZSTR_VAL(prop_info->ce->name), - ZSTR_VAL(name)); + zend_typed_property_uninitialized_access(prop_info, name); } else { zend_error(E_WARNING, "Undefined property: %s::$%s", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); } @@ -816,6 +954,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { +try_again: variable_ptr = OBJ_PROP(zobj, property_offset); if (Z_TYPE_P(variable_ptr) != IS_UNDEF) { Z_TRY_ADDREF_P(value); @@ -891,6 +1030,48 @@ found:; goto found; } } + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + zend_function *set = prop_info->hooks[ZEND_PROPERTY_HOOK_SET]; + + if (!set) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Property %s::$%s is read-only", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + variable_ptr = &EG(error_zval); + goto exit; + } + ZEND_SET_PROPERTY_HOOK_SIMPLE_WRITE(cache_slot); + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + + if (!zend_should_call_hook(prop_info, zobj)) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_no_prop_backing_value_access(zobj->ce->name, name, /* is_read */ false); + variable_ptr = &EG(error_zval); + goto exit; + } + + /* Writes to backing store can only occur in hooks, and hence will always remain simple. */ + zend_execute_data *execute_data = EG(current_execute_data); + if (cache_slot && EX(opline) && EX(opline)->opcode == ZEND_ASSIGN_OBJ && EX(opline)->op1_type == IS_UNUSED) { + ZEND_SET_PROPERTY_HOOK_SIMPLE_WRITE(cache_slot); + } + + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + GC_ADDREF(zobj); + zend_call_known_instance_method_with_1_params(set, zobj, NULL, value); + OBJ_RELEASE(zobj); + + variable_ptr = value; + goto exit; } else if (UNEXPECTED(EG(exception))) { variable_ptr = &EG(error_zval); goto exit; @@ -1107,10 +1288,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam UNEXPECTED(prop_info && (Z_PROP_FLAG_P(retval) & IS_PROP_UNINIT))) { if (UNEXPECTED(type == BP_VAR_RW || type == BP_VAR_R)) { if (prop_info) { - zend_throw_error(NULL, - "Typed property %s::$%s must not be accessed before initialization", - ZSTR_VAL(prop_info->ce->name), - ZSTR_VAL(name)); + zend_typed_property_uninitialized_access(prop_info, name); retval = &EG(error_zval); } else { zend_error(E_WARNING, "Undefined property: %s::$%s", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); @@ -1164,7 +1342,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam } retval = zend_hash_add(zobj->properties, name, &EG(uninitialized_zval)); } - } else if (zobj->ce->__get == NULL) { + } else if (!IS_HOOKED_PROPERTY_OFFSET(property_offset) && zobj->ce->__get == NULL) { retval = &EG(error_zval); } @@ -1227,6 +1405,10 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void if (EXPECTED(zend_hash_del(zobj->properties, name) != FAILURE)) { return; } + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + zend_throw_error(NULL, "Cannot unset hooked property %s::$%s", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return; } else if (UNEXPECTED(EG(exception))) { return; } @@ -1368,6 +1550,7 @@ ZEND_API zend_function *zend_get_call_trampoline_func(const zend_class_entry *ce } func->prototype = NULL; + func->prop_info = NULL; func->num_args = 0; func->required_num_args = 0; func->arg_info = (zend_arg_info *) arg_info; @@ -1376,6 +1559,87 @@ ZEND_API zend_function *zend_get_call_trampoline_func(const zend_class_entry *ce } /* }}} */ +static ZEND_FUNCTION(zend_parent_hook_get_trampoline) +{ + zend_object *obj = Z_PTR_P(ZEND_THIS); + zend_string *prop_name = EX(func)->internal_function.reserved[0]; + + if (UNEXPECTED(ZEND_NUM_ARGS() != 0)) { + zend_wrong_parameters_none_error(); + goto clean; + } + + zval rv; + zval *retval = obj->handlers->read_property(obj, prop_name, BP_VAR_R, NULL, &rv); + if (retval == &rv) { + RETVAL_COPY_VALUE(retval); + } else { + RETVAL_COPY(retval); + } + +clean: + zend_string_release(EX(func)->common.function_name); + zend_free_trampoline(EX(func)); + EX(func) = NULL; +} + +static ZEND_FUNCTION(zend_parent_hook_set_trampoline) +{ + zend_object *obj = Z_PTR_P(ZEND_THIS); + zend_string *prop_name = EX(func)->internal_function.reserved[0]; + + zval *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END_EX(goto clean); + + RETVAL_COPY(obj->handlers->write_property(obj, prop_name, value, NULL)); + +clean: + zend_string_release(EX(func)->common.function_name); + zend_free_trampoline(EX(func)); + EX(func) = NULL; +} + +ZEND_API zend_function *zend_get_property_hook_trampoline( + const zend_property_info *prop_info, + zend_property_hook_kind kind, zend_string *prop_name) +{ + static const zend_arg_info arg_info[1] = {{0}}; + zend_function *func; + if (EXPECTED(EG(trampoline).common.function_name == NULL)) { + func = &EG(trampoline); + } else { + func = (zend_function *)(uintptr_t)ecalloc(1, sizeof(zend_internal_function)); + } + func->type = ZEND_INTERNAL_FUNCTION; + func->common.arg_flags[0] = 0; + func->common.arg_flags[1] = 0; + func->common.arg_flags[2] = 0; + func->common.fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE; + func->common.function_name = zend_string_concat3( + "$", 1, ZSTR_VAL(prop_name), ZSTR_LEN(prop_name), + kind == ZEND_PROPERTY_HOOK_GET ? "::get" : "::set", 5); + /* set to 0 to avoid arg_info[] allocation, because all values are passed by value anyway */ + uint32_t args = kind == ZEND_PROPERTY_HOOK_GET ? 0 : 1; + func->common.num_args = args; + func->common.required_num_args = args; + func->common.scope = prop_info->ce; + func->common.prototype = NULL; + func->common.prop_info = prop_info; + func->common.arg_info = (zend_arg_info *) arg_info; + func->internal_function.handler = kind == ZEND_PROPERTY_HOOK_GET + ? ZEND_FN(zend_parent_hook_get_trampoline) + : ZEND_FN(zend_parent_hook_set_trampoline); + func->internal_function.module = NULL; + + func->internal_function.reserved[0] = prop_name; + func->internal_function.reserved[1] = NULL; + + return func; +} + static zend_always_inline zend_function *zend_get_user_call_function(zend_class_entry *ce, zend_string *method_name) /* {{{ */ { return zend_get_call_trampoline_func(ce, method_name, 0); @@ -1824,6 +2088,7 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has property_offset = zend_get_property_offset(zobj->ce, name, 1, cache_slot, &prop_info); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { +try_again: value = OBJ_PROP(zobj, property_offset); if (Z_TYPE_P(value) != IS_UNDEF) { goto found; @@ -1871,6 +2136,44 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has goto exit; } } + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + if (!get) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Property %s::$%s is write-only", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return 0; + } else { + property_offset = prop_info->offset; + goto try_again; + } + } + + if (has_set_exists == ZEND_PROPERTY_EXISTS) { + return 1; + } + + zval rv; + if (!zend_call_get_hook(prop_info, name, get, zobj, &rv)) { + if (EG(exception)) { + return 0; + } + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + + if (has_set_exists == ZEND_PROPERTY_NOT_EMPTY) { + result = zend_is_true(&rv); + } else { + ZEND_ASSERT(has_set_exists == ZEND_PROPERTY_ISSET); + result = Z_TYPE(rv) != IS_NULL + && (Z_TYPE(rv) != IS_REFERENCE || Z_TYPE_P(Z_REFVAL(rv)) != IS_NULL); + } + zval_ptr_dtor(&rv); + return result; } else if (UNEXPECTED(EG(exception))) { result = false; goto exit; @@ -1983,10 +2286,15 @@ ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purp return ht; } ZEND_FALLTHROUGH; + case ZEND_PROP_PURPOSE_JSON: + case ZEND_PROP_PURPOSE_GET_OBJECT_VARS: + case ZEND_PROP_PURPOSE_VAR_EXPORT: + if (obj->ce->num_hooked_props) { + return zend_hooked_object_build_properties(obj); + } + ZEND_FALLTHROUGH; case ZEND_PROP_PURPOSE_ARRAY_CAST: case ZEND_PROP_PURPOSE_SERIALIZE: - case ZEND_PROP_PURPOSE_VAR_EXPORT: - case ZEND_PROP_PURPOSE_JSON: ht = obj->handlers->get_properties(obj); if (ht) { GC_TRY_ADDREF(ht); diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 35498f34617c0..7483f85202a4c 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -23,6 +23,7 @@ #include #include "zend_types.h" +#include "zend_property_hooks.h" struct _zend_property_info; @@ -31,10 +32,47 @@ struct _zend_property_info; #define ZEND_DYNAMIC_PROPERTY_OFFSET ((uintptr_t)(intptr_t)(-1)) -#define IS_VALID_PROPERTY_OFFSET(offset) ((intptr_t)(offset) > 0) +/* The first 4 bits in the property offset are used within the cache slot for + * storing information about the property. This value may be bumped to + * offsetof(zend_object, properties_table) in the future. */ +#define ZEND_FIRST_PROPERTY_OFFSET (1 << 4) +#define IS_VALID_PROPERTY_OFFSET(offset) ((intptr_t)(offset) >= ZEND_FIRST_PROPERTY_OFFSET) #define IS_WRONG_PROPERTY_OFFSET(offset) ((intptr_t)(offset) == 0) +#define IS_HOOKED_PROPERTY_OFFSET(offset) \ + ((intptr_t)(offset) > 0 && (intptr_t)(offset) < 16) #define IS_DYNAMIC_PROPERTY_OFFSET(offset) ((intptr_t)(offset) < 0) +#define ZEND_PROPERTY_HOOK_SIMPLE_READ_BIT 2u +#define ZEND_PROPERTY_HOOK_SIMPLE_WRITE_BIT 4u +#define ZEND_PROPERTY_HOOK_SIMPLE_GET_BIT 8u +#define ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(offset) \ + (((offset) & ZEND_PROPERTY_HOOK_SIMPLE_READ_BIT) != 0) +#define ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(offset) \ + (((offset) & ZEND_PROPERTY_HOOK_SIMPLE_WRITE_BIT) != 0) +#define ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(offset) \ + (((offset) & ZEND_PROPERTY_HOOK_SIMPLE_GET_BIT) != 0) +#define ZEND_SET_PROPERTY_HOOK_SIMPLE_READ(cache_slot) \ + do { \ + void **__cache_slot = (cache_slot); \ + if (__cache_slot) { \ + CACHE_PTR_EX(__cache_slot + 1, (void*)((uintptr_t)CACHED_PTR_EX(__cache_slot + 1) | ZEND_PROPERTY_HOOK_SIMPLE_READ_BIT)); \ + } \ + } while (0) +#define ZEND_SET_PROPERTY_HOOK_SIMPLE_WRITE(cache_slot) \ + do { \ + void **__cache_slot = (cache_slot); \ + if (__cache_slot) { \ + CACHE_PTR_EX(__cache_slot + 1, (void*)((uintptr_t)CACHED_PTR_EX(__cache_slot + 1) | ZEND_PROPERTY_HOOK_SIMPLE_WRITE_BIT)); \ + } \ + } while (0) +#define ZEND_SET_PROPERTY_HOOK_SIMPLE_GET(cache_slot) \ + do { \ + void **__cache_slot = (cache_slot); \ + if (__cache_slot) { \ + CACHE_PTR_EX(__cache_slot + 1, (void*)((uintptr_t)CACHED_PTR_EX(__cache_slot + 1) | ZEND_PROPERTY_HOOK_SIMPLE_GET_BIT)); \ + } \ + } while (0) + #define IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(offset) (offset == ZEND_DYNAMIC_PROPERTY_OFFSET) #define ZEND_DECODE_DYN_PROP_OFFSET(offset) ((uintptr_t)(-(intptr_t)(offset) - 2)) #define ZEND_ENCODE_DYN_PROP_OFFSET(offset) ((uintptr_t)(-((intptr_t)(offset) + 2))) @@ -100,6 +138,8 @@ typedef enum _zend_prop_purpose { ZEND_PROP_PURPOSE_VAR_EXPORT, /* Used for json_encode(). */ ZEND_PROP_PURPOSE_JSON, + /* Used for get_object_vars(). */ + ZEND_PROP_PURPOSE_GET_OBJECT_VARS, /* Dummy member to ensure that "default" is specified. */ _ZEND_PROP_PURPOSE_NON_EXHAUSTIVE_ENUM } zend_prop_purpose; @@ -255,6 +295,12 @@ ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purp * consumers of the get_properties_for API. */ ZEND_API HashTable *zend_get_properties_for(zval *obj, zend_prop_purpose purpose); +typedef struct _zend_property_info zend_property_info; + +ZEND_API zend_function *zend_get_property_hook_trampoline( + const zend_property_info *prop_info, + zend_property_hook_kind kind, zend_string *prop_name); + #define zend_release_properties(ht) do { \ if ((ht) && !(GC_FLAGS(ht) & GC_IMMUTABLE) && !GC_DELREF(ht)) { \ zend_array_destroy(ht); \ diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 8828a03d7b618..0d1d8b6bf528f 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -73,6 +73,7 @@ void init_op_array(zend_op_array *op_array, uint8_t type, int initial_ops_size) op_array->scope = NULL; op_array->prototype = NULL; + op_array->prop_info = NULL; op_array->live_range = NULL; op_array->try_catch_array = NULL; @@ -396,6 +397,13 @@ ZEND_API void destroy_zend_class(zval *zv) zend_hash_release(prop_info->attributes); } zend_type_release(prop_info->type, /* persistent */ 0); + if (prop_info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop_info->hooks[i]) { + destroy_op_array(&prop_info->hooks[i]->op_array); + } + } + } } } ZEND_HASH_FOREACH_END(); zend_hash_destroy(&ce->properties_info); @@ -786,6 +794,7 @@ static void emit_live_range( case ZEND_INIT_USER_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_NEW: level++; break; diff --git a/Zend/zend_property_hooks.c b/Zend/zend_property_hooks.c new file mode 100644 index 0000000000000..d3ae945f1d81e --- /dev/null +++ b/Zend/zend_property_hooks.c @@ -0,0 +1,309 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Authors: Ilija Tovilo | + +----------------------------------------------------------------------+ +*/ + +#include "zend.h" +#include "zend_API.h" +#include "zend_property_hooks.h" + +typedef struct { + zend_object_iterator it; + bool by_ref; + bool declared_props_done; + zval declared_props; + uint32_t dynamic_prop_it; + zval current_key; + zval current_data; +} zend_hooked_object_iterator; + +static zend_result zho_it_valid(zend_object_iterator *iter); + +// FIXME: This should probably be stored on zend_class_entry somewhere (e.g. through num_virtual_props). +static uint32_t zho_num_backed_props(zend_object *zobj) +{ + zend_property_info *prop_info; + int backed_property_count = 0; + ZEND_HASH_MAP_FOREACH_PTR(&zobj->ce->properties_info, prop_info) { + if (!(prop_info->flags & (ZEND_ACC_STATIC|ZEND_ACC_VIRTUAL))) { + backed_property_count++; + } + } ZEND_HASH_FOREACH_END(); + return backed_property_count; +} + +static zend_array *zho_build_properties_ex(zend_object *zobj, bool check_access, bool include_dynamic_props) +{ + zend_class_entry *ce = zobj->ce; + zend_array *properties = zend_new_array(ce->default_properties_count); + zend_hash_real_init_mixed(properties); + + zend_property_info *prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop_info) { + if (prop_info->flags & ZEND_ACC_STATIC) { + continue; + } + if (check_access && zend_check_property_access(zobj, prop_info->name, false) == FAILURE) { + continue; + } + if (prop_info->hooks) { + _zend_hash_append_ptr(properties, prop_info->name, prop_info); + } else { + if (UNEXPECTED(Z_TYPE_P(OBJ_PROP(zobj, prop_info->offset)) == IS_UNDEF)) { + HT_FLAGS(properties) |= HASH_FLAG_HAS_EMPTY_IND; + } + _zend_hash_append_ind(properties, prop_info->name, OBJ_PROP(zobj, prop_info->offset)); + } + } ZEND_HASH_FOREACH_END(); + + if (include_dynamic_props && zobj->properties) { + zend_string *prop_name; + zval *prop_value; + ZEND_HASH_FOREACH_STR_KEY_VAL_FROM(zobj->properties, prop_name, prop_value, zho_num_backed_props(zobj)) { + Z_TRY_ADDREF_P(_zend_hash_append(properties, prop_name, prop_value)); + } ZEND_HASH_FOREACH_END(); + } + + return properties; +} + +ZEND_API zend_array *zend_hooked_object_build_properties(zend_object *zobj) +{ + return zho_build_properties_ex(zobj, false, true); +} + +static bool zho_dynamic_it_init(zend_hooked_object_iterator *hooked_iter) +{ + zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data); + if (!zobj->properties) { + return false; + } + if (hooked_iter->dynamic_prop_it != (uint32_t) -1) { + return true; + } + hooked_iter->dynamic_prop_it = zend_hash_iterator_add(zobj->properties, zho_num_backed_props(zobj)); + return true; +} + +static void zho_it_get_current_key(zend_object_iterator *iter, zval *key); + +static void zho_declared_it_fetch_current(zend_object_iterator *iter) +{ + ZEND_ASSERT(zho_it_valid(iter) == SUCCESS); + + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zval_ptr_dtor(&hooked_iter->current_data); + if (EG(exception)) { + return; + } + ZVAL_UNDEF(&hooked_iter->current_data); + zval_ptr_dtor(&hooked_iter->current_key); + ZVAL_UNDEF(&hooked_iter->current_key); + zend_object *zobj = Z_OBJ_P(&iter->data); + zend_array *properties = Z_ARR(hooked_iter->declared_props); + + zval_ptr_dtor_nogc(&hooked_iter->current_key); + zend_hash_get_current_key_zval(properties, &hooked_iter->current_key); + + zval *property = zend_hash_get_current_data(properties); + if (Z_TYPE_P(property) == IS_PTR) { + zend_property_info *prop_info = Z_PTR_P(property); + zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + if (hooked_iter->by_ref + && (get == NULL + || !(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE))) { + zend_throw_error(NULL, "Cannot create reference to property %s::$%s", + ZSTR_VAL(zobj->ce->name), zend_get_unmangled_property_name(prop_info->name)); + return; + } + zend_read_property_ex(prop_info->ce, zobj, prop_info->name, /* silent */ true, &hooked_iter->current_data); + } else { + ZVAL_DEINDIRECT(property); + if (hooked_iter->by_ref && Z_TYPE_P(property) != IS_REFERENCE) { + ZEND_ASSERT(Z_TYPE_P(property) != IS_UNDEF); + ZVAL_MAKE_REF(property); + } + ZVAL_COPY(&hooked_iter->current_data, property); + } + zend_hash_get_current_key_zval(properties, &hooked_iter->current_key); +} + +static void zho_dynamic_it_fetch_current(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + ZEND_ASSERT(hooked_iter->dynamic_prop_it != (uint32_t) -1); + + zend_array *properties = Z_OBJ(iter->data)->properties; + HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties); + + Bucket *bucket = properties->arData + pos; + if (hooked_iter->by_ref && Z_TYPE(bucket->val) != IS_REFERENCE) { + ZEND_ASSERT(Z_TYPE(bucket->val) != IS_UNDEF); + ZVAL_MAKE_REF(&bucket->val); + } + zval_ptr_dtor(&hooked_iter->current_data); + ZVAL_COPY(&hooked_iter->current_data, &bucket->val); + + zval_ptr_dtor_nogc(&hooked_iter->current_key); + if (bucket->key) { + ZVAL_STR_COPY(&hooked_iter->current_key, bucket->key); + } else { + ZVAL_LONG(&hooked_iter->current_key, bucket->h); + } +} + +static void zho_it_fetch_current(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + + if (!hooked_iter->declared_props_done) { + zho_declared_it_fetch_current(iter); + } else if (zho_dynamic_it_init(hooked_iter)) { + zho_dynamic_it_fetch_current(iter); + } else { + ZEND_UNREACHABLE(); + } +} + +static void zho_it_dtor(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zval_ptr_dtor(&iter->data); + zval_ptr_dtor(&hooked_iter->declared_props); + zval_ptr_dtor_nogc(&hooked_iter->current_key); + zval_ptr_dtor(&hooked_iter->current_data); + zval_ptr_dtor(&hooked_iter->current_key); + if (hooked_iter->dynamic_prop_it != (uint32_t) -1) { + zend_hash_iterator_del(hooked_iter->dynamic_prop_it); + } +} + +static zend_result zho_it_valid(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + if (!hooked_iter->declared_props_done) { + if (zend_hash_has_more_elements(Z_ARR(hooked_iter->declared_props)) == SUCCESS) { + return SUCCESS; + } + } + + if (zho_dynamic_it_init(hooked_iter)) { + zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data); + HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, zobj->properties); + return pos < zobj->properties->nNumUsed ? SUCCESS : FAILURE; + } + + return FAILURE; +} + +static zval *zho_it_get_current_data(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + return &hooked_iter->current_data; +} + +static void zho_it_get_current_key(zend_object_iterator *iter, zval *key) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + ZVAL_COPY(key, &hooked_iter->current_key); +} + +static void zho_it_move_forward(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zend_array *properties = Z_ARR(hooked_iter->declared_props); + if (!hooked_iter->declared_props_done) { + zend_hash_move_forward(properties); + if (zend_hash_has_more_elements(properties) == SUCCESS) { + zho_declared_it_fetch_current(iter); + } else { + hooked_iter->declared_props_done = true; + if (zho_dynamic_it_init(hooked_iter)) { + zho_dynamic_it_fetch_current(iter); + } + } + } else if (zho_dynamic_it_init(hooked_iter)) { + zend_array *properties = Z_OBJ(iter->data)->properties; + HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties); + Bucket *bucket = properties->arData + pos; + while (true) { + pos++; + bucket++; + if (pos >= properties->nNumUsed) { + break; + } + if (Z_TYPE_INFO_P(&bucket->val) != IS_UNDEF) { + zho_dynamic_it_fetch_current(iter); + break; + } + } + EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = pos; + } +} + +static void zho_it_rewind(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + hooked_iter->declared_props_done = false; + zend_array *properties = Z_ARR(hooked_iter->declared_props); + zend_hash_internal_pointer_reset(properties); + if (hooked_iter->dynamic_prop_it != (uint32_t) -1) { + EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = zho_num_backed_props(Z_OBJ(iter->data)); + } + if (zho_it_valid(iter) == SUCCESS) { + zho_it_fetch_current(iter); + } +} + +static HashTable *zho_it_get_gc(zend_object_iterator *iter, zval **table, int *n) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + zend_get_gc_buffer_add_zval(gc_buffer, &iter->data); + zend_get_gc_buffer_add_zval(gc_buffer, &hooked_iter->declared_props); + zend_get_gc_buffer_add_zval(gc_buffer, &hooked_iter->current_data); + zend_get_gc_buffer_use(gc_buffer, table, n); + return NULL; +} + +static const zend_object_iterator_funcs zend_hooked_object_it_funcs = { + zho_it_dtor, + zho_it_valid, + zho_it_get_current_data, + zho_it_get_current_key, + zho_it_move_forward, + zho_it_rewind, + NULL, + zho_it_get_gc, +}; + +ZEND_API zend_object_iterator *zend_hooked_object_get_iterator(zend_class_entry *ce, zval *object, int by_ref) +{ + zend_hooked_object_iterator *iterator = emalloc(sizeof(zend_hooked_object_iterator)); + zend_iterator_init(&iterator->it); + + ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object)); + iterator->it.funcs = &zend_hooked_object_it_funcs; + iterator->by_ref = by_ref; + iterator->declared_props_done = false; + zend_array *properties = zho_build_properties_ex(Z_OBJ_P(object), true, false); + ZVAL_ARR(&iterator->declared_props, properties); + iterator->dynamic_prop_it = (uint32_t) -1; + ZVAL_UNDEF(&iterator->current_key); + ZVAL_UNDEF(&iterator->current_data); + + return &iterator->it; +} diff --git a/Zend/zend_property_hooks.h b/Zend/zend_property_hooks.h new file mode 100644 index 0000000000000..3793fa1a683cb --- /dev/null +++ b/Zend/zend_property_hooks.h @@ -0,0 +1,42 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Authors: Ilija Tovilo | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_PROPERTY_HOOKS_H +#define ZEND_PROPERTY_HOOKS_H + +#include "zend_portability.h" + +BEGIN_EXTERN_C() + +typedef struct _zend_array zend_array; +typedef struct _zend_class_entry zend_class_entry; +typedef struct _zend_object zend_object; +typedef struct _zend_object_iterator zend_object_iterator; +typedef struct _zval_struct zval; + +typedef enum { + ZEND_PROPERTY_HOOK_GET = 0, + ZEND_PROPERTY_HOOK_SET = 1, +} zend_property_hook_kind; + +ZEND_API zend_object_iterator *zend_hooked_object_get_iterator(zend_class_entry *ce, zval *object, int by_ref); +ZEND_API zend_array *zend_hooked_object_build_properties(zend_object *zobj); + +END_EXTERN_C() + +#endif /* ZEND_PROPERTY_HOOKS_H */ diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 02e1fceffae71..b62875a6ec1ce 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -635,6 +635,8 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED, "Deprecated") \ _(ZEND_STR_SINCE, "since") \ + _(ZEND_STR_GET, "get") \ + _(ZEND_STR_SET, "set") \ typedef enum _zend_known_string_id { diff --git a/Zend/zend_types.h b/Zend/zend_types.h index c4a07f58874ab..d1db25a8f2bda 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -633,10 +633,11 @@ struct _zend_ast_ref { #define ZEND_GUARD_PROPERTY_SET (1<<1) #define ZEND_GUARD_PROPERTY_UNSET (1<<2) #define ZEND_GUARD_PROPERTY_ISSET (1<<3) -#define ZEND_GUARD_PROPERTY_MASK 15 -#define ZEND_GUARD_RECURSION_DEBUG (1<<4) -#define ZEND_GUARD_RECURSION_EXPORT (1<<5) -#define ZEND_GUARD_RECURSION_JSON (1<<6) +#define ZEND_GUARD_PROPERTY_HOOK (1<<4) +#define ZEND_GUARD_PROPERTY_MASK 31 +#define ZEND_GUARD_RECURSION_DEBUG (1<<5) +#define ZEND_GUARD_RECURSION_EXPORT (1<<6) +#define ZEND_GUARD_RECURSION_JSON (1<<7) #define ZEND_GUARD_RECURSION_TYPE(t) ZEND_GUARD_RECURSION_ ## t diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b710fa874af15..06fd3d576cf87 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2075,6 +2075,7 @@ ZEND_VM_HOT_OBJ_HANDLER(82, ZEND_FETCH_OBJ_R, CONST|TMPVAR|UNUSED|THIS|CV, CONST uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +ZEND_VM_C_LABEL(fetch_obj_r_simple): retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -2085,7 +2086,47 @@ ZEND_VM_C_LABEL(fetch_obj_r_fast_copy): ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + ZEND_VM_C_GOTO(fetch_obj_r_simple); + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (OP1_TYPE & IS_CV) { + GC_ADDREF(zobj); + } + if (OP1_TYPE & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + ZEND_OBSERVER_SAVE_OPLINE(); + ZEND_OBSERVER_FCALL_BEGIN(execute_data); + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -2240,6 +2281,7 @@ ZEND_VM_COLD_CONST_HANDLER(91, ZEND_FETCH_OBJ_IS, CONST|TMPVAR|UNUSED|THIS|CV, C uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +ZEND_VM_C_LABEL(fetch_obj_is_simple): retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -2250,7 +2292,15 @@ ZEND_VM_C_LABEL(fetch_obj_is_fast_copy): ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + ZEND_VM_C_GOTO(fetch_obj_is_simple); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -2429,7 +2479,7 @@ ZEND_VM_C_LABEL(fast_assign_obj): ZEND_VM_C_GOTO(exit_assign_obj); } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -2478,6 +2528,19 @@ ZEND_VM_C_LABEL(fast_assign_obj): } ZEND_VM_C_GOTO(exit_assign_obj); } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + ZEND_VM_C_GOTO(free_and_exit_assign_obj); + } else { + ZEND_VM_C_GOTO(fast_assign_obj); + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); @@ -9732,6 +9795,62 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER)) ZEND_VM_NEXT_OPCODE_EX(1, 2); } +ZEND_VM_HANDLER(209, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL, CONST, UNUSED|NUM, NUM) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_class_entry *ce = EX(func)->common.scope; + ZEND_ASSERT(ce); + + zend_class_entry *parent_ce = ce->parent; + if (!parent_ce) { + zend_throw_error(NULL, "Cannot use \"parent\" when current class scope has no parent"); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + zend_string *property_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + zend_property_hook_kind hook_kind = opline->op2.num; + + zend_property_info *prop_info = zend_hash_find_ptr(&parent_ce->properties_info, property_name); + if (!prop_info) { + zend_throw_error(NULL, "Undefined property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + if (prop_info->flags & ZEND_ACC_PRIVATE) { + zend_throw_error(NULL, "Cannot access private property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + zend_function **hooks = prop_info->hooks; + zend_function *hook = hooks ? hooks[hook_kind] : NULL; + + zend_execute_data *call; + if (hook) { + call = zend_vm_stack_push_call_frame( + ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS, + hook, opline->extended_value, Z_OBJ_P(ZEND_THIS)); + if (EXPECTED(hook->type == ZEND_USER_FUNCTION)) { + if (UNEXPECTED(!RUN_TIME_CACHE(&hook->op_array))) { + init_func_run_time_cache(&hook->op_array); + } + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + } + } else { + zend_function *fbc = zend_get_property_hook_trampoline(prop_info, hook_kind, property_name); + call = zend_vm_stack_push_call_frame( + ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS, + fbc, opline->extended_value, Z_OBJ_P(ZEND_THIS)); + } + + call->prev_execute_data = EX(call); + EX(call) = call; + ZEND_VM_NEXT_OPCODE(); +} + ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_JMP, (OP_JMP_ADDR(op, op->op1) > op), ZEND_JMP_FORWARD, JMP_ADDR, ANY) { USE_OPLINE diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 6cb6ff41ec8d9..6e6c7c9aa6657 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -6714,6 +6714,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -6724,7 +6725,46 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_CONST & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_CONST & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -6841,6 +6881,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -6851,7 +6892,15 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -9234,6 +9283,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -9244,7 +9294,46 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_CONST & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_CONST & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -9361,6 +9450,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -9371,7 +9461,15 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -11394,6 +11492,62 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSE } /* Contrary to what its name indicates, ZEND_COPY_TMP may receive and define references. */ +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_class_entry *ce = EX(func)->common.scope; + ZEND_ASSERT(ce); + + zend_class_entry *parent_ce = ce->parent; + if (!parent_ce) { + zend_throw_error(NULL, "Cannot use \"parent\" when current class scope has no parent"); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + zend_string *property_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + zend_property_hook_kind hook_kind = opline->op2.num; + + zend_property_info *prop_info = zend_hash_find_ptr(&parent_ce->properties_info, property_name); + if (!prop_info) { + zend_throw_error(NULL, "Undefined property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + if (prop_info->flags & ZEND_ACC_PRIVATE) { + zend_throw_error(NULL, "Cannot access private property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + zend_function **hooks = prop_info->hooks; + zend_function *hook = hooks ? hooks[hook_kind] : NULL; + + zend_execute_data *call; + if (hook) { + call = zend_vm_stack_push_call_frame( + ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS, + hook, opline->extended_value, Z_OBJ_P(ZEND_THIS)); + if (EXPECTED(hook->type == ZEND_USER_FUNCTION)) { + if (UNEXPECTED(!RUN_TIME_CACHE(&hook->op_array))) { + init_func_run_time_cache(&hook->op_array); + } + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + } + } else { + zend_function *fbc = zend_get_property_hook_trampoline(prop_info, hook_kind, property_name); + call = zend_vm_stack_push_call_frame( + ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS, + fbc, opline->extended_value, Z_OBJ_P(ZEND_THIS)); + } + + call->prev_execute_data = EX(call); + EX(call) = call; + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DIV_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -11620,6 +11774,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -11630,7 +11785,46 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_CONST & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_CONST & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -11747,6 +11941,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -11757,7 +11952,15 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -16002,6 +16205,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CONST_ uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -16012,7 +16216,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CONST_ ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if ((IS_TMP_VAR|IS_VAR) & IS_CV) { + GC_ADDREF(zobj); + } + if ((IS_TMP_VAR|IS_VAR) & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -16129,6 +16372,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CONST uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -16139,7 +16383,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CONST ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -17447,6 +17699,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_TMPVAR uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -17457,7 +17710,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_TMPVAR ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if ((IS_TMP_VAR|IS_VAR) & IS_CV) { + GC_ADDREF(zobj); + } + if ((IS_TMP_VAR|IS_VAR) & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -17574,6 +17866,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_TMPVA uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -17584,7 +17877,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_TMPVA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -18806,6 +19107,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CV_HAN uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -18816,7 +19118,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CV_HAN ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if ((IS_TMP_VAR|IS_VAR) & IS_CV) { + GC_ADDREF(zobj); + } + if ((IS_TMP_VAR|IS_VAR) & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -18933,6 +19274,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CV_HA uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -18943,7 +19285,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CV_HA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -23706,7 +24056,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -23755,6 +24105,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23840,7 +24203,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -23889,6 +24252,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23974,7 +24350,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -24023,6 +24399,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -24108,7 +24497,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -24157,6 +24546,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -26632,7 +27034,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -26681,6 +27083,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -26766,7 +27181,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -26815,6 +27230,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -26900,7 +27328,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -26949,6 +27377,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -27034,7 +27475,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -27083,6 +27524,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -30924,7 +31378,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -30973,6 +31427,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -31058,7 +31525,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -31107,6 +31574,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -31192,7 +31672,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -31241,6 +31721,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -31326,7 +31819,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -31375,6 +31868,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -33233,6 +33739,7 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -33243,7 +33750,46 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_UNUSED & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_UNUSED & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -33403,6 +33949,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CONST uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -33413,7 +33960,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CONST ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -33557,7 +34112,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -33606,6 +34161,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33691,7 +34259,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -33740,6 +34308,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33825,7 +34406,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -33874,6 +34455,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33959,7 +34553,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -34008,6 +34602,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -35292,6 +35899,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_TMPVAR uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -35302,7 +35910,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_TMPVAR ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_UNUSED & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_UNUSED & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -35457,6 +36104,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_TMPVA uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -35467,7 +36115,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_TMPVA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -35611,7 +36267,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -35660,6 +36316,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -35745,7 +36414,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -35794,6 +36463,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -35879,7 +36561,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -35928,6 +36610,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -36013,7 +36708,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -36062,6 +36757,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -37828,6 +38536,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_CV_HAN uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -37838,7 +38547,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_CV_HAN ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_UNUSED & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_UNUSED & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -37993,6 +38741,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CV_HA uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -38003,7 +38752,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CV_HA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -38147,7 +38904,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -38196,6 +38953,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -38281,7 +39051,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -38330,6 +39100,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -38415,7 +39198,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -38464,6 +39247,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -38549,7 +39345,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -38598,6 +39394,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -42098,6 +42907,7 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -42108,7 +42918,46 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_CV & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_CV & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -42268,6 +43117,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CONST_HAN uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -42278,7 +43128,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CONST_HAN ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -42422,7 +43280,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -42471,6 +43329,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -42556,7 +43427,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -42605,6 +43476,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -42690,7 +43574,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -42739,6 +43623,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -42824,7 +43721,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -42873,6 +43770,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -45942,6 +46852,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_TMPVAR_HAN uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -45952,7 +46863,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_TMPVAR_HAN ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_CV & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_CV & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -46107,6 +47057,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_TMPVAR_HA uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -46117,7 +47068,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_TMPVAR_HA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -46261,7 +47220,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -46310,6 +47269,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -46395,7 +47367,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -46444,6 +47416,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -46529,7 +47514,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -46578,6 +47563,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -46663,7 +47661,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -46712,6 +47710,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -51332,6 +52343,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_CV_HANDLER uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_r_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) { if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -51342,7 +52354,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_CV_HANDLER ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) { + zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array)); + + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS; + if (IS_CV & IS_CV) { + GC_ADDREF(zobj); + } + if (IS_CV & (IS_CV|IS_VAR|IS_TMP_VAR)) { + call_info |= ZEND_CALL_RELEASE_THIS; + } + zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj); + call->prev_execute_data = execute_data; + call->call = NULL; + call->return_value = EX_VAR(opline->result.var); + call->run_time_cache = RUN_TIME_CACHE(&hook->op_array); + + execute_data = call; + EG(current_execute_data) = execute_data; + zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC); + +#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) + opline = hook->op_array.opcodes; +#else + EX(opline) = hook->op_array.opcodes; +#endif + LOAD_OPLINE_EX(); + + + ZEND_VM_ENTER_EX(); + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -51497,6 +52548,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CV_HANDLE uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) { +fetch_obj_is_simple: retval = OBJ_PROP(zobj, prop_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { @@ -51507,7 +52559,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CV_HANDLE ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_is_simple; + } + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -51651,7 +52711,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -51700,6 +52760,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -51785,7 +52858,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -51834,6 +52907,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -51919,7 +53005,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -51968,6 +53054,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -52053,7 +53152,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ goto exit_assign_obj; } } - } else { + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { @@ -52102,6 +53201,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } + /* Fall through to write_property for hooks. */ } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -56397,6 +57509,7 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_FRAMELESS_ICALL_3_SPEC_LABEL, (void*)&&ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVER_LABEL, (void*)&&ZEND_JMP_FRAMELESS_SPEC_CONST_LABEL, + (void*)&&ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_LABEL, (void*)&&ZEND_INIT_FCALL_OFFSET_SPEC_CONST_LABEL, (void*)&&ZEND_RECV_NOTYPE_SPEC_LABEL, (void*)&&ZEND_NULL_LABEL, @@ -58763,6 +59876,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED) HYBRID_BREAK(); + HYBRID_CASE(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED): + VM_TRACE(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) + ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) + HYBRID_BREAK(); HYBRID_CASE(ZEND_DIV_SPEC_CONST_CV): VM_TRACE(ZEND_DIV_SPEC_CONST_CV) ZEND_DIV_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -65584,6 +66702,7 @@ void zend_vm_init(void) ZEND_FRAMELESS_ICALL_3_SPEC_HANDLER, ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVER_HANDLER, ZEND_JMP_FRAMELESS_SPEC_CONST_HANDLER, + ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER, ZEND_INIT_FCALL_OFFSET_SPEC_CONST_HANDLER, ZEND_RECV_NOTYPE_SPEC_HANDLER, ZEND_NULL_HANDLER, @@ -66541,7 +67660,7 @@ void zend_vm_init(void) 1255, 1256 | SPEC_RULE_OP1, 1261 | SPEC_RULE_OP1, - 3486, + 3487, 1266 | SPEC_RULE_OP1, 1271 | SPEC_RULE_OP1, 1276 | SPEC_RULE_OP2, @@ -66705,53 +67824,53 @@ void zend_vm_init(void) 2572 | SPEC_RULE_OBSERVER, 2574 | SPEC_RULE_OBSERVER, 2576, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, - 3486, + 2577, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, + 3487, }; #if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) zend_opcode_handler_funcs = labels; @@ -66924,7 +68043,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2585 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2586 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -66932,7 +68051,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2610 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2611 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -66940,7 +68059,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2635 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2636 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -66951,17 +68070,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2660 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2661 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2685 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2686 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2710 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2711 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_MUL: @@ -66972,17 +68091,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2735 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2736 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2760 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2761 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2785 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2786 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_IDENTICAL: @@ -66993,14 +68112,14 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2810 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2811 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2885 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2886 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3110 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3111 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_IDENTICAL: @@ -67011,14 +68130,14 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2960 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2961 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3035 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3036 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3115 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3116 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_EQUAL: @@ -67029,12 +68148,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2810 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2811 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2885 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2886 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_EQUAL: @@ -67045,12 +68164,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2960 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2961 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3035 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3036 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_SMALLER: @@ -67058,12 +68177,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3120 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3121 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3195 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3196 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_IS_SMALLER_OR_EQUAL: @@ -67071,79 +68190,79 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3270 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3271 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3345 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3346 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_QM_ASSIGN: if (op1_info == MAY_BE_LONG) { - spec = 3432 | SPEC_RULE_OP1; + spec = 3433 | SPEC_RULE_OP1; } else if (op1_info == MAY_BE_DOUBLE) { - spec = 3437 | SPEC_RULE_OP1; + spec = 3438 | SPEC_RULE_OP1; } else if ((op->op1_type == IS_CONST) ? !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)) : (!(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))))) { - spec = 3442 | SPEC_RULE_OP1; + spec = 3443 | SPEC_RULE_OP1; } break; case ZEND_PRE_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3420 | SPEC_RULE_RETVAL; + spec = 3421 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3422 | SPEC_RULE_RETVAL; + spec = 3423 | SPEC_RULE_RETVAL; } break; case ZEND_PRE_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3424 | SPEC_RULE_RETVAL; + spec = 3425 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3426 | SPEC_RULE_RETVAL; + spec = 3427 | SPEC_RULE_RETVAL; } break; case ZEND_POST_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3428; - } else if (op1_info == MAY_BE_LONG) { spec = 3429; + } else if (op1_info == MAY_BE_LONG) { + spec = 3430; } break; case ZEND_POST_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3430; - } else if (op1_info == MAY_BE_LONG) { spec = 3431; + } else if (op1_info == MAY_BE_LONG) { + spec = 3432; } break; case ZEND_JMP: if (OP_JMP_ADDR(op, op->op1) > op) { - spec = 2584; + spec = 2585; } break; case ZEND_INIT_FCALL: if (Z_EXTRA_P(RT_CONSTANT(op, op->op2)) != 0) { - spec = 2577; + spec = 2578; } break; case ZEND_RECV: if (op->op2.num == MAY_BE_ANY) { - spec = 2578; + spec = 2579; } break; case ZEND_SEND_VAL: if (op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3482; + spec = 3483; } break; case ZEND_SEND_VAR_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3477 | SPEC_RULE_OP1; + spec = 3478 | SPEC_RULE_OP1; } break; case ZEND_FE_FETCH_R: if (op->op2_type == IS_CV && (op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 3484 | SPEC_RULE_RETVAL; + spec = 3485 | SPEC_RULE_RETVAL; } break; case ZEND_FETCH_DIM_R: @@ -67151,22 +68270,22 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3447 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 3448 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_SEND_VAL_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3483; + spec = 3484; } break; case ZEND_SEND_VAR: if (op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3472 | SPEC_RULE_OP1; + spec = 3473 | SPEC_RULE_OP1; } break; case ZEND_COUNT: if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 2579 | SPEC_RULE_OP1; + spec = 2580 | SPEC_RULE_OP1; } break; case ZEND_BW_OR: diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index 1e897f215b11d..99abc0ffb12de 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -1372,502 +1372,503 @@ _(2574, ZEND_FRAMELESS_ICALL_3_SPEC) \ _(2575, ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVER) \ _(2576, ZEND_JMP_FRAMELESS_SPEC_CONST) \ - _(2577, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ - _(2578, ZEND_RECV_NOTYPE_SPEC) \ - _(2580, ZEND_COUNT_ARRAY_SPEC_TMPVAR_UNUSED) \ + _(2577, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) \ + _(2578, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ + _(2579, ZEND_RECV_NOTYPE_SPEC) \ _(2581, ZEND_COUNT_ARRAY_SPEC_TMPVAR_UNUSED) \ - _(2583, ZEND_COUNT_ARRAY_SPEC_CV_UNUSED) \ - _(2584, ZEND_JMP_FORWARD_SPEC) \ - _(2590, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2591, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2582, ZEND_COUNT_ARRAY_SPEC_TMPVAR_UNUSED) \ + _(2584, ZEND_COUNT_ARRAY_SPEC_CV_UNUSED) \ + _(2585, ZEND_JMP_FORWARD_SPEC) \ + _(2591, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2592, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2594, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2595, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2596, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2593, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2595, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2596, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2597, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2599, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2605, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2606, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2598, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2600, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2606, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2607, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2609, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2615, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2616, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2608, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2610, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2616, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2617, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2619, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2620, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2621, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2618, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2620, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2621, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2622, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2624, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2630, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2631, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2623, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2625, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2631, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2632, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2634, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2640, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2641, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2633, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2635, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2641, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2642, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2644, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2645, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2646, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2643, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2645, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2646, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2647, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2649, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2655, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2656, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2648, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2650, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2656, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2657, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2659, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2661, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2658, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2660, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2662, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2664, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2665, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2666, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2663, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2665, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2666, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2667, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2669, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2670, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2671, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2668, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2670, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2671, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2672, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2674, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2680, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2681, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2673, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2675, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2681, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2682, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2684, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2686, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2683, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2685, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2687, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2689, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2690, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2691, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2688, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2690, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2691, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2692, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2694, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2695, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2696, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2693, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2695, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2696, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2697, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2699, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2705, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2706, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2698, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2700, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2706, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2707, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2709, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2711, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2708, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2710, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2712, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2714, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2715, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2716, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2713, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2715, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2716, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2717, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2719, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2720, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2721, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2718, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2720, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2721, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2722, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2724, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2730, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2731, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2723, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2725, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2731, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2732, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2734, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2740, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2741, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2733, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2735, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2741, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2742, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2744, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2745, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2746, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2743, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2745, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2746, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2747, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2749, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2755, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2756, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2748, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2750, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2756, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2757, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2759, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2765, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2766, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2758, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2760, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2766, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2767, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2769, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2770, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2771, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2768, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2770, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2771, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2772, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2774, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2780, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2781, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2773, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2775, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2781, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2782, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2784, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2790, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2791, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2783, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2785, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2791, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2792, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2794, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2795, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2796, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2793, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2795, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2796, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2797, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2799, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2805, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2806, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2798, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2800, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2806, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2807, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2809, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2825, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2826, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2827, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2828, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2831, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2832, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2833, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2837, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2838, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2839, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2840, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2841, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2842, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2843, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2844, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2845, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2846, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2847, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2848, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2852, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2853, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2854, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2870, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2871, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2872, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2873, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2874, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2875, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2876, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2877, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2878, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2882, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2883, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2884, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2900, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2901, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2902, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2903, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2906, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2907, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2908, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2912, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2913, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2914, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2915, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2916, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2917, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2918, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2919, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2920, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2921, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2922, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2923, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2927, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2928, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2929, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2945, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2946, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2947, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2948, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2949, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2950, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2951, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2952, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2953, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2957, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2958, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2959, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2975, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2976, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2977, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2978, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2981, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2982, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2983, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2987, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2988, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2989, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2990, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2991, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2992, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2993, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2994, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2995, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2996, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2997, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2998, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3002, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3003, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3004, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3020, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3021, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3022, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3023, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3024, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3025, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3026, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3027, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3028, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3032, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3033, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3034, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3050, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3051, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3052, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3053, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3056, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3057, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3058, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3062, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3063, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3064, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3065, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3066, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3067, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3068, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3069, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3070, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3071, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3072, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3073, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3077, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3078, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3079, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3095, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3096, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3097, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3098, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3099, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3100, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3101, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3102, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3103, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3107, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3108, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3109, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3110, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3114, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3115, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3119, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3123, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3124, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3125, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3126, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3127, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3128, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3132, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3133, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3134, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3135, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3140, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3141, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3142, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3147, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3148, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3149, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3151, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3152, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3153, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3154, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3155, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3156, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3157, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3158, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3162, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3163, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3164, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3180, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3181, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3182, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3183, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3184, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3185, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3186, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3187, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3188, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3192, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3193, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3194, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3198, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3200, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3201, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3202, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3203, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3207, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3208, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3209, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3222, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3223, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3224, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3227, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3228, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3229, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3230, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3231, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3232, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3233, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3237, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3238, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3239, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3255, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3256, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3257, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3258, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3259, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3260, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3261, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3262, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3263, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3267, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3268, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3269, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3273, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3275, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3276, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3277, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3278, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3282, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3283, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3284, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3297, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3298, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3299, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3302, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3303, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3304, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3305, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3306, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3307, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3308, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3312, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3313, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3314, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3330, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3331, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3332, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3333, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3334, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3335, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3336, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3337, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3338, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3342, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3343, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3344, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3348, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3350, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3351, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3352, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3353, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3357, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3358, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3359, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3372, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3373, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3374, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3377, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3378, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3379, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3380, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3381, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3382, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3383, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3387, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3388, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3389, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3405, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3406, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3407, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3408, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3409, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3410, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3411, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3412, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3413, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3417, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3418, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3419, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3420, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3421, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3422, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3423, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ - _(3424, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3425, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3426, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3427, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ - _(3428, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3429, ZEND_POST_INC_LONG_SPEC_CV) \ - _(3430, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3431, ZEND_POST_DEC_LONG_SPEC_CV) \ - _(3432, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ - _(3433, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(2808, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2810, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2826, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2827, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2828, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2831, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2832, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2833, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2834, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2838, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2839, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2840, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2841, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2842, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2843, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2844, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2845, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2846, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2847, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2848, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2849, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2853, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2854, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2855, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2871, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2872, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2873, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2874, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2875, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2876, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2877, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2878, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2879, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2883, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2884, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2885, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2901, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2902, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2903, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2906, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2907, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2908, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2909, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2913, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2914, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2915, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2916, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2917, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2918, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2919, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2920, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2921, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2922, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2923, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2924, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2928, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2929, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2930, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2946, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2947, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2948, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2949, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2950, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2951, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2952, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2953, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2954, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2958, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2959, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2960, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2976, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2977, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2978, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2981, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2982, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2983, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2984, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2988, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2989, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2990, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2991, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2992, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2993, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2994, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2995, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2996, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2997, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2998, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2999, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3003, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3004, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3005, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3021, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3022, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3023, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3024, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3025, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3026, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3027, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3028, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3029, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3033, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3034, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3035, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3051, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3052, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3053, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3056, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3057, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3058, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3059, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3063, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3064, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3065, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3066, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3067, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3068, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3069, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3070, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3071, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3072, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3073, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3074, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3078, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3079, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3080, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3096, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3097, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3098, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3099, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3100, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3101, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3102, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3103, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3104, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3108, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3109, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3110, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3111, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3115, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3116, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3120, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3124, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3125, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3126, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3127, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3128, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3129, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3133, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3134, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3135, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3140, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3141, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3142, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3144, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3148, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3149, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3151, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3152, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3153, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3154, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3155, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3156, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3157, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3158, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3159, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3163, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3164, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3165, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3181, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3182, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3183, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3184, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3185, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3186, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3187, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3188, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3189, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3193, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3194, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3195, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3200, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3201, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3202, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3203, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3204, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3208, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3209, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3223, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3224, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3227, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3228, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3229, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3230, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3231, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3232, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3233, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3234, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3238, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3239, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3240, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3256, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3257, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3258, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3259, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3260, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3261, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3262, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3263, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3264, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3268, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3269, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3270, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3275, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3276, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3277, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3278, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3279, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3283, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3284, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3298, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3299, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3302, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3303, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3304, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3305, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3306, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3307, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3308, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3309, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3313, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3314, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3315, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3331, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3332, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3333, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3334, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3335, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3336, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3337, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3338, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3339, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3343, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3344, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3345, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3350, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3351, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3352, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3353, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3354, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3358, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3359, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3373, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3374, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3377, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3378, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3379, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3380, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3381, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3382, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3383, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3384, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3388, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3389, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3390, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3406, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3407, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3408, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3409, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3410, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3411, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3412, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3413, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3414, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3418, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3419, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3420, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3421, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3422, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3423, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3424, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ + _(3425, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3426, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3427, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3428, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ + _(3429, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3430, ZEND_POST_INC_LONG_SPEC_CV) \ + _(3431, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3432, ZEND_POST_DEC_LONG_SPEC_CV) \ + _(3433, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ _(3434, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3436, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3437, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ - _(3438, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3435, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3437, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3438, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ _(3439, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3441, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3442, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ - _(3443, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3440, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3442, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3443, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ _(3444, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3446, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3448, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3445, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3447, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ _(3449, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3451, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3452, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3453, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3450, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3452, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3453, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ _(3454, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3456, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3458, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3458, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ _(3459, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3461, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3467, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ - _(3468, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3460, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3462, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3468, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ _(3469, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3471, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3474, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ - _(3476, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ - _(3479, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ - _(3481, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ - _(3482, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ - _(3483, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ - _(3484, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ - _(3485, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ - _(3485+1, ZEND_NULL) + _(3470, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3472, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3475, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ + _(3477, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ + _(3480, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ + _(3482, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ + _(3483, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ + _(3484, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ + _(3485, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ + _(3486, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ + _(3486+1, ZEND_NULL) diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 218a55a8f4e8b..d29fc069255db 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -22,7 +22,7 @@ #include #include -static const char *zend_vm_opcodes_names[209] = { +static const char *zend_vm_opcodes_names[210] = { "ZEND_NOP", "ZEND_ADD", "ZEND_SUB", @@ -232,9 +232,10 @@ static const char *zend_vm_opcodes_names[209] = { "ZEND_FRAMELESS_ICALL_2", "ZEND_FRAMELESS_ICALL_3", "ZEND_JMP_FRAMELESS", + "ZEND_INIT_PARENT_PROPERTY_HOOK_CALL", }; -static uint32_t zend_vm_opcodes_flags[209] = { +static uint32_t zend_vm_opcodes_flags[210] = { 0x00000000, 0x00000b0b, 0x00000b0b, @@ -444,6 +445,7 @@ static uint32_t zend_vm_opcodes_flags[209] = { 0x00000000, 0x00000000, 0x01042003, + 0x01001103, }; ZEND_API const char* ZEND_FASTCALL zend_get_opcode_name(uint8_t opcode) { diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index a273a9b7e2de0..2f59a5c874462 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -83,215 +83,216 @@ ZEND_API uint8_t zend_get_opcode_id(const char *name, size_t length); END_EXTERN_C() -#define ZEND_NOP 0 -#define ZEND_ADD 1 -#define ZEND_SUB 2 -#define ZEND_MUL 3 -#define ZEND_DIV 4 -#define ZEND_MOD 5 -#define ZEND_SL 6 -#define ZEND_SR 7 -#define ZEND_CONCAT 8 -#define ZEND_BW_OR 9 -#define ZEND_BW_AND 10 -#define ZEND_BW_XOR 11 -#define ZEND_POW 12 -#define ZEND_BW_NOT 13 -#define ZEND_BOOL_NOT 14 -#define ZEND_BOOL_XOR 15 -#define ZEND_IS_IDENTICAL 16 -#define ZEND_IS_NOT_IDENTICAL 17 -#define ZEND_IS_EQUAL 18 -#define ZEND_IS_NOT_EQUAL 19 -#define ZEND_IS_SMALLER 20 -#define ZEND_IS_SMALLER_OR_EQUAL 21 -#define ZEND_ASSIGN 22 -#define ZEND_ASSIGN_DIM 23 -#define ZEND_ASSIGN_OBJ 24 -#define ZEND_ASSIGN_STATIC_PROP 25 -#define ZEND_ASSIGN_OP 26 -#define ZEND_ASSIGN_DIM_OP 27 -#define ZEND_ASSIGN_OBJ_OP 28 -#define ZEND_ASSIGN_STATIC_PROP_OP 29 -#define ZEND_ASSIGN_REF 30 -#define ZEND_QM_ASSIGN 31 -#define ZEND_ASSIGN_OBJ_REF 32 -#define ZEND_ASSIGN_STATIC_PROP_REF 33 -#define ZEND_PRE_INC 34 -#define ZEND_PRE_DEC 35 -#define ZEND_POST_INC 36 -#define ZEND_POST_DEC 37 -#define ZEND_PRE_INC_STATIC_PROP 38 -#define ZEND_PRE_DEC_STATIC_PROP 39 -#define ZEND_POST_INC_STATIC_PROP 40 -#define ZEND_POST_DEC_STATIC_PROP 41 -#define ZEND_JMP 42 -#define ZEND_JMPZ 43 -#define ZEND_JMPNZ 44 -#define ZEND_JMPZ_EX 46 -#define ZEND_JMPNZ_EX 47 -#define ZEND_CASE 48 -#define ZEND_CHECK_VAR 49 -#define ZEND_SEND_VAR_NO_REF_EX 50 -#define ZEND_CAST 51 -#define ZEND_BOOL 52 -#define ZEND_FAST_CONCAT 53 -#define ZEND_ROPE_INIT 54 -#define ZEND_ROPE_ADD 55 -#define ZEND_ROPE_END 56 -#define ZEND_BEGIN_SILENCE 57 -#define ZEND_END_SILENCE 58 -#define ZEND_INIT_FCALL_BY_NAME 59 -#define ZEND_DO_FCALL 60 -#define ZEND_INIT_FCALL 61 -#define ZEND_RETURN 62 -#define ZEND_RECV 63 -#define ZEND_RECV_INIT 64 -#define ZEND_SEND_VAL 65 -#define ZEND_SEND_VAR_EX 66 -#define ZEND_SEND_REF 67 -#define ZEND_NEW 68 -#define ZEND_INIT_NS_FCALL_BY_NAME 69 -#define ZEND_FREE 70 -#define ZEND_INIT_ARRAY 71 -#define ZEND_ADD_ARRAY_ELEMENT 72 -#define ZEND_INCLUDE_OR_EVAL 73 -#define ZEND_UNSET_VAR 74 -#define ZEND_UNSET_DIM 75 -#define ZEND_UNSET_OBJ 76 -#define ZEND_FE_RESET_R 77 -#define ZEND_FE_FETCH_R 78 -#define ZEND_EXIT 79 -#define ZEND_FETCH_R 80 -#define ZEND_FETCH_DIM_R 81 -#define ZEND_FETCH_OBJ_R 82 -#define ZEND_FETCH_W 83 -#define ZEND_FETCH_DIM_W 84 -#define ZEND_FETCH_OBJ_W 85 -#define ZEND_FETCH_RW 86 -#define ZEND_FETCH_DIM_RW 87 -#define ZEND_FETCH_OBJ_RW 88 -#define ZEND_FETCH_IS 89 -#define ZEND_FETCH_DIM_IS 90 -#define ZEND_FETCH_OBJ_IS 91 -#define ZEND_FETCH_FUNC_ARG 92 -#define ZEND_FETCH_DIM_FUNC_ARG 93 -#define ZEND_FETCH_OBJ_FUNC_ARG 94 -#define ZEND_FETCH_UNSET 95 -#define ZEND_FETCH_DIM_UNSET 96 -#define ZEND_FETCH_OBJ_UNSET 97 -#define ZEND_FETCH_LIST_R 98 -#define ZEND_FETCH_CONSTANT 99 -#define ZEND_CHECK_FUNC_ARG 100 -#define ZEND_EXT_STMT 101 -#define ZEND_EXT_FCALL_BEGIN 102 -#define ZEND_EXT_FCALL_END 103 -#define ZEND_EXT_NOP 104 -#define ZEND_TICKS 105 -#define ZEND_SEND_VAR_NO_REF 106 -#define ZEND_CATCH 107 -#define ZEND_THROW 108 -#define ZEND_FETCH_CLASS 109 -#define ZEND_CLONE 110 -#define ZEND_RETURN_BY_REF 111 -#define ZEND_INIT_METHOD_CALL 112 -#define ZEND_INIT_STATIC_METHOD_CALL 113 -#define ZEND_ISSET_ISEMPTY_VAR 114 -#define ZEND_ISSET_ISEMPTY_DIM_OBJ 115 -#define ZEND_SEND_VAL_EX 116 -#define ZEND_SEND_VAR 117 -#define ZEND_INIT_USER_CALL 118 -#define ZEND_SEND_ARRAY 119 -#define ZEND_SEND_USER 120 -#define ZEND_STRLEN 121 -#define ZEND_DEFINED 122 -#define ZEND_TYPE_CHECK 123 -#define ZEND_VERIFY_RETURN_TYPE 124 -#define ZEND_FE_RESET_RW 125 -#define ZEND_FE_FETCH_RW 126 -#define ZEND_FE_FREE 127 -#define ZEND_INIT_DYNAMIC_CALL 128 -#define ZEND_DO_ICALL 129 -#define ZEND_DO_UCALL 130 -#define ZEND_DO_FCALL_BY_NAME 131 -#define ZEND_PRE_INC_OBJ 132 -#define ZEND_PRE_DEC_OBJ 133 -#define ZEND_POST_INC_OBJ 134 -#define ZEND_POST_DEC_OBJ 135 -#define ZEND_ECHO 136 -#define ZEND_OP_DATA 137 -#define ZEND_INSTANCEOF 138 -#define ZEND_GENERATOR_CREATE 139 -#define ZEND_MAKE_REF 140 -#define ZEND_DECLARE_FUNCTION 141 -#define ZEND_DECLARE_LAMBDA_FUNCTION 142 -#define ZEND_DECLARE_CONST 143 -#define ZEND_DECLARE_CLASS 144 -#define ZEND_DECLARE_CLASS_DELAYED 145 -#define ZEND_DECLARE_ANON_CLASS 146 -#define ZEND_ADD_ARRAY_UNPACK 147 -#define ZEND_ISSET_ISEMPTY_PROP_OBJ 148 -#define ZEND_HANDLE_EXCEPTION 149 -#define ZEND_USER_OPCODE 150 -#define ZEND_ASSERT_CHECK 151 -#define ZEND_JMP_SET 152 -#define ZEND_UNSET_CV 153 -#define ZEND_ISSET_ISEMPTY_CV 154 -#define ZEND_FETCH_LIST_W 155 -#define ZEND_SEPARATE 156 -#define ZEND_FETCH_CLASS_NAME 157 -#define ZEND_CALL_TRAMPOLINE 158 -#define ZEND_DISCARD_EXCEPTION 159 -#define ZEND_YIELD 160 -#define ZEND_GENERATOR_RETURN 161 -#define ZEND_FAST_CALL 162 -#define ZEND_FAST_RET 163 -#define ZEND_RECV_VARIADIC 164 -#define ZEND_SEND_UNPACK 165 -#define ZEND_YIELD_FROM 166 -#define ZEND_COPY_TMP 167 -#define ZEND_BIND_GLOBAL 168 -#define ZEND_COALESCE 169 -#define ZEND_SPACESHIP 170 -#define ZEND_FUNC_NUM_ARGS 171 -#define ZEND_FUNC_GET_ARGS 172 -#define ZEND_FETCH_STATIC_PROP_R 173 -#define ZEND_FETCH_STATIC_PROP_W 174 -#define ZEND_FETCH_STATIC_PROP_RW 175 -#define ZEND_FETCH_STATIC_PROP_IS 176 -#define ZEND_FETCH_STATIC_PROP_FUNC_ARG 177 -#define ZEND_FETCH_STATIC_PROP_UNSET 178 -#define ZEND_UNSET_STATIC_PROP 179 -#define ZEND_ISSET_ISEMPTY_STATIC_PROP 180 -#define ZEND_FETCH_CLASS_CONSTANT 181 -#define ZEND_BIND_LEXICAL 182 -#define ZEND_BIND_STATIC 183 -#define ZEND_FETCH_THIS 184 -#define ZEND_SEND_FUNC_ARG 185 -#define ZEND_ISSET_ISEMPTY_THIS 186 -#define ZEND_SWITCH_LONG 187 -#define ZEND_SWITCH_STRING 188 -#define ZEND_IN_ARRAY 189 -#define ZEND_COUNT 190 -#define ZEND_GET_CLASS 191 -#define ZEND_GET_CALLED_CLASS 192 -#define ZEND_GET_TYPE 193 -#define ZEND_ARRAY_KEY_EXISTS 194 -#define ZEND_MATCH 195 -#define ZEND_CASE_STRICT 196 -#define ZEND_MATCH_ERROR 197 -#define ZEND_JMP_NULL 198 -#define ZEND_CHECK_UNDEF_ARGS 199 -#define ZEND_FETCH_GLOBALS 200 -#define ZEND_VERIFY_NEVER_TYPE 201 -#define ZEND_CALLABLE_CONVERT 202 -#define ZEND_BIND_INIT_STATIC_OR_JMP 203 -#define ZEND_FRAMELESS_ICALL_0 204 -#define ZEND_FRAMELESS_ICALL_1 205 -#define ZEND_FRAMELESS_ICALL_2 206 -#define ZEND_FRAMELESS_ICALL_3 207 -#define ZEND_JMP_FRAMELESS 208 +#define ZEND_NOP 0 +#define ZEND_ADD 1 +#define ZEND_SUB 2 +#define ZEND_MUL 3 +#define ZEND_DIV 4 +#define ZEND_MOD 5 +#define ZEND_SL 6 +#define ZEND_SR 7 +#define ZEND_CONCAT 8 +#define ZEND_BW_OR 9 +#define ZEND_BW_AND 10 +#define ZEND_BW_XOR 11 +#define ZEND_POW 12 +#define ZEND_BW_NOT 13 +#define ZEND_BOOL_NOT 14 +#define ZEND_BOOL_XOR 15 +#define ZEND_IS_IDENTICAL 16 +#define ZEND_IS_NOT_IDENTICAL 17 +#define ZEND_IS_EQUAL 18 +#define ZEND_IS_NOT_EQUAL 19 +#define ZEND_IS_SMALLER 20 +#define ZEND_IS_SMALLER_OR_EQUAL 21 +#define ZEND_ASSIGN 22 +#define ZEND_ASSIGN_DIM 23 +#define ZEND_ASSIGN_OBJ 24 +#define ZEND_ASSIGN_STATIC_PROP 25 +#define ZEND_ASSIGN_OP 26 +#define ZEND_ASSIGN_DIM_OP 27 +#define ZEND_ASSIGN_OBJ_OP 28 +#define ZEND_ASSIGN_STATIC_PROP_OP 29 +#define ZEND_ASSIGN_REF 30 +#define ZEND_QM_ASSIGN 31 +#define ZEND_ASSIGN_OBJ_REF 32 +#define ZEND_ASSIGN_STATIC_PROP_REF 33 +#define ZEND_PRE_INC 34 +#define ZEND_PRE_DEC 35 +#define ZEND_POST_INC 36 +#define ZEND_POST_DEC 37 +#define ZEND_PRE_INC_STATIC_PROP 38 +#define ZEND_PRE_DEC_STATIC_PROP 39 +#define ZEND_POST_INC_STATIC_PROP 40 +#define ZEND_POST_DEC_STATIC_PROP 41 +#define ZEND_JMP 42 +#define ZEND_JMPZ 43 +#define ZEND_JMPNZ 44 +#define ZEND_JMPZ_EX 46 +#define ZEND_JMPNZ_EX 47 +#define ZEND_CASE 48 +#define ZEND_CHECK_VAR 49 +#define ZEND_SEND_VAR_NO_REF_EX 50 +#define ZEND_CAST 51 +#define ZEND_BOOL 52 +#define ZEND_FAST_CONCAT 53 +#define ZEND_ROPE_INIT 54 +#define ZEND_ROPE_ADD 55 +#define ZEND_ROPE_END 56 +#define ZEND_BEGIN_SILENCE 57 +#define ZEND_END_SILENCE 58 +#define ZEND_INIT_FCALL_BY_NAME 59 +#define ZEND_DO_FCALL 60 +#define ZEND_INIT_FCALL 61 +#define ZEND_RETURN 62 +#define ZEND_RECV 63 +#define ZEND_RECV_INIT 64 +#define ZEND_SEND_VAL 65 +#define ZEND_SEND_VAR_EX 66 +#define ZEND_SEND_REF 67 +#define ZEND_NEW 68 +#define ZEND_INIT_NS_FCALL_BY_NAME 69 +#define ZEND_FREE 70 +#define ZEND_INIT_ARRAY 71 +#define ZEND_ADD_ARRAY_ELEMENT 72 +#define ZEND_INCLUDE_OR_EVAL 73 +#define ZEND_UNSET_VAR 74 +#define ZEND_UNSET_DIM 75 +#define ZEND_UNSET_OBJ 76 +#define ZEND_FE_RESET_R 77 +#define ZEND_FE_FETCH_R 78 +#define ZEND_EXIT 79 +#define ZEND_FETCH_R 80 +#define ZEND_FETCH_DIM_R 81 +#define ZEND_FETCH_OBJ_R 82 +#define ZEND_FETCH_W 83 +#define ZEND_FETCH_DIM_W 84 +#define ZEND_FETCH_OBJ_W 85 +#define ZEND_FETCH_RW 86 +#define ZEND_FETCH_DIM_RW 87 +#define ZEND_FETCH_OBJ_RW 88 +#define ZEND_FETCH_IS 89 +#define ZEND_FETCH_DIM_IS 90 +#define ZEND_FETCH_OBJ_IS 91 +#define ZEND_FETCH_FUNC_ARG 92 +#define ZEND_FETCH_DIM_FUNC_ARG 93 +#define ZEND_FETCH_OBJ_FUNC_ARG 94 +#define ZEND_FETCH_UNSET 95 +#define ZEND_FETCH_DIM_UNSET 96 +#define ZEND_FETCH_OBJ_UNSET 97 +#define ZEND_FETCH_LIST_R 98 +#define ZEND_FETCH_CONSTANT 99 +#define ZEND_CHECK_FUNC_ARG 100 +#define ZEND_EXT_STMT 101 +#define ZEND_EXT_FCALL_BEGIN 102 +#define ZEND_EXT_FCALL_END 103 +#define ZEND_EXT_NOP 104 +#define ZEND_TICKS 105 +#define ZEND_SEND_VAR_NO_REF 106 +#define ZEND_CATCH 107 +#define ZEND_THROW 108 +#define ZEND_FETCH_CLASS 109 +#define ZEND_CLONE 110 +#define ZEND_RETURN_BY_REF 111 +#define ZEND_INIT_METHOD_CALL 112 +#define ZEND_INIT_STATIC_METHOD_CALL 113 +#define ZEND_ISSET_ISEMPTY_VAR 114 +#define ZEND_ISSET_ISEMPTY_DIM_OBJ 115 +#define ZEND_SEND_VAL_EX 116 +#define ZEND_SEND_VAR 117 +#define ZEND_INIT_USER_CALL 118 +#define ZEND_SEND_ARRAY 119 +#define ZEND_SEND_USER 120 +#define ZEND_STRLEN 121 +#define ZEND_DEFINED 122 +#define ZEND_TYPE_CHECK 123 +#define ZEND_VERIFY_RETURN_TYPE 124 +#define ZEND_FE_RESET_RW 125 +#define ZEND_FE_FETCH_RW 126 +#define ZEND_FE_FREE 127 +#define ZEND_INIT_DYNAMIC_CALL 128 +#define ZEND_DO_ICALL 129 +#define ZEND_DO_UCALL 130 +#define ZEND_DO_FCALL_BY_NAME 131 +#define ZEND_PRE_INC_OBJ 132 +#define ZEND_PRE_DEC_OBJ 133 +#define ZEND_POST_INC_OBJ 134 +#define ZEND_POST_DEC_OBJ 135 +#define ZEND_ECHO 136 +#define ZEND_OP_DATA 137 +#define ZEND_INSTANCEOF 138 +#define ZEND_GENERATOR_CREATE 139 +#define ZEND_MAKE_REF 140 +#define ZEND_DECLARE_FUNCTION 141 +#define ZEND_DECLARE_LAMBDA_FUNCTION 142 +#define ZEND_DECLARE_CONST 143 +#define ZEND_DECLARE_CLASS 144 +#define ZEND_DECLARE_CLASS_DELAYED 145 +#define ZEND_DECLARE_ANON_CLASS 146 +#define ZEND_ADD_ARRAY_UNPACK 147 +#define ZEND_ISSET_ISEMPTY_PROP_OBJ 148 +#define ZEND_HANDLE_EXCEPTION 149 +#define ZEND_USER_OPCODE 150 +#define ZEND_ASSERT_CHECK 151 +#define ZEND_JMP_SET 152 +#define ZEND_UNSET_CV 153 +#define ZEND_ISSET_ISEMPTY_CV 154 +#define ZEND_FETCH_LIST_W 155 +#define ZEND_SEPARATE 156 +#define ZEND_FETCH_CLASS_NAME 157 +#define ZEND_CALL_TRAMPOLINE 158 +#define ZEND_DISCARD_EXCEPTION 159 +#define ZEND_YIELD 160 +#define ZEND_GENERATOR_RETURN 161 +#define ZEND_FAST_CALL 162 +#define ZEND_FAST_RET 163 +#define ZEND_RECV_VARIADIC 164 +#define ZEND_SEND_UNPACK 165 +#define ZEND_YIELD_FROM 166 +#define ZEND_COPY_TMP 167 +#define ZEND_BIND_GLOBAL 168 +#define ZEND_COALESCE 169 +#define ZEND_SPACESHIP 170 +#define ZEND_FUNC_NUM_ARGS 171 +#define ZEND_FUNC_GET_ARGS 172 +#define ZEND_FETCH_STATIC_PROP_R 173 +#define ZEND_FETCH_STATIC_PROP_W 174 +#define ZEND_FETCH_STATIC_PROP_RW 175 +#define ZEND_FETCH_STATIC_PROP_IS 176 +#define ZEND_FETCH_STATIC_PROP_FUNC_ARG 177 +#define ZEND_FETCH_STATIC_PROP_UNSET 178 +#define ZEND_UNSET_STATIC_PROP 179 +#define ZEND_ISSET_ISEMPTY_STATIC_PROP 180 +#define ZEND_FETCH_CLASS_CONSTANT 181 +#define ZEND_BIND_LEXICAL 182 +#define ZEND_BIND_STATIC 183 +#define ZEND_FETCH_THIS 184 +#define ZEND_SEND_FUNC_ARG 185 +#define ZEND_ISSET_ISEMPTY_THIS 186 +#define ZEND_SWITCH_LONG 187 +#define ZEND_SWITCH_STRING 188 +#define ZEND_IN_ARRAY 189 +#define ZEND_COUNT 190 +#define ZEND_GET_CLASS 191 +#define ZEND_GET_CALLED_CLASS 192 +#define ZEND_GET_TYPE 193 +#define ZEND_ARRAY_KEY_EXISTS 194 +#define ZEND_MATCH 195 +#define ZEND_CASE_STRICT 196 +#define ZEND_MATCH_ERROR 197 +#define ZEND_JMP_NULL 198 +#define ZEND_CHECK_UNDEF_ARGS 199 +#define ZEND_FETCH_GLOBALS 200 +#define ZEND_VERIFY_NEVER_TYPE 201 +#define ZEND_CALLABLE_CONVERT 202 +#define ZEND_BIND_INIT_STATIC_OR_JMP 203 +#define ZEND_FRAMELESS_ICALL_0 204 +#define ZEND_FRAMELESS_ICALL_1 205 +#define ZEND_FRAMELESS_ICALL_2 206 +#define ZEND_FRAMELESS_ICALL_3 207 +#define ZEND_JMP_FRAMELESS 208 +#define ZEND_INIT_PARENT_PROPERTY_HOOK_CALL 209 -#define ZEND_VM_LAST_OPCODE 208 +#define ZEND_VM_LAST_OPCODE 209 #endif diff --git a/configure.ac b/configure.ac index d09a9876e84ad..3ba5703871bd1 100644 --- a/configure.ac +++ b/configure.ac @@ -1741,6 +1741,7 @@ PHP_ADD_SOURCES([Zend], [m4_normalize([ zend_observer.c zend_opcode.c zend_operators.c + zend_property_hooks.c zend_ptr_stack.c zend_signal.c zend_smart_str.c diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 68418d9e2d6b5..1e344cde436ff 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -27,6 +27,7 @@ #include "zend_portability.h" #include #include "zend_enum.h" +#include "zend_property_hooks.h" static const char digits[] = "0123456789abcdef"; @@ -113,14 +114,17 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, { int r, need_comma = 0; HashTable *myht, *prop_ht; + zend_refcounted *recursion_rc; if (Z_TYPE_P(val) == IS_ARRAY) { myht = Z_ARRVAL_P(val); + recursion_rc = (zend_refcounted *)myht; prop_ht = NULL; r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val); } else if (Z_OBJ_P(val)->properties == NULL && Z_OBJ_HT_P(val)->get_properties_for == NULL - && Z_OBJ_HT_P(val)->get_properties == zend_std_get_properties) { + && Z_OBJ_HT_P(val)->get_properties == zend_std_get_properties + && Z_OBJ_P(val)->ce->num_hooked_props == 0) { /* Optimized version without rebuilding properties HashTable */ zend_object *obj = Z_OBJ_P(val); zend_class_entry *ce = obj->ce; @@ -196,18 +200,26 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, smart_str_appendc(buf, '}'); return SUCCESS; } else { + zend_object *obj = Z_OBJ_P(val); prop_ht = myht = zend_get_properties_for(val, ZEND_PROP_PURPOSE_JSON); + if (obj->ce->num_hooked_props == 0) { + recursion_rc = (zend_refcounted *)prop_ht; + } else { + /* Protecting the object itself is fine here because myht is temporary and can't be + * referenced from a different place in the object graph. */ + recursion_rc = (zend_refcounted *)obj; + } r = PHP_JSON_OUTPUT_OBJECT; } - if (myht && GC_IS_RECURSIVE(myht)) { + if (recursion_rc && GC_IS_RECURSIVE(recursion_rc)) { encoder->error_code = PHP_JSON_ERROR_RECURSION; smart_str_appendl(buf, "null", 4); zend_release_properties(prop_ht); return FAILURE; } - PHP_JSON_HASH_PROTECT_RECURSION(myht); + PHP_JSON_HASH_PROTECT_RECURSION(recursion_rc); if (r == PHP_JSON_OUTPUT_ARRAY) { smart_str_appendc(buf, '['); @@ -225,7 +237,12 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, zend_ulong index; ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) { + zval tmp; + ZVAL_UNDEF(&tmp); + if (r == PHP_JSON_OUTPUT_ARRAY) { + ZEND_ASSERT(Z_TYPE_P(data) != IS_PTR); + if (need_comma) { smart_str_appendc(buf, ','); } else { @@ -241,6 +258,21 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, continue; } + /* data is IS_PTR for properties with hooks. */ + if (UNEXPECTED(Z_TYPE_P(data) == IS_PTR)) { + zend_property_info *prop_info = Z_PTR_P(data); + if ((prop_info->flags & ZEND_ACC_VIRTUAL) && !prop_info->hooks[ZEND_PROPERTY_HOOK_GET]) { + continue; + } + zend_read_property_ex(prop_info->ce, Z_OBJ_P(val), prop_info->name, /* silent */ true, &tmp); + if (EG(exception)) { + PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc); + zend_release_properties(prop_ht); + return FAILURE; + } + data = &tmp; + } + if (need_comma) { smart_str_appendc(buf, ','); } else { @@ -278,14 +310,16 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, if (php_json_encode_zval(buf, data, options, encoder) == FAILURE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc); zend_release_properties(prop_ht); + zval_ptr_dtor(&tmp); return FAILURE; } + zval_ptr_dtor(&tmp); } ZEND_HASH_FOREACH_END(); } - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc); if (encoder->depth > encoder->max_depth) { encoder->error_code = PHP_JSON_ERROR_DEPTH; diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index b3a5b5735c6ee..17333fc0d5067 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -266,6 +266,7 @@ static int zend_jit_needs_call_chain(zend_call_info *call_info, uint32_t b, cons case ZEND_INIT_FCALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: case ZEND_INIT_DYNAMIC_CALL: @@ -350,6 +351,7 @@ static int zend_jit_needs_call_chain(zend_call_info *call_info, uint32_t b, cons case ZEND_INIT_FCALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: case ZEND_INIT_DYNAMIC_CALL: @@ -594,10 +596,12 @@ static zend_property_info* zend_get_known_property_info(const zend_op_array *op_ } } + // TODO: Treat property hooks more precisely. info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member); if (info == NULL || !IS_VALID_PROPERTY_OFFSET(info->offset) || - (info->flags & ZEND_ACC_STATIC)) { + (info->flags & ZEND_ACC_STATIC) || + info->hooks) { return NULL; } @@ -634,10 +638,12 @@ static bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string *memb } } + // TODO: Treat property hooks more precisely. info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member); if (info == NULL || !IS_VALID_PROPERTY_OFFSET(info->offset) || - (info->flags & ZEND_ACC_STATIC)) { + (info->flags & ZEND_ACC_STATIC) || + info->hooks) { return 1; } @@ -1462,6 +1468,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op case ZEND_INIT_METHOD_CALL: case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_INIT_USER_CALL: case ZEND_NEW: call_level++; diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 9ef79c80b58b2..8ed378d7552b0 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1856,7 +1856,7 @@ static void ZEND_FASTCALL zend_jit_fetch_obj_r_slow(zend_object *zobj) static void ZEND_FASTCALL zend_jit_fetch_obj_r_dynamic(zend_object *zobj, intptr_t prop_offset) { - if (zobj->properties) { + if (zobj->properties && !IS_HOOKED_PROPERTY_OFFSET(prop_offset)) { zval *retval; zend_execute_data *execute_data = EG(current_execute_data); const zend_op *opline = EX(opline); @@ -1912,7 +1912,7 @@ static void ZEND_FASTCALL zend_jit_fetch_obj_is_slow(zend_object *zobj) static void ZEND_FASTCALL zend_jit_fetch_obj_is_dynamic(zend_object *zobj, intptr_t prop_offset) { - if (zobj->properties) { + if (zobj->properties && !IS_HOOKED_PROPERTY_OFFSET(prop_offset)) { zval *retval; zend_execute_data *execute_data = EG(current_execute_data); const zend_op *opline = EX(opline); @@ -1967,7 +1967,7 @@ static zval* ZEND_FASTCALL zend_jit_fetch_obj_r_slow_ex(zend_object *zobj) static zval* ZEND_FASTCALL zend_jit_fetch_obj_r_dynamic_ex(zend_object *zobj, intptr_t prop_offset) { - if (zobj->properties) { + if (zobj->properties && !IS_HOOKED_PROPERTY_OFFSET(prop_offset)) { zval *retval; zend_execute_data *execute_data = EG(current_execute_data); const zend_op *opline = EX(opline); @@ -2019,7 +2019,7 @@ static zval* ZEND_FASTCALL zend_jit_fetch_obj_is_slow_ex(zend_object *zobj) static zval* ZEND_FASTCALL zend_jit_fetch_obj_is_dynamic_ex(zend_object *zobj, intptr_t prop_offset) { - if (zobj->properties) { + if (zobj->properties && !IS_HOOKED_PROPERTY_OFFSET(prop_offset)) { zval *retval; zend_execute_data *execute_data = EG(current_execute_data); const zend_op *opline = EX(opline); @@ -2135,9 +2135,10 @@ static zend_never_inline bool zend_handle_fetch_obj_flags( } ZVAL_NULL(ptr); } - - ZVAL_NEW_REF(ptr, ptr); - ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(ptr), prop_info); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + ZVAL_NEW_REF(ptr, ptr); + ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(ptr), prop_info); + } } break; EMPTY_SWITCH_DEFAULT_CASE() diff --git a/ext/opcache/jit/zend_jit_internal.h b/ext/opcache/jit/zend_jit_internal.h index 25123a92276e9..ccc86a4834cf5 100644 --- a/ext/opcache/jit/zend_jit_internal.h +++ b/ext/opcache/jit/zend_jit_internal.h @@ -692,7 +692,8 @@ static zend_always_inline bool zend_jit_may_be_polymorphic_call(const zend_op *o { if (opline->opcode == ZEND_INIT_FCALL || opline->opcode == ZEND_INIT_FCALL_BY_NAME - || opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { + || opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME + || opline->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL) { return 0; } else if (opline->opcode == ZEND_INIT_METHOD_CALL || opline->opcode == ZEND_INIT_DYNAMIC_CALL) { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 84dce9707e209..5ee8ff9e84ee6 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -13885,7 +13885,7 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit, may_be_dynamic = zend_may_be_dynamic_property(ce, Z_STR_P(member), opline->op1_type == IS_UNUSED, op_array->filename); if (may_be_dynamic) { - ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, IR_NULL)); + ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET))); if (opline->opcode == ZEND_FETCH_OBJ_W) { ir_IF_TRUE_cold(if_dynamic); ir_END_list(slow_inputs); @@ -14365,7 +14365,7 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, ir_ref offset_ref = ir_LOAD_A( ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*))); - ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, IR_NULL)); + ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET))); ir_IF_TRUE_cold(if_dynamic); ir_END_list(slow_inputs); @@ -14723,7 +14723,7 @@ static int zend_jit_assign_obj_op(zend_jit_ctx *jit, ir_ref offset_ref = ir_LOAD_A( ir_ADD_OFFSET(run_time_cache, ((opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*))); - ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, IR_NULL)); + ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET))); ir_IF_TRUE_cold(if_dynamic); ir_END_list(slow_inputs); @@ -15146,7 +15146,7 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, ir_ref offset_ref = ir_LOAD_A( ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*))); - ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, IR_NULL)); + ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET))); ir_IF_TRUE_cold(if_dynamic); ir_END_list(slow_inputs); diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 57333c05101a9..de2481fb8d2bc 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1080,7 +1080,8 @@ static const zend_op *zend_jit_trace_find_init_fcall_op(zend_jit_trace_rec *p, c || p->opline->opcode == ZEND_INIT_USER_CALL || p->opline->opcode == ZEND_NEW || p->opline->opcode == ZEND_INIT_METHOD_CALL - || p->opline->opcode == ZEND_INIT_STATIC_METHOD_CALL) { + || p->opline->opcode == ZEND_INIT_STATIC_METHOD_CALL + || p->opline->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL) { return p->opline; } return NULL; @@ -1118,6 +1119,7 @@ static const zend_op *zend_jit_trace_find_init_fcall_op(zend_jit_trace_rec *p, c case ZEND_INIT_METHOD_CALL: case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_INIT_USER_CALL: case ZEND_NEW: if (call_level == 0) { @@ -2547,6 +2549,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin case ZEND_INIT_METHOD_CALL: case ZEND_INIT_DYNAMIC_CALL: //case ZEND_INIT_STATIC_METHOD_CALL: + //case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: //case ZEND_INIT_USER_CALL: //case ZEND_NEW: frame->used_stack = zend_vm_calc_used_stack(opline->extended_value, (zend_function*)p->func); @@ -4344,6 +4347,7 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par case ZEND_INIT_METHOD_CALL: case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_INIT_USER_CALL: case ZEND_NEW: frame->call_level++; @@ -7011,6 +7015,7 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par case ZEND_INIT_METHOD_CALL: case ZEND_INIT_DYNAMIC_CALL: //case ZEND_INIT_STATIC_METHOD_CALL: + //case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: //case ZEND_INIT_USER_CALL: //case ZEND_NEW: checked_stack += call->used_stack; diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index fd4d83aeca71a..2a7399c185d5f 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -828,7 +828,17 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, } } break; - case ZEND_FETCH_OBJ_R: + case ZEND_FETCH_OBJ_R: { + if (opline->op2_type == IS_CONST) { + /* Remove the SIMPLE_GET flag to avoid inlining hooks. */ + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_FETCH_REF); + uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); + if (IS_HOOKED_PROPERTY_OFFSET(prop_offset)) { + CACHE_PTR_EX(cache_slot + 1, (void*)((uintptr_t)CACHED_PTR_EX(cache_slot + 1) & ~ZEND_PROPERTY_HOOK_SIMPLE_GET_BIT)); \ + } + } + ZEND_FALLTHROUGH; + } case ZEND_FETCH_OBJ_W: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_IS: @@ -863,6 +873,7 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, prop_info = zend_get_property_info(Z_OBJCE_P(obj), prop_name, 1); if (prop_info && prop_info != ZEND_WRONG_PROPERTY_INFO + && !prop_info->hooks && !(prop_info->flags & ZEND_ACC_STATIC)) { val = OBJ_PROP(Z_OBJ_P(obj), prop_info->offset); TRACE_RECORD_VM(ZEND_JIT_TRACE_VAL_INFO, NULL, Z_TYPE_P(val), 0, 0); diff --git a/ext/opcache/tests/dump_property_hooks.phpt b/ext/opcache/tests/dump_property_hooks.phpt new file mode 100644 index 0000000000000..d8727e873b85f --- /dev/null +++ b/ext/opcache/tests/dump_property_hooks.phpt @@ -0,0 +1,64 @@ +--TEST-- +Optimizer optimizes hooks, OpCache dump emits them +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.opt_debug_level=0x20000 +--EXTENSIONS-- +opcache +--FILE-- +prop); +$a->prop = 41; + +?> +--EXPECTF-- +$_main: + ; (lines=10, args=0, vars=1, tmps=%d) + ; (after optimizer) + ; %sdump_property_hooks.php:1-22 +0000 V1 = NEW 0 string("A") +0001 DO_FCALL +0002 ASSIGN CV0($a) V1 +0003 INIT_FCALL 1 %d string("var_dump") +0004 T1 = FETCH_OBJ_R CV0($a) string("prop") +0005 SEND_VAL T1 1 +0006 DO_ICALL +0007 ASSIGN_OBJ CV0($a) string("prop") +0008 OP_DATA int(41) +0009 RETURN int(1) +LIVE RANGES: + 1: 0001 - 0002 (new) + +A::$prop::get: + ; (lines=1, args=0, vars=0, tmps=%d) + ; (after optimizer) + ; %sdump_property_hooks.php:5-8 +0000 RETURN int(42) + +A::$prop::set: + ; (lines=4, args=1, vars=1, tmps=%d) + ; (after optimizer) + ; %sdump_property_hooks.php:9-13 +0000 CV0($value) = RECV 1 +0001 T1 = FAST_CONCAT string("Setting ") CV0($value) +0002 ECHO T1 +0003 RETURN null +int(42) +Setting 41 diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 1a8ae8873c1cf..c09e39579a474 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -186,6 +186,8 @@ static int zend_file_cache_flock(int fd, int type) } \ } while (0) +#define HOOKED_ITERATOR_PLACEHOLDER ((void*)1) + static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX}; @@ -485,6 +487,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_ATTRIBUTES(op_array->attributes); SERIALIZE_PTR(op_array->try_catch_array); SERIALIZE_PTR(op_array->prototype); + SERIALIZE_PTR(op_array->prop_info); return; } zend_shared_alloc_register_xlat_entry(op_array->opcodes, op_array->opcodes); @@ -636,6 +639,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_ATTRIBUTES(op_array->attributes); SERIALIZE_PTR(op_array->try_catch_array); SERIALIZE_PTR(op_array->prototype); + SERIALIZE_PTR(op_array->prop_info); } } @@ -672,6 +676,20 @@ static void zend_file_cache_serialize_prop_info(zval *zv, SERIALIZE_STR(prop->doc_comment); } SERIALIZE_ATTRIBUTES(prop->attributes); + SERIALIZE_PTR(prop->prototype); + if (prop->hooks) { + SERIALIZE_PTR(prop->hooks); + zend_function **hooks = prop->hooks; + UNSERIALIZE_PTR(hooks); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (hooks[i]) { + SERIALIZE_PTR(hooks[i]); + zend_function *hook = hooks[i]; + UNSERIALIZE_PTR(hook); + zend_file_cache_serialize_op_array(&hook->op_array, script, info, buf); + } + } + } zend_file_cache_serialize_type(&prop->type, script, info, buf); } } @@ -887,6 +905,11 @@ static void zend_file_cache_serialize_class(zval *zv, ZEND_MAP_PTR_INIT(ce->mutable_data, NULL); ce->inheritance_cache = NULL; + + if (ce->get_iterator) { + ZEND_ASSERT(ce->get_iterator == zend_hooked_object_get_iterator); + ce->get_iterator = HOOKED_ITERATOR_PLACEHOLDER; + } } static void zend_file_cache_serialize_warnings( @@ -1346,6 +1369,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr UNSERIALIZE_ATTRIBUTES(op_array->attributes); UNSERIALIZE_PTR(op_array->try_catch_array); UNSERIALIZE_PTR(op_array->prototype); + UNSERIALIZE_PTR(op_array->prop_info); return; } @@ -1480,6 +1504,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr UNSERIALIZE_ATTRIBUTES(op_array->attributes); UNSERIALIZE_PTR(op_array->try_catch_array); UNSERIALIZE_PTR(op_array->prototype); + UNSERIALIZE_PTR(op_array->prop_info); } } @@ -1512,6 +1537,16 @@ static void zend_file_cache_unserialize_prop_info(zval *zv, UNSERIALIZE_STR(prop->doc_comment); } UNSERIALIZE_ATTRIBUTES(prop->attributes); + UNSERIALIZE_PTR(prop->prototype); + if (prop->hooks) { + UNSERIALIZE_PTR(prop->hooks); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop->hooks[i]) { + UNSERIALIZE_PTR(prop->hooks[i]); + zend_file_cache_unserialize_op_array(&prop->hooks[i]->op_array, script, buf); + } + } + } zend_file_cache_unserialize_type(&prop->type, prop->ce, script, buf); } } @@ -1723,6 +1758,11 @@ static void zend_file_cache_unserialize_class(zval *zv, ZEND_MAP_PTR_INIT(ce->static_members_table, NULL); } + if (ce->get_iterator) { + ZEND_ASSERT(ce->get_iterator == HOOKED_ITERATOR_PLACEHOLDER); + ce->get_iterator = zend_hooked_object_get_iterator; + } + // Memory addresses of object handlers are not stable. They can change due to ASLR or order of linking dynamic. To // avoid pointing to invalid memory we relink default_object_handlers here. ce->default_object_handlers = ce->ce_flags & ZEND_ACC_ENUM ? &zend_enum_object_handlers : &std_object_handlers; diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 7cc22f9669196..e12363b2ca5b3 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -703,9 +703,8 @@ static void zend_persist_op_array(zval *zv) } } -static void zend_persist_class_method(zval *zv, zend_class_entry *ce) +static zend_op_array *zend_persist_class_method(zend_op_array *op_array, zend_class_entry *ce) { - zend_op_array *op_array = Z_PTR_P(zv); zend_op_array *old_op_array; if (op_array->type != ZEND_USER_FUNCTION) { @@ -713,9 +712,9 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce) if (op_array->fn_flags & ZEND_ACC_ARENA_ALLOCATED) { old_op_array = zend_shared_alloc_get_xlat_entry(op_array); if (old_op_array) { - Z_PTR_P(zv) = old_op_array; + return old_op_array; } else { - op_array = Z_PTR_P(zv) = zend_shared_memdup_put(op_array, sizeof(zend_internal_function)); + op_array = zend_shared_memdup_put(op_array, sizeof(zend_internal_function)); if (op_array->scope) { void *persist_ptr; @@ -735,19 +734,18 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce) } } } - return; + return op_array; } if ((op_array->fn_flags & ZEND_ACC_IMMUTABLE) && !ZCG(current_persistent_script)->corrupted && zend_accel_in_shm(op_array)) { zend_shared_alloc_register_xlat_entry(op_array, op_array); - return; + return op_array; } old_op_array = zend_shared_alloc_get_xlat_entry(op_array); if (old_op_array) { - Z_PTR_P(zv) = old_op_array; if (op_array->refcount && --(*op_array->refcount) == 0) { efree(op_array->refcount); } @@ -759,9 +757,10 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce) if (old_function_name) { zend_string_release_ex(old_function_name, 0); } - return; + return old_op_array; } - op_array = Z_PTR_P(zv) = zend_shared_memdup_put(op_array, sizeof(zend_op_array)); + + op_array = zend_shared_memdup_put(op_array, sizeof(zend_op_array)); zend_persist_op_array_ex(op_array, NULL); if (ce->ce_flags & ZEND_ACC_IMMUTABLE) { op_array->fn_flags |= ZEND_ACC_IMMUTABLE; @@ -775,6 +774,7 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce) ZEND_MAP_PTR_INIT(op_array->static_variables_ptr, NULL); } } + return op_array; } static zend_property_info *zend_persist_property_info(zend_property_info *prop) @@ -800,6 +800,32 @@ static zend_property_info *zend_persist_property_info(zend_property_info *prop) if (prop->attributes) { prop->attributes = zend_persist_attributes(prop->attributes); } + if (prop->prototype) { + zend_property_info *new_prototype = (zend_property_info *) zend_shared_alloc_get_xlat_entry(prop->prototype); + if (new_prototype) { + prop->prototype = new_prototype; + } + } + if (prop->hooks) { + prop->hooks = zend_shared_memdup_put(prop->hooks, ZEND_PROPERTY_HOOK_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop->hooks[i]) { + zend_op_array *hook = zend_persist_class_method(&prop->hooks[i]->op_array, ce); +#ifdef HAVE_JIT + if (JIT_G(on) && JIT_G(opt_level) <= ZEND_JIT_LEVEL_OPT_FUNCS) { + if (hook->scope == ce && !(hook->fn_flags & ZEND_ACC_TRAIT_CLONE)) { + zend_jit_op_array(hook, ZCG(current_persistent_script) ? &ZCG(current_persistent_script)->script : NULL); + } + } +#endif + zend_property_info *new_prop_info = (zend_property_info *) zend_shared_alloc_get_xlat_entry(hook->prop_info); + if (new_prop_info) { + hook->prop_info = new_prop_info; + } + prop->hooks[i] = (zend_function *) hook; + } + } + } zend_persist_type(&prop->type); return prop; } @@ -893,7 +919,7 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) ZEND_HASH_MAP_FOREACH_BUCKET(&ce->function_table, p) { ZEND_ASSERT(p->key != NULL); zend_accel_store_interned_string(p->key); - zend_persist_class_method(&p->val, ce); + Z_PTR(p->val) = zend_persist_class_method(Z_PTR(p->val), ce); } ZEND_HASH_FOREACH_END(); HT_FLAGS(&ce->function_table) &= (HASH_FLAG_UNINITIALIZED | HASH_FLAG_STATIC_KEYS); if (ce->default_properties_table) { diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 881f3954c04f3..d7a0455e3e5c1 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -329,9 +329,8 @@ static void zend_persist_op_array_calc(zval *zv) } } -static void zend_persist_class_method_calc(zval *zv) +static void zend_persist_class_method_calc(zend_op_array *op_array) { - zend_op_array *op_array = Z_PTR_P(zv); zend_op_array *old_op_array; if (op_array->type != ZEND_USER_FUNCTION) { @@ -340,7 +339,7 @@ static void zend_persist_class_method_calc(zval *zv) old_op_array = zend_shared_alloc_get_xlat_entry(op_array); if (!old_op_array) { ADD_SIZE(sizeof(zend_internal_function)); - zend_shared_alloc_register_xlat_entry(op_array, Z_PTR_P(zv)); + zend_shared_alloc_register_xlat_entry(op_array, op_array); } } return; @@ -356,8 +355,8 @@ static void zend_persist_class_method_calc(zval *zv) old_op_array = zend_shared_alloc_get_xlat_entry(op_array); if (!old_op_array) { ADD_SIZE(sizeof(zend_op_array)); - zend_persist_op_array_calc_ex(Z_PTR_P(zv)); - zend_shared_alloc_register_xlat_entry(op_array, Z_PTR_P(zv)); + zend_persist_op_array_calc_ex(op_array); + zend_shared_alloc_register_xlat_entry(op_array, op_array); } else { /* If op_array is shared, the function name refcount is still incremented for each use, * so we need to release it here. We remembered the original function name in xlat. */ @@ -380,6 +379,14 @@ static void zend_persist_property_info_calc(zend_property_info *prop) if (prop->attributes) { zend_persist_attributes_calc(prop->attributes); } + if (prop->hooks) { + ADD_SIZE(ZEND_PROPERTY_HOOK_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop->hooks[i]) { + zend_persist_class_method_calc(&prop->hooks[i]->op_array); + } + } + } } static void zend_persist_class_constant_calc(zval *zv) @@ -433,7 +440,7 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) ZEND_HASH_MAP_FOREACH_BUCKET(&ce->function_table, p) { ZEND_ASSERT(p->key != NULL); ADD_INTERNED_STRING(p->key); - zend_persist_class_method_calc(&p->val); + zend_persist_class_method_calc(Z_PTR(p->val)); } ZEND_HASH_FOREACH_END(); if (ce->default_properties_table) { int i; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index b63e94ebf5e75..0956bbc1acf70 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -18,6 +18,8 @@ +----------------------------------------------------------------------+ */ +#include "zend_compile.h" +#include "zend_type_info.h" #ifdef HAVE_CONFIG_H #include #endif @@ -98,6 +100,7 @@ PHPAPI zend_class_entry *reflection_enum_unit_case_ptr; PHPAPI zend_class_entry *reflection_enum_backed_case_ptr; PHPAPI zend_class_entry *reflection_fiber_ptr; PHPAPI zend_class_entry *reflection_constant_ptr; +PHPAPI zend_class_entry *reflection_property_hook_type_ptr; /* Exception throwing macro */ #define _DO_THROW(msg) \ @@ -908,6 +911,8 @@ static zval *property_get_default(zend_property_info *prop_info) { zval *prop = &ce->default_static_members_table[prop_info->offset]; ZVAL_DEINDIRECT(prop); return prop; + } else if (prop_info->flags & ZEND_ACC_VIRTUAL) { + return NULL; } else { return &ce->default_properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]; } @@ -954,7 +959,7 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha smart_str_append_printf(str, "$%s", prop_name); zval *default_value = property_get_default(prop); - if (!Z_ISUNDEF_P(default_value)) { + if (default_value && !Z_ISUNDEF_P(default_value)) { smart_str_appends(str, " = "); if (format_default_value(str, default_value) == FAILURE) { return; @@ -4145,7 +4150,7 @@ static void add_class_vars(zend_class_entry *ce, bool statics, zval *return_valu } prop = property_get_default(prop_info); - if (Z_ISUNDEF_P(prop)) { + if (!prop || Z_ISUNDEF_P(prop)) { continue; } @@ -5686,6 +5691,16 @@ ZEND_METHOD(ReflectionProperty, isReadOnly) _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_READONLY); } +ZEND_METHOD(ReflectionProperty, isAbstract) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_ABSTRACT); +} + +ZEND_METHOD(ReflectionProperty, isVirtual) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_VIRTUAL); +} + /* {{{ Returns whether this property is default (declared at compilation time). */ ZEND_METHOD(ReflectionProperty, isDefault) { @@ -5712,7 +5727,7 @@ ZEND_METHOD(ReflectionProperty, getModifiers) { reflection_object *intern; property_reference *ref; - uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY; + uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY | ZEND_ACC_ABSTRACT | ZEND_ACC_VIRTUAL; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); @@ -5814,6 +5829,72 @@ ZEND_METHOD(ReflectionProperty, setValue) } /* }}} */ +ZEND_METHOD(ReflectionProperty, getRawValue) +{ + reflection_object *intern; + property_reference *ref; + zval *object; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "o", &object) == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ref); + + if (prop_get_flags(ref) & ZEND_ACC_STATIC) { + _DO_THROW("May not use getRawValue on static properties"); + RETURN_THROWS(); + } + + if (!instanceof_function(Z_OBJCE_P(object), intern->ce)) { + _DO_THROW("Given object is not an instance of the class this property was declared in"); + RETURN_THROWS(); + } + + if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) { + zval rv; + zval *member_p = zend_read_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, 0, &rv); + + if (member_p != &rv) { + RETURN_COPY_DEREF(member_p); + } else { + if (Z_ISREF_P(member_p)) { + zend_unwrap_reference(member_p); + } + RETURN_COPY_VALUE(member_p); + } + } else { + zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_GET, ref->unmangled_name); + zend_call_known_instance_method_with_0_params(func, Z_OBJ_P(object), return_value); + } +} + +ZEND_METHOD(ReflectionProperty, setRawValue) +{ + reflection_object *intern; + property_reference *ref; + zval *object; + zval *value; + + GET_REFLECTION_OBJECT_PTR(ref); + + if (prop_get_flags(ref) & ZEND_ACC_STATIC) { + _DO_THROW("May not use setRawValue on static properties"); + RETURN_THROWS(); + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "oz", &object, &value) == FAILURE) { + RETURN_THROWS(); + } + + if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + zend_update_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, value); + } else { + zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_SET, ref->unmangled_name); + zend_call_known_instance_method_with_1_params(func, Z_OBJ_P(object), NULL, value); + } +} + /* {{{ Returns true if property was initialized */ ZEND_METHOD(ReflectionProperty, isInitialized) { @@ -5942,6 +6023,47 @@ ZEND_METHOD(ReflectionProperty, getType) } /* }}} */ +ZEND_METHOD(ReflectionProperty, getSettableType) +{ + reflection_object *intern; + property_reference *ref; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ref); + + zend_property_info *prop = ref->prop; + /* Dynamic property is untyped. */ + if (!ref->prop) { + RETURN_NULL(); + } + + /* Get-only virtual property can never be written to. */ + if ((prop->flags & ZEND_ACC_VIRTUAL) && !prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + zend_type never_type = ZEND_TYPE_INIT_CODE(IS_NEVER, 0, 0); + reflection_type_factory(never_type, return_value, 0); + return; + } + + /* Extract set $value parameter type. */ + if (prop->hooks && prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + zend_arg_info *arg_info = &prop->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0]; + if (!ZEND_TYPE_IS_SET(arg_info->type)) { + RETURN_NULL(); + } + reflection_type_factory(arg_info->type, return_value, 0); + return; + } + + /* Fall back to property type */ + if (!ZEND_TYPE_IS_SET(ref->prop->type)) { + RETURN_NULL(); + } + reflection_type_factory(ref->prop->type, return_value, 0); +} + /* {{{ Returns whether property has a type */ ZEND_METHOD(ReflectionProperty, hasType) { @@ -5979,7 +6101,7 @@ ZEND_METHOD(ReflectionProperty, hasDefaultValue) } prop = property_get_default(prop_info); - RETURN_BOOL(!Z_ISUNDEF_P(prop)); + RETURN_BOOL(prop && !Z_ISUNDEF_P(prop)); } /* }}} */ @@ -6004,7 +6126,7 @@ ZEND_METHOD(ReflectionProperty, getDefaultValue) } prop = property_get_default(prop_info); - if (Z_ISUNDEF_P(prop)) { + if (!prop || Z_ISUNDEF_P(prop)) { return; } @@ -6022,6 +6144,60 @@ ZEND_METHOD(ReflectionProperty, getDefaultValue) } /* }}} */ +ZEND_METHOD(ReflectionProperty, getHooks) +{ + reflection_object *intern; + property_reference *ref; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(ref); + + if (!ref->prop->hooks) { + RETURN_EMPTY_ARRAY(); + } + + array_init(return_value); + if (ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) { + zval hook_obj; + zend_function *hook = ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]; + reflection_method_factory(hook->common.scope, hook, NULL, &hook_obj); + zend_hash_update(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_GET), &hook_obj); + } + if (ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + zval hook_obj; + zend_function *hook = ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]; + reflection_method_factory(hook->common.scope, hook, NULL, &hook_obj); + zend_hash_update(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_SET), &hook_obj); + } +} + +ZEND_METHOD(ReflectionProperty, getHook) +{ + reflection_object *intern; + property_reference *ref; + zend_object *type; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJ_OF_CLASS(type, reflection_property_hook_type_ptr) + ZEND_PARSE_PARAMETERS_END(); + + GET_REFLECTION_OBJECT_PTR(ref); + + zend_function *hook; + if (zend_string_equals_literal(Z_STR_P(zend_enum_fetch_case_name(type)), "Get")) { + hook = ref->prop->hooks ? ref->prop->hooks[ZEND_PROPERTY_HOOK_GET] : NULL; + } else { + hook = ref->prop->hooks ? ref->prop->hooks[ZEND_PROPERTY_HOOK_SET] : NULL; + } + + if (!hook) { + RETURN_NULL(); + } + + reflection_method_factory(hook->common.scope, hook, NULL, return_value); +} + /* {{{ Constructor. Throws an Exception in case the given extension does not exist */ ZEND_METHOD(ReflectionExtension, __construct) { @@ -7355,6 +7531,8 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_constant_ptr->create_object = reflection_objects_new; reflection_constant_ptr->default_object_handlers = &reflection_object_handlers; + reflection_property_hook_type_ptr = register_class_PropertyHookType(); + REFLECTION_G(key_initialized) = 0; return SUCCESS; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index b487f545562c8..d6d9c9d715f4a 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -423,6 +423,12 @@ class ReflectionObject extends ReflectionClass public function __construct(object $object) {} } +enum PropertyHookType: string +{ + case Get = 'get'; + case Set = 'set'; +} + /** @not-serializable */ class ReflectionProperty implements Reflector { @@ -436,6 +442,8 @@ class ReflectionProperty implements Reflector public const int IS_PROTECTED = UNKNOWN; /** @cvalue ZEND_ACC_PRIVATE */ public const int IS_PRIVATE = UNKNOWN; + /** @cvalue ZEND_ACC_ABSTRACT */ + public const int IS_ABSTRACT = UNKNOWN; public string $name; public string $class; @@ -456,6 +464,10 @@ public function getValue(?object $object = null): mixed {} /** @tentative-return-type */ public function setValue(mixed $objectOrValue, mixed $value = UNKNOWN): void {} + public function getRawValue(object $object): mixed {} + + public function setRawValue(object $object, mixed $value): void {} + /** @tentative-return-type */ public function isInitialized(?object $object = null): bool {} @@ -476,6 +488,10 @@ public function isReadOnly(): bool {} /** @tentative-return-type */ public function isDefault(): bool {} + public function isAbstract(): bool {} + + public function isVirtual(): bool {} + public function isPromoted(): bool {} /** @tentative-return-type */ @@ -493,6 +509,8 @@ public function setAccessible(bool $accessible): void {} /** @tentative-return-type */ public function getType(): ?ReflectionType {} + public function getSettableType(): ?ReflectionType {} + /** @tentative-return-type */ public function hasType(): bool {} @@ -502,6 +520,11 @@ public function hasDefaultValue(): bool {} public function getDefaultValue(): mixed {} public function getAttributes(?string $name = null, int $flags = 0): array {} + + /** @return array */ + public function getHooks(): array {} + + public function getHook(PropertyHookType $type): ?ReflectionMethod {} } /** @not-serializable */ diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 4a69d73fa907d..a35bdd766ef7b 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 9658aab117448d2a2337c749d8c8ccd5835321a6 */ + * Stub hash: d2a365f6f398bbcf3f2520c28d508ca63f08b5ff */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -352,6 +352,15 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProper ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_getRawValue, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_setRawValue, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 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_ReflectionProperty_isInitialized, 0, 0, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, object, IS_OBJECT, 1, "null") ZEND_END_ARG_INFO() @@ -368,6 +377,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionProperty_isAbstract arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + +#define arginfo_class_ReflectionProperty_isVirtual arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + #define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType #define arginfo_class_ReflectionProperty_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters @@ -380,6 +393,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_getType arginfo_class_ReflectionFunctionAbstract_getReturnType +#define arginfo_class_ReflectionProperty_getSettableType arginfo_class_ReflectionFunctionAbstract_getTentativeReturnType + #define arginfo_class_ReflectionProperty_hasType arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionProperty_hasDefaultValue arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType @@ -389,6 +404,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes +#define arginfo_class_ReflectionProperty_getHooks arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionProperty_getHook, 0, 1, ReflectionMethod, 1) + ZEND_ARG_OBJ_INFO(0, type, PropertyHookType, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionClassConstant___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionClassConstant___construct, 0, 0, 2) @@ -758,6 +779,8 @@ ZEND_METHOD(ReflectionProperty, __toString); ZEND_METHOD(ReflectionProperty, getName); ZEND_METHOD(ReflectionProperty, getValue); ZEND_METHOD(ReflectionProperty, setValue); +ZEND_METHOD(ReflectionProperty, getRawValue); +ZEND_METHOD(ReflectionProperty, setRawValue); ZEND_METHOD(ReflectionProperty, isInitialized); ZEND_METHOD(ReflectionProperty, isPublic); ZEND_METHOD(ReflectionProperty, isPrivate); @@ -765,16 +788,21 @@ ZEND_METHOD(ReflectionProperty, isProtected); ZEND_METHOD(ReflectionProperty, isStatic); ZEND_METHOD(ReflectionProperty, isReadOnly); ZEND_METHOD(ReflectionProperty, isDefault); +ZEND_METHOD(ReflectionProperty, isAbstract); +ZEND_METHOD(ReflectionProperty, isVirtual); ZEND_METHOD(ReflectionProperty, isPromoted); ZEND_METHOD(ReflectionProperty, getModifiers); ZEND_METHOD(ReflectionProperty, getDeclaringClass); ZEND_METHOD(ReflectionProperty, getDocComment); ZEND_METHOD(ReflectionProperty, setAccessible); ZEND_METHOD(ReflectionProperty, getType); +ZEND_METHOD(ReflectionProperty, getSettableType); ZEND_METHOD(ReflectionProperty, hasType); ZEND_METHOD(ReflectionProperty, hasDefaultValue); ZEND_METHOD(ReflectionProperty, getDefaultValue); ZEND_METHOD(ReflectionProperty, getAttributes); +ZEND_METHOD(ReflectionProperty, getHooks); +ZEND_METHOD(ReflectionProperty, getHook); ZEND_METHOD(ReflectionClassConstant, __construct); ZEND_METHOD(ReflectionClassConstant, __toString); ZEND_METHOD(ReflectionClassConstant, getName); @@ -1033,6 +1061,10 @@ static const zend_function_entry class_ReflectionObject_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_PropertyHookType_methods[] = { + ZEND_FE_END +}; + static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_RAW_FENTRY("__clone", zim_ReflectionClass___clone, arginfo_class_ReflectionProperty___clone, ZEND_ACC_PRIVATE, NULL, NULL) ZEND_ME(ReflectionProperty, __construct, arginfo_class_ReflectionProperty___construct, ZEND_ACC_PUBLIC) @@ -1040,6 +1072,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, getName, arginfo_class_ReflectionProperty_getName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getValue, arginfo_class_ReflectionProperty_getValue, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, setValue, arginfo_class_ReflectionProperty_setValue, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, getRawValue, arginfo_class_ReflectionProperty_getRawValue, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, setRawValue, arginfo_class_ReflectionProperty_setRawValue, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isInitialized, arginfo_class_ReflectionProperty_isInitialized, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPublic, arginfo_class_ReflectionProperty_isPublic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPrivate, arginfo_class_ReflectionProperty_isPrivate, ZEND_ACC_PUBLIC) @@ -1047,16 +1081,21 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, isStatic, arginfo_class_ReflectionProperty_isStatic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isReadOnly, arginfo_class_ReflectionProperty_isReadOnly, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isDefault, arginfo_class_ReflectionProperty_isDefault, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isAbstract, arginfo_class_ReflectionProperty_isAbstract, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isVirtual, arginfo_class_ReflectionProperty_isVirtual, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPromoted, arginfo_class_ReflectionProperty_isPromoted, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getModifiers, arginfo_class_ReflectionProperty_getModifiers, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getDeclaringClass, arginfo_class_ReflectionProperty_getDeclaringClass, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getDocComment, arginfo_class_ReflectionProperty_getDocComment, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, setAccessible, arginfo_class_ReflectionProperty_setAccessible, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getType, arginfo_class_ReflectionProperty_getType, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, getSettableType, arginfo_class_ReflectionProperty_getSettableType, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, hasType, arginfo_class_ReflectionProperty_hasType, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, hasDefaultValue, arginfo_class_ReflectionProperty_hasDefaultValue, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getDefaultValue, arginfo_class_ReflectionProperty_getDefaultValue, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getAttributes, arginfo_class_ReflectionProperty_getAttributes, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, getHooks, arginfo_class_ReflectionProperty_getHooks, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, getHook, arginfo_class_ReflectionProperty_getHook, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -1418,6 +1457,23 @@ static zend_class_entry *register_class_ReflectionObject(zend_class_entry *class return class_entry; } +static zend_class_entry *register_class_PropertyHookType(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum("PropertyHookType", IS_STRING, class_PropertyHookType_methods); + + zval enum_case_Get_value; + zend_string *enum_case_Get_value_str = zend_string_init("get", strlen("get"), 1); + ZVAL_STR(&enum_case_Get_value, enum_case_Get_value_str); + zend_enum_add_case_cstr(class_entry, "Get", &enum_case_Get_value); + + zval enum_case_Set_value; + zend_string *enum_case_Set_value_str = zend_string_init("set", strlen("set"), 1); + ZVAL_STR(&enum_case_Set_value, enum_case_Set_value_str); + zend_enum_add_case_cstr(class_entry, "Set", &enum_case_Set_value); + + return class_entry; +} + static zend_class_entry *register_class_ReflectionProperty(zend_class_entry *class_entry_Reflector) { zend_class_entry ce, *class_entry; @@ -1457,6 +1513,12 @@ static zend_class_entry *register_class_ReflectionProperty(zend_class_entry *cla zend_declare_typed_class_constant(class_entry, const_IS_PRIVATE_name, &const_IS_PRIVATE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(const_IS_PRIVATE_name); + zval const_IS_ABSTRACT_value; + ZVAL_LONG(&const_IS_ABSTRACT_value, ZEND_ACC_ABSTRACT); + zend_string *const_IS_ABSTRACT_name = zend_string_init_interned("IS_ABSTRACT", sizeof("IS_ABSTRACT") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_IS_ABSTRACT_name, &const_IS_ABSTRACT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_IS_ABSTRACT_name); + zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index b7537f170a502..8ba243a503bd0 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -7,131 +7,136 @@ Thijs Lensselink $ext = new ReflectionExtension('reflection'); var_dump($ext->getClasses()); ?> ---EXPECT-- -array(25) { +--EXPECTF-- +array(26) { ["ReflectionException"]=> - object(ReflectionClass)#2 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(19) "ReflectionException" } ["Reflection"]=> - object(ReflectionClass)#3 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(10) "Reflection" } ["Reflector"]=> - object(ReflectionClass)#4 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(9) "Reflector" } ["ReflectionFunctionAbstract"]=> - object(ReflectionClass)#5 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(26) "ReflectionFunctionAbstract" } ["ReflectionFunction"]=> - object(ReflectionClass)#6 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(18) "ReflectionFunction" } ["ReflectionGenerator"]=> - object(ReflectionClass)#7 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(19) "ReflectionGenerator" } ["ReflectionParameter"]=> - object(ReflectionClass)#8 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(19) "ReflectionParameter" } ["ReflectionType"]=> - object(ReflectionClass)#9 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(14) "ReflectionType" } ["ReflectionNamedType"]=> - object(ReflectionClass)#10 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(19) "ReflectionNamedType" } ["ReflectionUnionType"]=> - object(ReflectionClass)#11 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(19) "ReflectionUnionType" } ["ReflectionIntersectionType"]=> - object(ReflectionClass)#12 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(26) "ReflectionIntersectionType" } ["ReflectionMethod"]=> - object(ReflectionClass)#13 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(16) "ReflectionMethod" } ["ReflectionClass"]=> - object(ReflectionClass)#14 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(15) "ReflectionClass" } ["ReflectionObject"]=> - object(ReflectionClass)#15 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(16) "ReflectionObject" } ["ReflectionProperty"]=> - object(ReflectionClass)#16 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(18) "ReflectionProperty" } ["ReflectionClassConstant"]=> - object(ReflectionClass)#17 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(23) "ReflectionClassConstant" } ["ReflectionExtension"]=> - object(ReflectionClass)#18 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(19) "ReflectionExtension" } ["ReflectionZendExtension"]=> - object(ReflectionClass)#19 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(23) "ReflectionZendExtension" } ["ReflectionReference"]=> - object(ReflectionClass)#20 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(19) "ReflectionReference" } ["ReflectionAttribute"]=> - object(ReflectionClass)#21 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(19) "ReflectionAttribute" } ["ReflectionEnum"]=> - object(ReflectionClass)#22 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(14) "ReflectionEnum" } ["ReflectionEnumUnitCase"]=> - object(ReflectionClass)#23 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(22) "ReflectionEnumUnitCase" } ["ReflectionEnumBackedCase"]=> - object(ReflectionClass)#24 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(24) "ReflectionEnumBackedCase" } ["ReflectionFiber"]=> - object(ReflectionClass)#25 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(15) "ReflectionFiber" } ["ReflectionConstant"]=> - object(ReflectionClass)#26 (1) { + object(ReflectionClass)#%d (1) { ["name"]=> string(18) "ReflectionConstant" } + ["PropertyHookType"]=> + object(ReflectionEnum)#%d (1) { + ["name"]=> + string(16) "PropertyHookType" + } } diff --git a/ext/reflection/tests/ReflectionParameter_getSettableType.phpt b/ext/reflection/tests/ReflectionParameter_getSettableType.phpt new file mode 100644 index 0000000000000..caddaeb1ddfdb --- /dev/null +++ b/ext/reflection/tests/ReflectionParameter_getSettableType.phpt @@ -0,0 +1,55 @@ +--TEST-- +ReflectionProperty::getSettableType() +--FILE-- + 42; + } + + public int $virtualReadOnlyTyped { + get => 42; + } + + public int $asymmetricVirtualTyped { + get => 42; + set(int|string $value) {} + } + + public $backedUntyped { + get => $this->backedUntyped; + set => $value; + } + + public int $backedTyped { + get => $this->backedTyped; + set => $value; + } + + public int $asymmetricBackedTyped { + get => $this->backedTyped; + set(int|string $value) => (int) $value; + } +} + +$reflectionClass = new ReflectionClass(Test::class); +foreach ($reflectionClass->getProperties() as $reflectionProperty) { + $type = $reflectionProperty->getSettableType(); + echo ($type ? $type : "NULL") . "\n"; +} + +?> +--EXPECT-- +NULL +int +never +never +string|int +NULL +int +string|int diff --git a/ext/reflection/tests/property_hooks/ReflectionClass_getMethods.phpt b/ext/reflection/tests/property_hooks/ReflectionClass_getMethods.phpt new file mode 100644 index 0000000000000..c22c92016a4d3 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionClass_getMethods.phpt @@ -0,0 +1,15 @@ +--TEST-- +ReflectionClass::getMethods() does not contain property hooks +--FILE-- +getMethods()); + +?> +--EXPECT-- +array(0) { +} diff --git a/ext/reflection/tests/property_hooks/ReflectionProperty_getHook_inheritance.phpt b/ext/reflection/tests/property_hooks/ReflectionProperty_getHook_inheritance.phpt new file mode 100644 index 0000000000000..820f83f025dc1 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionProperty_getHook_inheritance.phpt @@ -0,0 +1,43 @@ +--TEST-- +ReflectionClass::get{Get,Set}() inheritance +--FILE-- +getHook(PropertyHookType::Get)->invoke($a)), "\n"; +echo ((new ReflectionProperty(A::class, 'foo'))->getHook(PropertyHookType::Get)->invoke($b)), "\n"; +echo ((new ReflectionProperty(B::class, 'foo'))->getHook(PropertyHookType::Get)->invoke($b)), "\n"; + +((new ReflectionProperty(A::class, 'foo'))->getHook(PropertyHookType::Set)->invoke($a, null)); +((new ReflectionProperty(A::class, 'foo'))->getHook(PropertyHookType::Set)->invoke($b, null)); +((new ReflectionProperty(B::class, 'foo'))->getHook(PropertyHookType::Set)->invoke($b, null)); + +?> +--EXPECT-- +A::$foo::get +A::$foo::get +B::$foo +A::$foo::set +A::$foo::set +A::$foo::set diff --git a/ext/reflection/tests/property_hooks/ReflectionProperty_getHooks.phpt b/ext/reflection/tests/property_hooks/ReflectionProperty_getHooks.phpt new file mode 100644 index 0000000000000..ab6c0ea291be1 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionProperty_getHooks.phpt @@ -0,0 +1,55 @@ +--TEST-- +ReflectionProperty::getHooks() +--FILE-- +getHooks()); +} + +?> +--EXPECT-- +array(0) { +} +array(2) { + ["get"]=> + object(ReflectionMethod)#1 (2) { + ["name"]=> + string(11) "$prop2::get" + ["class"]=> + string(4) "Test" + } + ["set"]=> + object(ReflectionMethod)#3 (2) { + ["name"]=> + string(11) "$prop2::set" + ["class"]=> + string(4) "Test" + } +} +array(1) { + ["get"]=> + object(ReflectionMethod)#2 (2) { + ["name"]=> + string(11) "$prop3::get" + ["class"]=> + string(4) "Test" + } +} +array(1) { + ["set"]=> + object(ReflectionMethod)#3 (2) { + ["name"]=> + string(11) "$prop4::set" + ["class"]=> + string(4) "Test" + } +} diff --git a/ext/reflection/tests/property_hooks/ReflectionProperty_getSetRawValue.phpt b/ext/reflection/tests/property_hooks/ReflectionProperty_getSetRawValue.phpt new file mode 100644 index 0000000000000..bae5608bb44b7 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionProperty_getSetRawValue.phpt @@ -0,0 +1,76 @@ +--TEST-- +ReflectionProperty::{get,set}RawValue() does not invoke hooks +--FILE-- +publicProp; + } + set { + echo __FUNCTION__, "\n"; + $field = $value; + } + } + public $privateProp { + get { + echo __FUNCTION__, "\n"; + return $this->privateProp; + } + set { + echo __FUNCTION__, "\n"; + $field = $value; + } + } + public $virtualProp { + get { + echo __FUNCTION__, "\n"; + return 42; + } + set { + echo __FUNCTION__, "\n"; + } + } + private $plainProp; + + public function __get($name) { + echo __FUNCTION__, "\n"; + return 42; + } +} + +class Unguarded { + private $plainProp; +} + +function test($class, $prop) { + $propertyReflection = (new ReflectionProperty($class, $prop)); + $object = new $class(); + try { + $propertyReflection->setRawValue($object, 42); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump($propertyReflection->getRawValue($object)); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +} + +test(Test::class, 'publicProp'); +test(Test::class, 'privateProp'); +test(Test::class, 'virtualProp'); +test(Test::class, 'plainProp'); +test(Unguarded::class, 'plainProp'); + +?> +--EXPECT-- +int(42) +int(42) +Must not write to virtual property Test::$virtualProp +Must not read from virtual property Test::$virtualProp +int(42) +int(42) diff --git a/ext/reflection/tests/property_hooks/ReflectionProperty_getSetValue.phpt b/ext/reflection/tests/property_hooks/ReflectionProperty_getSetValue.phpt new file mode 100644 index 0000000000000..9daca77330680 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionProperty_getSetValue.phpt @@ -0,0 +1,28 @@ +--TEST-- +ReflectionProperty::{get,set}Value() invokes hooks +--FILE-- +getValue($test)); +$propertyReflection->setValue($test, 'Baz'); + +?> +--EXPECT-- +Get called +string(3) "Foo" +Set called with value Baz diff --git a/ext/reflection/tests/property_hooks/basics.phpt b/ext/reflection/tests/property_hooks/basics.phpt new file mode 100644 index 0000000000000..92a613bcb8042 --- /dev/null +++ b/ext/reflection/tests/property_hooks/basics.phpt @@ -0,0 +1,99 @@ +--TEST-- +Property hook reflection +--FILE-- +getModifiers(); + echo "Abstract: "; + echo $rp->isAbstract() ? "true" : "false"; + echo " "; + echo $modifiers & ReflectionProperty::IS_ABSTRACT ? "true" : "false"; + echo "\n"; +} + +$test = new Test; + +$rp1 = new ReflectionProperty(Test::class, 'prop1'); +var_dump($rp1->getHook(PropertyHookType::Get)); +var_dump($rp1->getHook(PropertyHookType::Set)); +dumpFlags($rp1); +echo "\n"; + +$rp2 = new ReflectionProperty(Test::class, 'prop2'); +var_dump($g = $rp2->getHook(PropertyHookType::Get)); +var_dump($s = $rp2->getHook(PropertyHookType::Set)); +var_dump($g->invoke($test)); +try { + $s->invoke($test, 42); +} catch (ReflectionException $e) { + echo $e->getMessage(), "\n"; +} +$s->setAccessible(true); +$s->invoke($test, 42); +var_dump($test->prop2); +dumpFlags($rp2); +echo "\n"; + +$rp3 = new ReflectionProperty(Test::class, 'prop3'); +var_dump($g = $rp3->getHook(PropertyHookType::Get)); +var_dump($s = $rp3->getHook(PropertyHookType::Set)); +$g->invoke($test); +$s->invoke($test, 42); +dumpFlags($rp3); +echo "\n"; + +$rp4 = new ReflectionProperty(Test2::class, 'prop4'); +dumpFlags($rp4); + +?> +--EXPECT-- +NULL +NULL +Abstract: false false + +object(ReflectionMethod)#6 (2) { + ["name"]=> + string(11) "$prop2::get" + ["class"]=> + string(4) "Test" +} +object(ReflectionMethod)#7 (2) { + ["name"]=> + string(11) "$prop2::set" + ["class"]=> + string(4) "Test" +} +NULL +NULL +Abstract: false false + +object(ReflectionMethod)#9 (2) { + ["name"]=> + string(11) "$prop3::get" + ["class"]=> + string(4) "Test" +} +object(ReflectionMethod)#6 (2) { + ["name"]=> + string(11) "$prop3::set" + ["class"]=> + string(4) "Test" +} +get +set(42) +Abstract: false false + +Abstract: true true diff --git a/ext/reflection/tests/property_hooks/bug_001.phpt b/ext/reflection/tests/property_hooks/bug_001.phpt new file mode 100644 index 0000000000000..75973ac4c731a --- /dev/null +++ b/ext/reflection/tests/property_hooks/bug_001.phpt @@ -0,0 +1,25 @@ +--TEST-- +ReflectionProperty::{get,set}RawValue() crashes on dynamic properties +--FILE-- +dyn = 1; + +$prop = new ReflectionProperty($b, 'dyn'); +var_dump($prop->getRawValue($a)); +$prop->setRawValue($a, 1); + +?> +--EXPECTF-- +Deprecated: Creation of dynamic property A::$dyn is deprecated in %s on line %d + +Warning: Undefined property: A::$dyn in %s on line %d +NULL + +Deprecated: Creation of dynamic property A::$dyn is deprecated in %s on line %d diff --git a/ext/reflection/tests/property_hooks/hook_guard.phpt b/ext/reflection/tests/property_hooks/hook_guard.phpt new file mode 100644 index 0000000000000..901073aab848c --- /dev/null +++ b/ext/reflection/tests/property_hooks/hook_guard.phpt @@ -0,0 +1,31 @@ +--TEST-- +Calling hook method with parent::$prop::get() from reflection +--FILE-- +getHook(PropertyHookType::Set)->invoke($b, 43); +var_dump((new ReflectionProperty(B::class, 'prop'))->getHook(PropertyHookType::Get)->invoke($b)); + +?> +--EXPECT-- +$prop::set +$prop::get +int(43) diff --git a/ext/standard/php_var.h b/ext/standard/php_var.h index b816da8e19de9..d70bbfed814a0 100644 --- a/ext/standard/php_var.h +++ b/ext/standard/php_var.h @@ -24,7 +24,7 @@ PHP_MINIT_FUNCTION(var); PHPAPI void php_var_dump(zval *struc, int level); PHPAPI void php_var_export(zval *struc, int level); -PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf); +PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf); PHPAPI void php_debug_zval_dump(zval *struc, int level); diff --git a/ext/standard/tests/serialize/bug68594.phpt b/ext/standard/tests/serialize/bug68594.phpt index 69ff9e2bf4cd3..e2233187be9d1 100644 --- a/ext/standard/tests/serialize/bug68594.phpt +++ b/ext/standard/tests/serialize/bug68594.phpt @@ -9,7 +9,7 @@ for ($i=4; $i<100; $i++) { $m->aaa = array(1,2,&$u,4,5); $m->bbb = 1; - $m->ccc = &$u; + // $m->ccc = &$u; $m->ddd = str_repeat("A", $i); $z = serialize($m); diff --git a/ext/standard/var.c b/ext/standard/var.c index 85546320b4cae..dd2bd86d91ee8 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -432,7 +432,7 @@ PHP_FUNCTION(debug_zval_dump) efree(tmp_spaces); \ } while(0); -static void php_array_element_export(zval *zv, zend_ulong index, zend_string *key, int level, smart_str *buf) /* {{{ */ +static zend_result php_array_element_export(zval *zv, zend_ulong index, zend_string *key, int level, smart_str *buf) /* {{{ */ { if (key == NULL) { /* numeric key */ buffer_append_spaces(buf, level+1); @@ -453,14 +453,16 @@ static void php_array_element_export(zval *zv, zend_ulong index, zend_string *ke zend_string_free(ckey); zend_string_free(tmp_str); } - php_var_export_ex(zv, level + 2, buf); + zend_result result = php_var_export_ex(zv, level + 2, buf); smart_str_appendc(buf, ','); smart_str_appendc(buf, '\n'); + + return result; } /* }}} */ -static void php_object_element_export(zval *zv, zend_ulong index, zend_string *key, int level, smart_str *buf) /* {{{ */ +static zend_result php_object_element_export(zval *zv, zend_ulong index, zend_string *key, int level, smart_str *buf) /* {{{ */ { buffer_append_spaces(buf, level + 2); if (key != NULL) { @@ -479,13 +481,15 @@ static void php_object_element_export(zval *zv, zend_ulong index, zend_string *k smart_str_append_long(buf, (zend_long) index); } smart_str_appendl(buf, " => ", 4); - php_var_export_ex(zv, level + 2, buf); + zend_result result = php_var_export_ex(zv, level + 2, buf); smart_str_appendc(buf, ','); smart_str_appendc(buf, '\n'); + + return result; } /* }}} */ -PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ +PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ { HashTable *myht; zend_string *ztmp, *ztmp2; @@ -535,7 +539,7 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ if (GC_IS_RECURSIVE(myht)) { smart_str_appendl(buf, "NULL", 4); zend_error(E_WARNING, "var_export does not handle circular references"); - return; + return SUCCESS; } GC_ADDREF(myht); GC_PROTECT_RECURSION(myht); @@ -546,7 +550,13 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ } smart_str_appendl(buf, "array (\n", 8); ZEND_HASH_FOREACH_KEY_VAL(myht, index, key, val) { - php_array_element_export(val, index, key, level, buf); + if (php_array_element_export(val, index, key, level, buf) == FAILURE) { + if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { + GC_UNPROTECT_RECURSION(myht); + GC_DELREF(myht); + } + return FAILURE; + } } ZEND_HASH_FOREACH_END(); if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { GC_UNPROTECT_RECURSION(myht); @@ -569,7 +579,7 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, EXPORT, zobj)) { smart_str_appendl(buf, "NULL", 4); zend_error(E_WARNING, "var_export does not handle circular references"); - return; + return SUCCESS; } ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, EXPORT, zobj); myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT); @@ -600,7 +610,27 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ if (myht) { if (!is_enum) { ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, val) { + /* data is IS_PTR for properties with hooks. */ + zval tmp; + if (UNEXPECTED(Z_TYPE_P(val) == IS_PTR)) { + zend_property_info *prop_info = Z_PTR_P(val); + if ((prop_info->flags & ZEND_ACC_VIRTUAL) && !prop_info->hooks[ZEND_PROPERTY_HOOK_GET]) { + continue; + } + const char *unmangled_name_cstr = zend_get_unmangled_property_name(prop_info->name); + zend_string *unmangled_name = zend_string_init(unmangled_name_cstr, strlen(unmangled_name_cstr), false); + val = zend_read_property_ex(prop_info->ce, zobj, unmangled_name, /* silent */ true, &tmp); + zend_string_release_ex(unmangled_name, false); + if (EG(exception)) { + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj); + zend_release_properties(myht); + return FAILURE; + } + } php_object_element_export(val, index, key, level, buf); + if (val == &tmp) { + zval_ptr_dtor(val); + } } ZEND_HASH_FOREACH_END(); } zend_release_properties(myht); @@ -625,6 +655,8 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ smart_str_appendl(buf, "NULL", 4); break; } + + return SUCCESS; } /* }}} */ @@ -632,9 +664,11 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ PHPAPI void php_var_export(zval *struc, int level) /* {{{ */ { smart_str buf = {0}; - php_var_export_ex(struc, level, &buf); + zend_result result = php_var_export_ex(struc, level, &buf); smart_str_0(&buf); - PHPWRITE(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)); + if (result == SUCCESS) { + PHPWRITE(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)); + } smart_str_free(&buf); } /* }}} */ @@ -652,10 +686,12 @@ PHP_FUNCTION(var_export) Z_PARAM_BOOL(return_output) ZEND_PARSE_PARAMETERS_END(); - php_var_export_ex(var, 1, &buf); + zend_result result = php_var_export_ex(var, 1, &buf); smart_str_0 (&buf); - if (return_output) { + if (result == FAILURE) { + smart_str_free(&buf); + } else if (return_output) { RETURN_STR(smart_str_extract(&buf)); } else { PHPWRITE(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)); diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index d16566a073ff1..69dd3f84d3a7e 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -534,7 +534,7 @@ failure: static int is_property_visibility_changed(zend_class_entry *ce, zval *key) { if (zend_hash_num_elements(&ce->properties_info) > 0) { - zend_property_info *existing_propinfo; + zend_property_info *existing_propinfo = NULL; const char *unmangled_class = NULL; const char *unmangled_prop; size_t unmangled_prop_len; @@ -546,21 +546,25 @@ static int is_property_visibility_changed(zend_class_entry *ce, zval *key) if (unmangled_class == NULL) { existing_propinfo = zend_hash_find_ptr(&ce->properties_info, Z_STR_P(key)); - if (existing_propinfo != NULL) { - zval_ptr_dtor_str(key); - ZVAL_STR_COPY(key, existing_propinfo->name); - return 1; - } } else { if (!strcmp(unmangled_class, "*") || !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))) { existing_propinfo = zend_hash_str_find_ptr( &ce->properties_info, unmangled_prop, unmangled_prop_len); - if (existing_propinfo != NULL) { - zval_ptr_dtor_str(key); - ZVAL_STR_COPY(key, existing_propinfo->name); - return 1; - } + } + } + + if (existing_propinfo != NULL) { + if (!(existing_propinfo->flags & ZEND_ACC_VIRTUAL)) { + zval_ptr_dtor_str(key); + ZVAL_STR_COPY(key, existing_propinfo->name); + return 1; + } else { + php_error_docref(NULL, E_WARNING, + "Cannot unserialize value for hooked property %s::$%s", + ZSTR_VAL(existing_propinfo->ce->name), Z_STRVAL_P(key)); + zval_ptr_dtor_str(key); + return -1; } } } diff --git a/ext/tokenizer/tests/001.phpt b/ext/tokenizer/tests/001.phpt index 52eb469a970f8..15bc8cf8a3cfb 100644 --- a/ext/tokenizer/tests/001.phpt +++ b/ext/tokenizer/tests/001.phpt @@ -99,6 +99,7 @@ echo token_name(T_LIST), "\n"; echo token_name(T_ARRAY), "\n"; echo token_name(T_CLASS_C), "\n"; echo token_name(T_FUNC_C), "\n"; +echo token_name(T_PROPERTY_C), "\n"; echo token_name(T_METHOD_C), "\n"; echo token_name(T_LINE), "\n"; echo token_name(T_FILE), "\n"; @@ -223,6 +224,7 @@ T_LIST T_ARRAY T_CLASS_C T_FUNC_C +T_PROPERTY_C T_METHOD_C T_LINE T_FILE diff --git a/ext/tokenizer/tests/token_get_all_variation12.phpt b/ext/tokenizer/tests/token_get_all_variation12.phpt index 44f7673c7894c..fc9224ba63972 100644 --- a/ext/tokenizer/tests/token_get_all_variation12.phpt +++ b/ext/tokenizer/tests/token_get_all_variation12.phpt @@ -10,6 +10,7 @@ tokenizer * __CLASS__ - T_CLASS_C * __TRAIT__ - T_TRAIT_C * __FUNCTION__ - T_FUNC_C + * __PROPERTY__ - T_PROPERTY_C * __LINE__ - T_LINE * __METHOD__ - T_METHOD_C */ diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index e1fa407532f9a..cdaaddecd7bfa 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -115,6 +115,7 @@ char *get_token_type_name(int token_type) case T_TRAIT_C: return "T_TRAIT_C"; case T_METHOD_C: return "T_METHOD_C"; case T_FUNC_C: return "T_FUNC_C"; + case T_PROPERTY_C: return "T_PROPERTY_C"; case T_NS_C: return "T_NS_C"; case T_ATTRIBUTE: return "T_ATTRIBUTE"; case T_PLUS_EQUAL: return "T_PLUS_EQUAL"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 42c69c4f82eff..81e4e92626f37 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -452,6 +452,11 @@ * @cvalue T_FUNC_C */ const T_FUNC_C = UNKNOWN; +/** + * @var int + * @cvalue T_PROPERTY_C + */ +const T_PROPERTY_C = UNKNOWN; /** * @var int * @cvalue T_NS_C diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 4363a46720afd..0fc1f219619dd 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1dd42ee5b5b818c5bd131b5c4bbb13c153d99499 */ + * Stub hash: b7a13b3b242bd535f62a7d819eb0df751efb54ed */ static void register_tokenizer_data_symbols(int module_number) { @@ -93,6 +93,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_TRAIT_C", T_TRAIT_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_METHOD_C", T_METHOD_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_FUNC_C", T_FUNC_C, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_PROPERTY_C", T_PROPERTY_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NS_C", T_NS_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ATTRIBUTE", T_ATTRIBUTE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PLUS_EQUAL", T_PLUS_EQUAL, CONST_PERSISTENT); diff --git a/tests/classes/interface_member.phpt b/tests/classes/interface_member.phpt index 04fff625f3aa4..78f329f221502 100644 --- a/tests/classes/interface_member.phpt +++ b/tests/classes/interface_member.phpt @@ -8,4 +8,4 @@ interface if_a { } ?> --EXPECTF-- -Fatal error: Interfaces may not include properties in %s on line %d +Fatal error: Interfaces may only include hooked properties in %s on line %d diff --git a/win32/build/config.w32 b/win32/build/config.w32 index ae9b6146b5f06..c4a617a2e16ec 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -240,7 +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_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c"); + zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.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"); var PHP_ASSEMBLER = PATH_PROG({