From fd951b92513ebcc7265e00f4276ff5ab24869b01 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 22 Mar 2021 15:18:09 +0100 Subject: [PATCH 01/44] Basic accessors implementation --- Zend/tests/accessors/abstract.phpt | 15 + Zend/tests/accessors/abstract_prop.phpt | 17 + .../abstract_prop_abstract_accessor.phpt | 12 + .../abstract_prop_not_implemented.phpt | 15 + Zend/tests/accessors/auto.phpt | 34 + Zend/tests/accessors/backing_prop.phpt | 24 + Zend/tests/accessors/basic.phpt | 17 + Zend/tests/accessors/cache.phpt | 54 + Zend/tests/accessors/duplicate_accessor.phpt | 15 + Zend/tests/accessors/final.phpt | 20 + Zend/tests/accessors/final_prop.phpt | 16 + Zend/tests/accessors/final_prop_2.phpt | 16 + Zend/tests/accessors/get.phpt | 24 + Zend/tests/accessors/get_by_ref.phpt | 41 + Zend/tests/accessors/get_invalid_params.phpt | 14 + .../accessors/get_invalid_return_type.phpt | 14 + Zend/tests/accessors/get_type_check.phpt | 26 + Zend/tests/accessors/inheritance.phpt | 32 + .../inheritance_invalid_visibility.phpt | 22 + .../accessors/inheritance_ref_mismatch.phpt | 15 + Zend/tests/accessors/interface.phpt | 15 + ...interface_invalid_explicitly_abstract.phpt | 12 + .../accessors/interface_not_implemented.phpt | 15 + .../tests/accessors/interface_not_public.phpt | 15 + .../accessors/interface_not_public_2.phpt | 15 + Zend/tests/accessors/invalid_abstract.phpt | 41 + .../accessors/invalid_abstract_body.phpt | 14 + .../accessors/invalid_abstract_final.phpt | 12 + .../accessors/invalid_abstract_indirect.phpt | 16 + .../invalid_abstract_indirect_2.phpt | 15 + .../accessors/invalid_abstract_private.phpt | 14 + .../accessors/invalid_empty_accessors.phpt | 12 + .../accessors/invalid_final_private.phpt | 12 + .../invalid_increasing_visibility.phpt | 14 + .../accessors/invalid_plain_to_accessor.phpt | 15 + .../accessors/invalid_same_visibility.phpt | 14 + Zend/tests/accessors/invalid_static.phpt | 14 + Zend/tests/accessors/isset.phpt | 78 + Zend/tests/accessors/magic_consts.phpt | 24 + Zend/tests/accessors/magic_interaction.phpt | 63 + .../accessors/override_by_plain_prop.phpt | 30 + Zend/tests/accessors/private_override.phpt | 92 + .../accessors/private_override_auto.phpt | 36 + Zend/tests/accessors/set.phpt | 32 + .../accessors/set_invalid_by_ref_return.phpt | 12 + .../tests/accessors/set_invalid_params_1.phpt | 14 + .../tests/accessors/set_invalid_params_2.phpt | 14 + .../tests/accessors/set_invalid_params_3.phpt | 14 + .../tests/accessors/set_invalid_params_4.phpt | 14 + .../tests/accessors/set_invalid_params_5.phpt | 14 + Zend/tests/accessors/traits.phpt | 24 + Zend/tests/accessors/traits_abstract.phpt | 16 + Zend/tests/accessors/traits_conflict.phpt | 24 + Zend/tests/accessors/type_compatibility.phpt | 17 + .../accessors/type_compatibility_invalid.phpt | 15 + .../type_compatibility_invalid_2.phpt | 15 + Zend/tests/accessors/unknown_accessor.phpt | 14 + Zend/tests/accessors/unset.phpt | 25 + Zend/tests/accessors/visibility.phpt | 160 ++ ...et_syntax_compile_error_in_const_expr.phpt | 2 +- ...ntax_compile_error_outside_const_expr.phpt | 2 +- Zend/tests/errmsg_037.phpt | 2 +- Zend/tests/errmsg_038.phpt | 13 - Zend/zend_API.c | 1 + Zend/zend_ast.h | 3 +- Zend/zend_compile.c | 219 ++- Zend/zend_compile.h | 24 +- Zend/zend_execute.c | 27 +- Zend/zend_inheritance.c | 197 ++- Zend/zend_language_parser.y | 50 +- Zend/zend_object_handlers.c | 217 ++- Zend/zend_object_handlers.h | 3 +- Zend/zend_opcode.c | 9 + Zend/zend_vm_def.h | 115 +- Zend/zend_vm_execute.h | 1500 ++++++++++------- ext/opcache/zend_persist.c | 52 +- ext/opcache/zend_persist_calc.c | 20 +- tests/classes/interface_member.phpt | 2 +- 78 files changed, 3112 insertions(+), 801 deletions(-) create mode 100644 Zend/tests/accessors/abstract.phpt create mode 100644 Zend/tests/accessors/abstract_prop.phpt create mode 100644 Zend/tests/accessors/abstract_prop_abstract_accessor.phpt create mode 100644 Zend/tests/accessors/abstract_prop_not_implemented.phpt create mode 100644 Zend/tests/accessors/auto.phpt create mode 100644 Zend/tests/accessors/backing_prop.phpt create mode 100644 Zend/tests/accessors/basic.phpt create mode 100644 Zend/tests/accessors/cache.phpt create mode 100644 Zend/tests/accessors/duplicate_accessor.phpt create mode 100644 Zend/tests/accessors/final.phpt create mode 100644 Zend/tests/accessors/final_prop.phpt create mode 100644 Zend/tests/accessors/final_prop_2.phpt create mode 100644 Zend/tests/accessors/get.phpt create mode 100644 Zend/tests/accessors/get_by_ref.phpt create mode 100644 Zend/tests/accessors/get_invalid_params.phpt create mode 100644 Zend/tests/accessors/get_invalid_return_type.phpt create mode 100644 Zend/tests/accessors/get_type_check.phpt create mode 100644 Zend/tests/accessors/inheritance.phpt create mode 100644 Zend/tests/accessors/inheritance_invalid_visibility.phpt create mode 100644 Zend/tests/accessors/inheritance_ref_mismatch.phpt create mode 100644 Zend/tests/accessors/interface.phpt create mode 100644 Zend/tests/accessors/interface_invalid_explicitly_abstract.phpt create mode 100644 Zend/tests/accessors/interface_not_implemented.phpt create mode 100644 Zend/tests/accessors/interface_not_public.phpt create mode 100644 Zend/tests/accessors/interface_not_public_2.phpt create mode 100644 Zend/tests/accessors/invalid_abstract.phpt create mode 100644 Zend/tests/accessors/invalid_abstract_body.phpt create mode 100644 Zend/tests/accessors/invalid_abstract_final.phpt create mode 100644 Zend/tests/accessors/invalid_abstract_indirect.phpt create mode 100644 Zend/tests/accessors/invalid_abstract_indirect_2.phpt create mode 100644 Zend/tests/accessors/invalid_abstract_private.phpt create mode 100644 Zend/tests/accessors/invalid_empty_accessors.phpt create mode 100644 Zend/tests/accessors/invalid_final_private.phpt create mode 100644 Zend/tests/accessors/invalid_increasing_visibility.phpt create mode 100644 Zend/tests/accessors/invalid_plain_to_accessor.phpt create mode 100644 Zend/tests/accessors/invalid_same_visibility.phpt create mode 100644 Zend/tests/accessors/invalid_static.phpt create mode 100644 Zend/tests/accessors/isset.phpt create mode 100644 Zend/tests/accessors/magic_consts.phpt create mode 100644 Zend/tests/accessors/magic_interaction.phpt create mode 100644 Zend/tests/accessors/override_by_plain_prop.phpt create mode 100644 Zend/tests/accessors/private_override.phpt create mode 100644 Zend/tests/accessors/private_override_auto.phpt create mode 100644 Zend/tests/accessors/set.phpt create mode 100644 Zend/tests/accessors/set_invalid_by_ref_return.phpt create mode 100644 Zend/tests/accessors/set_invalid_params_1.phpt create mode 100644 Zend/tests/accessors/set_invalid_params_2.phpt create mode 100644 Zend/tests/accessors/set_invalid_params_3.phpt create mode 100644 Zend/tests/accessors/set_invalid_params_4.phpt create mode 100644 Zend/tests/accessors/set_invalid_params_5.phpt create mode 100644 Zend/tests/accessors/traits.phpt create mode 100644 Zend/tests/accessors/traits_abstract.phpt create mode 100644 Zend/tests/accessors/traits_conflict.phpt create mode 100644 Zend/tests/accessors/type_compatibility.phpt create mode 100644 Zend/tests/accessors/type_compatibility_invalid.phpt create mode 100644 Zend/tests/accessors/type_compatibility_invalid_2.phpt create mode 100644 Zend/tests/accessors/unknown_accessor.phpt create mode 100644 Zend/tests/accessors/unset.phpt create mode 100644 Zend/tests/accessors/visibility.phpt delete mode 100644 Zend/tests/errmsg_038.phpt diff --git a/Zend/tests/accessors/abstract.phpt b/Zend/tests/accessors/abstract.phpt new file mode 100644 index 0000000000000..5437e6a589ea2 --- /dev/null +++ b/Zend/tests/accessors/abstract.phpt @@ -0,0 +1,15 @@ +--TEST-- +Abstract accessors +--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/accessors/abstract_prop.phpt b/Zend/tests/accessors/abstract_prop.phpt new file mode 100644 index 0000000000000..677f415f70770 --- /dev/null +++ b/Zend/tests/accessors/abstract_prop.phpt @@ -0,0 +1,17 @@ +--TEST-- +Whole property can be marked abstract, but must use accessors +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/accessors/abstract_prop_abstract_accessor.phpt b/Zend/tests/accessors/abstract_prop_abstract_accessor.phpt new file mode 100644 index 0000000000000..4068688fb8f11 --- /dev/null +++ b/Zend/tests/accessors/abstract_prop_abstract_accessor.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract property with abstract accessor +--FILE-- + +--EXPECTF-- +Fatal error: Accessor on abstract property cannot be explicitly abstract in %s on line %d diff --git a/Zend/tests/accessors/abstract_prop_not_implemented.phpt b/Zend/tests/accessors/abstract_prop_not_implemented.phpt new file mode 100644 index 0000000000000..e0442abe95a45 --- /dev/null +++ b/Zend/tests/accessors/abstract_prop_not_implemented.phpt @@ -0,0 +1,15 @@ +--TEST-- +Abstract property not implemented +--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/accessors/auto.phpt b/Zend/tests/accessors/auto.phpt new file mode 100644 index 0000000000000..d0d8a6ded95dd --- /dev/null +++ b/Zend/tests/accessors/auto.phpt @@ -0,0 +1,34 @@ +--TEST-- +Basic auto-generated accessors +--FILE-- +prop = 42; + $this->prop = 24; + } +} + +$test = new Test; +try { + $test->prop = 12; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->prop); +var_dump(isset($test->prop)); +try { + unset($test->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Call to private accessor Test::$prop::set() from global scope +int(24) +bool(true) +Cannot unset accessor property Test::$prop diff --git a/Zend/tests/accessors/backing_prop.phpt b/Zend/tests/accessors/backing_prop.phpt new file mode 100644 index 0000000000000..87c4a247c279f --- /dev/null +++ b/Zend/tests/accessors/backing_prop.phpt @@ -0,0 +1,24 @@ +--TEST-- +Backing property +--FILE-- +prop * 2; } + set { $this->prop = $value * 2; } + } +} + +$test = new Test; +$test->prop = 10; +var_dump($test->prop); +var_dump($test); + +?> +--EXPECT-- +int(40) +object(Test)#1 (1) { + ["prop"]=> + int(20) +} diff --git a/Zend/tests/accessors/basic.phpt b/Zend/tests/accessors/basic.phpt new file mode 100644 index 0000000000000..1d9f85f944420 --- /dev/null +++ b/Zend/tests/accessors/basic.phpt @@ -0,0 +1,17 @@ +--TEST-- +Basic accessor syntax +--FILE-- + +--EXPECT-- + diff --git a/Zend/tests/accessors/cache.phpt b/Zend/tests/accessors/cache.phpt new file mode 100644 index 0000000000000..135266b886983 --- /dev/null +++ b/Zend/tests/accessors/cache.phpt @@ -0,0 +1,54 @@ +--TEST-- +Test caching of accessor property kind +--FILE-- +prop; } + set { echo __METHOD__, "\n"; $this->prop = $value; } + } +} + +function doTest(Test $test) { + $test->prop; + $test->prop = 1; + $test->prop += 1; + $test->prop = []; + $test->prop[] = 1; + 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); + +?> +--EXPECT-- +Test::$prop::get +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::set +Test::$prop::get +Test::$prop::get +Test::$prop::get +Cannot unset accessor property Test::$prop + +Test::$prop::get +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::set +Test::$prop::get +Test::$prop::get +Test::$prop::get +Cannot unset accessor property Test::$prop diff --git a/Zend/tests/accessors/duplicate_accessor.phpt b/Zend/tests/accessors/duplicate_accessor.phpt new file mode 100644 index 0000000000000..303adafec2a10 --- /dev/null +++ b/Zend/tests/accessors/duplicate_accessor.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot declare same accessor twice +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare accessor "get" in %s on line %d diff --git a/Zend/tests/accessors/final.phpt b/Zend/tests/accessors/final.phpt new file mode 100644 index 0000000000000..ded2acc1b7fef --- /dev/null +++ b/Zend/tests/accessors/final.phpt @@ -0,0 +1,20 @@ +--TEST-- +Final accessors +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final accessor A::$prop::get() in %s on line %d diff --git a/Zend/tests/accessors/final_prop.phpt b/Zend/tests/accessors/final_prop.phpt new file mode 100644 index 0000000000000..40ff9b39f52d6 --- /dev/null +++ b/Zend/tests/accessors/final_prop.phpt @@ -0,0 +1,16 @@ +--TEST-- +Property itself may be marked final (accessor) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property B::$prop in %s on line %d diff --git a/Zend/tests/accessors/final_prop_2.phpt b/Zend/tests/accessors/final_prop_2.phpt new file mode 100644 index 0000000000000..3f4fd59e36150 --- /dev/null +++ b/Zend/tests/accessors/final_prop_2.phpt @@ -0,0 +1,16 @@ +--TEST-- +Property itself may be marked final (normal) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property B::$prop in %s on line %d diff --git a/Zend/tests/accessors/get.phpt b/Zend/tests/accessors/get.phpt new file mode 100644 index 0000000000000..5bbd165f24a93 --- /dev/null +++ b/Zend/tests/accessors/get.phpt @@ -0,0 +1,24 @@ +--TEST-- +Basic get only accessor +--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/accessors/get_by_ref.phpt b/Zend/tests/accessors/get_by_ref.phpt new file mode 100644 index 0000000000000..bcccf169d7e86 --- /dev/null +++ b/Zend/tests/accessors/get_by_ref.phpt @@ -0,0 +1,41 @@ +--TEST-- +Get accessor by ref and indirect modification +--FILE-- +_byVal; } + } + + public $_byRef = []; + public $byRef { + &get { return $this->_byRef; } + } + + public int $byRefType { + &get { $var = "42"; return $var; } + } +} + +$test = new Test; +$test->byRef[] = 42; +var_dump($test->byRef); + +$test->byVal[] = 42; +var_dump($test->byVal); + +var_dump($test->byRefType); + +?> +--EXPECTF-- +array(1) { + [0]=> + int(42) +} + +Notice: Indirect modification of accessor property Test::$byVal has no effect (did you mean to use "&get"?) in %s on line %d +array(0) { +} +int(42) diff --git a/Zend/tests/accessors/get_invalid_params.phpt b/Zend/tests/accessors/get_invalid_params.phpt new file mode 100644 index 0000000000000..a978b8c812028 --- /dev/null +++ b/Zend/tests/accessors/get_invalid_params.phpt @@ -0,0 +1,14 @@ +--TEST-- +get accessor may not have parameters +--FILE-- + +--EXPECTF-- +Fatal error: Accessor "get" may not have parameters in %s on line %d diff --git a/Zend/tests/accessors/get_invalid_return_type.phpt b/Zend/tests/accessors/get_invalid_return_type.phpt new file mode 100644 index 0000000000000..7dda1765d8a5f --- /dev/null +++ b/Zend/tests/accessors/get_invalid_return_type.phpt @@ -0,0 +1,14 @@ +--TEST-- +get accessor may not have return type +--FILE-- + +--EXPECTF-- +Fatal error: Accessor "get" may not have a return type (accessor types are determined by the property type) in %s on line %d diff --git a/Zend/tests/accessors/get_type_check.phpt b/Zend/tests/accessors/get_type_check.phpt new file mode 100644 index 0000000000000..d3ee513da9062 --- /dev/null +++ b/Zend/tests/accessors/get_type_check.phpt @@ -0,0 +1,26 @@ +--TEST-- +Get accessor 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/accessors/inheritance.phpt b/Zend/tests/accessors/inheritance.phpt new file mode 100644 index 0000000000000..22d4d2e5de521 --- /dev/null +++ b/Zend/tests/accessors/inheritance.phpt @@ -0,0 +1,32 @@ +--TEST-- +Basic accessor 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/accessors/inheritance_invalid_visibility.phpt b/Zend/tests/accessors/inheritance_invalid_visibility.phpt new file mode 100644 index 0000000000000..f8c1cc1b9a9f8 --- /dev/null +++ b/Zend/tests/accessors/inheritance_invalid_visibility.phpt @@ -0,0 +1,22 @@ +--TEST-- +Accessor visibility cannot be reduced during inheritance +--FILE-- + +--EXPECTF-- +Fatal error: Visibility of B::$prop::set() must be protected or higher (as in class A) in %s on line %d diff --git a/Zend/tests/accessors/inheritance_ref_mismatch.phpt b/Zend/tests/accessors/inheritance_ref_mismatch.phpt new file mode 100644 index 0000000000000..f85cd7b9daef9 --- /dev/null +++ b/Zend/tests/accessors/inheritance_ref_mismatch.phpt @@ -0,0 +1,15 @@ +--TEST-- +By-ref return mismatch during inheritance +--FILE-- + +--EXPECTF-- +Fatal error: B::$prop::get() must return by reference (as in class A) in %s on line %d diff --git a/Zend/tests/accessors/interface.phpt b/Zend/tests/accessors/interface.phpt new file mode 100644 index 0000000000000..870eb4865d509 --- /dev/null +++ b/Zend/tests/accessors/interface.phpt @@ -0,0 +1,15 @@ +--TEST-- +Accessors 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/accessors/interface_invalid_explicitly_abstract.phpt b/Zend/tests/accessors/interface_invalid_explicitly_abstract.phpt new file mode 100644 index 0000000000000..ebfd70f8bd235 --- /dev/null +++ b/Zend/tests/accessors/interface_invalid_explicitly_abstract.phpt @@ -0,0 +1,12 @@ +--TEST-- +Accessors in interfaces cannot be explicitly abstract +--FILE-- + +--EXPECTF-- +Fatal error: Accessor in interface cannot be explicitly abstract. All interface members are implicitly abstract in %s on line %d diff --git a/Zend/tests/accessors/interface_not_implemented.phpt b/Zend/tests/accessors/interface_not_implemented.phpt new file mode 100644 index 0000000000000..dcd994a3fa091 --- /dev/null +++ b/Zend/tests/accessors/interface_not_implemented.phpt @@ -0,0 +1,15 @@ +--TEST-- +Accessors 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/accessors/interface_not_public.phpt b/Zend/tests/accessors/interface_not_public.phpt new file mode 100644 index 0000000000000..aee807e3a4401 --- /dev/null +++ b/Zend/tests/accessors/interface_not_public.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot use non-public accessor in interface +--FILE-- + +--EXPECTF-- +Fatal error: Accessor in interface cannot be protected or private in %s on line %d diff --git a/Zend/tests/accessors/interface_not_public_2.phpt b/Zend/tests/accessors/interface_not_public_2.phpt new file mode 100644 index 0000000000000..778004f8a60d7 --- /dev/null +++ b/Zend/tests/accessors/interface_not_public_2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot use non-public accessor in interface (whole property) +--FILE-- + +--EXPECTF-- +Fatal error: Accessor in interface cannot be protected or private in %s on line %d diff --git a/Zend/tests/accessors/invalid_abstract.phpt b/Zend/tests/accessors/invalid_abstract.phpt new file mode 100644 index 0000000000000..07a40e5d5da5c --- /dev/null +++ b/Zend/tests/accessors/invalid_abstract.phpt @@ -0,0 +1,41 @@ +--TEST-- +Implementing abstract accessors +--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/accessors/invalid_abstract_body.phpt b/Zend/tests/accessors/invalid_abstract_body.phpt new file mode 100644 index 0000000000000..9f8ca13b95571 --- /dev/null +++ b/Zend/tests/accessors/invalid_abstract_body.phpt @@ -0,0 +1,14 @@ +--TEST-- +Abstract accessor cannot have body +--FILE-- + +--EXPECTF-- +Fatal error: Abstract accessor cannot have body in %s on line %d diff --git a/Zend/tests/accessors/invalid_abstract_final.phpt b/Zend/tests/accessors/invalid_abstract_final.phpt new file mode 100644 index 0000000000000..36651848339af --- /dev/null +++ b/Zend/tests/accessors/invalid_abstract_final.phpt @@ -0,0 +1,12 @@ +--TEST-- +Accessor cannot be both abstract and final +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the final modifier on an abstract class member in %s on line %d diff --git a/Zend/tests/accessors/invalid_abstract_indirect.phpt b/Zend/tests/accessors/invalid_abstract_indirect.phpt new file mode 100644 index 0000000000000..83f0d910f77c3 --- /dev/null +++ b/Zend/tests/accessors/invalid_abstract_indirect.phpt @@ -0,0 +1,16 @@ +--TEST-- +Class with abstract accessor 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/accessors/invalid_abstract_indirect_2.phpt b/Zend/tests/accessors/invalid_abstract_indirect_2.phpt new file mode 100644 index 0000000000000..78c2a63c7882b --- /dev/null +++ b/Zend/tests/accessors/invalid_abstract_indirect_2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Class with abstract accessor 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/accessors/invalid_abstract_private.phpt b/Zend/tests/accessors/invalid_abstract_private.phpt new file mode 100644 index 0000000000000..56abb3e45eb25 --- /dev/null +++ b/Zend/tests/accessors/invalid_abstract_private.phpt @@ -0,0 +1,14 @@ +--TEST-- +Accessor cannot be both abstract and private +--FILE-- + +--EXPECTF-- +Fatal error: Accessor cannot be both abstract and private in %s on line %d diff --git a/Zend/tests/accessors/invalid_empty_accessors.phpt b/Zend/tests/accessors/invalid_empty_accessors.phpt new file mode 100644 index 0000000000000..b839567b3afd5 --- /dev/null +++ b/Zend/tests/accessors/invalid_empty_accessors.phpt @@ -0,0 +1,12 @@ +--TEST-- +Accessor list cannot be empty +--FILE-- + +--EXPECTF-- +Fatal error: Accessor list cannot be empty in %s on line %d diff --git a/Zend/tests/accessors/invalid_final_private.phpt b/Zend/tests/accessors/invalid_final_private.phpt new file mode 100644 index 0000000000000..cc4cfa9fd0b1c --- /dev/null +++ b/Zend/tests/accessors/invalid_final_private.phpt @@ -0,0 +1,12 @@ +--TEST-- +Accessor cannot be both final and private +--FILE-- + +--EXPECTF-- +Fatal error: Accessor cannot be both final and private in %s on line %d diff --git a/Zend/tests/accessors/invalid_increasing_visibility.phpt b/Zend/tests/accessors/invalid_increasing_visibility.phpt new file mode 100644 index 0000000000000..871c58e5d87e9 --- /dev/null +++ b/Zend/tests/accessors/invalid_increasing_visibility.phpt @@ -0,0 +1,14 @@ +--TEST-- +Visibility of accessor cannot be higher than of property +--FILE-- + +--EXPECTF-- +Fatal error: Visibility of accessor cannot be higher than visibility of property in %s on line %d diff --git a/Zend/tests/accessors/invalid_plain_to_accessor.phpt b/Zend/tests/accessors/invalid_plain_to_accessor.phpt new file mode 100644 index 0000000000000..1ab6ab188ede1 --- /dev/null +++ b/Zend/tests/accessors/invalid_plain_to_accessor.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot override plain property with accessor property +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override plain property A::$prop with accessor property in class B in %s on line %d diff --git a/Zend/tests/accessors/invalid_same_visibility.phpt b/Zend/tests/accessors/invalid_same_visibility.phpt new file mode 100644 index 0000000000000..29e22acf332c1 --- /dev/null +++ b/Zend/tests/accessors/invalid_same_visibility.phpt @@ -0,0 +1,14 @@ +--TEST-- +Visibility of accessor cannot be (explicitly) same as property +--FILE-- + +--EXPECTF-- +Fatal error: Explicit accessor visibility cannot be the same as property visibility. Omit the explicit accessor visibility in %s on line %d diff --git a/Zend/tests/accessors/invalid_static.phpt b/Zend/tests/accessors/invalid_static.phpt new file mode 100644 index 0000000000000..0f24e756aae25 --- /dev/null +++ b/Zend/tests/accessors/invalid_static.phpt @@ -0,0 +1,14 @@ +--TEST-- +Accessor method cannot be static +--FILE-- + +--EXPECTF-- +Fatal error: Accessor cannot be static in %s on line %d diff --git a/Zend/tests/accessors/isset.phpt b/Zend/tests/accessors/isset.phpt new file mode 100644 index 0000000000000..a1f062854e743 --- /dev/null +++ b/Zend/tests/accessors/isset.phpt @@ -0,0 +1,78 @@ +--TEST-- +isset() and empty() call get accessor +--FILE-- +_prop1; } + set { $this->_prop1 = $value; } + } + + public $prop2 { + get { return $this->prop2; } + set { $this->prop2 = $value; } + } + + public array $prop3 { private get; } +} + +$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"; + +$test->prop2 = true; +var_dump(isset($test->prop2)); +var_dump(!empty($test->prop2)); +echo "\n", +$test->prop2 = false; +var_dump(isset($test->prop2)); +var_dump(!empty($test->prop2)); +echo "\n", +$test->prop2 = null; +var_dump(isset($test->prop2)); +var_dump(!empty($test->prop2)); +echo "\n"; + +// Private accessor should simply report as false, not throw +var_dump(isset($test->prop3)); +var_dump(!empty($test->prop3)); +var_dump(isset($test->prop3[0])); +var_dump(!empty($test->prop3[0])); + +?> +--EXPECT-- +bool(true) +bool(true) + +bool(true) +bool(false) + +bool(false) +bool(false) + +bool(true) +bool(true) + +bool(true) +bool(false) + +bool(false) +bool(false) + +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/Zend/tests/accessors/magic_consts.phpt b/Zend/tests/accessors/magic_consts.phpt new file mode 100644 index 0000000000000..77dfb061f2792 --- /dev/null +++ b/Zend/tests/accessors/magic_consts.phpt @@ -0,0 +1,24 @@ +--TEST-- +Magic constants in accessors +--FILE-- +prop; + +?> +--EXPECT-- +string(10) "$prop::get" +string(16) "Test::$prop::get" +string(4) "Test" diff --git a/Zend/tests/accessors/magic_interaction.phpt b/Zend/tests/accessors/magic_interaction.phpt new file mode 100644 index 0000000000000..5d956f0342a42 --- /dev/null +++ b/Zend/tests/accessors/magic_interaction.phpt @@ -0,0 +1,63 @@ +--TEST-- +Interaction of inaccessible accessors 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 __METHOD__, "($name)\n"; + try { + unset($this->$name); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$b = new B; +$b->prop; +isset($b->prop); +$b->prop = 1; +unset($b->prop); + +?> +--EXPECT-- +B::__get(prop) +Call to private accessor A::$prop::get() from scope B +B::__isset(prop) +bool(false) +B::__set(prop, 1) +Call to private accessor A::$prop::set() from scope B +B::__unset(prop) +Cannot unset accessor property B::$prop diff --git a/Zend/tests/accessors/override_by_plain_prop.phpt b/Zend/tests/accessors/override_by_plain_prop.phpt new file mode 100644 index 0000000000000..8396b91a42536 --- /dev/null +++ b/Zend/tests/accessors/override_by_plain_prop.phpt @@ -0,0 +1,30 @@ +--TEST-- +Accessor can be overridden by a plain property +--FILE-- +prop = 1; +var_dump($a->prop); + +$b = new B; +$b->prop = 1; +var_dump($b->prop); + +?> +--EXPECT-- +A::$prop::set +A::$prop::get +int(42) +int(1) diff --git a/Zend/tests/accessors/private_override.phpt b/Zend/tests/accessors/private_override.phpt new file mode 100644 index 0000000000000..52d54ec4c7fce --- /dev/null +++ b/Zend/tests/accessors/private_override.phpt @@ -0,0 +1,92 @@ +--TEST-- +Overriding private accessors +--FILE-- +prop1; + $this->prop1 = 1; + $this->prop2; + $this->prop2 = 1; + $this->prop3; + $this->prop3 = 1; + $this->prop4; + $this->prop4 = 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"; } + } + public $prop3 { + get { echo __METHOD__, "\n"; } + set { echo __METHOD__, "\n"; } + } + public $prop4 { + 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; +$b->prop3; +$b->prop3 = 1; +$b->prop4; +$b->prop4 = 1; + +?> +--EXPECT-- +A::$prop2::get +A::$prop2::set +A::$prop3::get +A::$prop3::set +A::$prop4::get +A::$prop4::set + +A::$prop2::get +A::$prop2::set +B::$prop3::get +A::$prop3::set +A::$prop4::get +B::$prop4::set + +B::$prop1::get +B::$prop1::set +B::$prop2::get +B::$prop2::set +B::$prop3::get +B::$prop3::set +B::$prop4::get +B::$prop4::set diff --git a/Zend/tests/accessors/private_override_auto.phpt b/Zend/tests/accessors/private_override_auto.phpt new file mode 100644 index 0000000000000..e9d71f979194e --- /dev/null +++ b/Zend/tests/accessors/private_override_auto.phpt @@ -0,0 +1,36 @@ +--TEST-- +Overriding private accessors (auto-generated) +--FILE-- +prop1 = 'A'; + var_dump($this->prop1); + try { + $this->prop1 = []; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +class B extends A { + public string $prop1 { + set { echo __METHOD__, "\n"; } + } +} + +$b = new B; +$b->testPrivate(); +$b->prop1 = 'B'; +var_dump($b->prop1); + +?> +--EXPECT-- +string(1) "A" +Cannot assign array to property A::$prop1 of type string +B::$prop1::set +string(1) "A" diff --git a/Zend/tests/accessors/set.phpt b/Zend/tests/accessors/set.phpt new file mode 100644 index 0000000000000..0426a4df0da86 --- /dev/null +++ b/Zend/tests/accessors/set.phpt @@ -0,0 +1,32 @@ +--TEST-- +Basic set only accessor +--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/accessors/set_invalid_by_ref_return.phpt b/Zend/tests/accessors/set_invalid_by_ref_return.phpt new file mode 100644 index 0000000000000..3f00bcea87103 --- /dev/null +++ b/Zend/tests/accessors/set_invalid_by_ref_return.phpt @@ -0,0 +1,12 @@ +--TEST-- +set accessor cannot return by ref +--FILE-- + +--EXPECTF-- +Fatal error: Accessor "set" cannot return by reference in %s on line %d diff --git a/Zend/tests/accessors/set_invalid_params_1.phpt b/Zend/tests/accessors/set_invalid_params_1.phpt new file mode 100644 index 0000000000000..939e5896c9849 --- /dev/null +++ b/Zend/tests/accessors/set_invalid_params_1.phpt @@ -0,0 +1,14 @@ +--TEST-- +set accessor must have exactly one parameter +--FILE-- + +--EXPECTF-- +Fatal error: Accessor "set" must have exactly one required by-value parameter in %s on line %d diff --git a/Zend/tests/accessors/set_invalid_params_2.phpt b/Zend/tests/accessors/set_invalid_params_2.phpt new file mode 100644 index 0000000000000..579e80246d6a6 --- /dev/null +++ b/Zend/tests/accessors/set_invalid_params_2.phpt @@ -0,0 +1,14 @@ +--TEST-- +set accessor parameter cannot be optional +--FILE-- + +--EXPECTF-- +Fatal error: Accessor "set" must have exactly one required by-value parameter in %s on line %d diff --git a/Zend/tests/accessors/set_invalid_params_3.phpt b/Zend/tests/accessors/set_invalid_params_3.phpt new file mode 100644 index 0000000000000..5847636d64264 --- /dev/null +++ b/Zend/tests/accessors/set_invalid_params_3.phpt @@ -0,0 +1,14 @@ +--TEST-- +set accessor parameter cannot be by-reference +--FILE-- + +--EXPECTF-- +Fatal error: Accessor "set" must have exactly one required by-value parameter in %s on line %d diff --git a/Zend/tests/accessors/set_invalid_params_4.phpt b/Zend/tests/accessors/set_invalid_params_4.phpt new file mode 100644 index 0000000000000..fa170181deff8 --- /dev/null +++ b/Zend/tests/accessors/set_invalid_params_4.phpt @@ -0,0 +1,14 @@ +--TEST-- +set accessor parameter cannot be variadic +--FILE-- + +--EXPECTF-- +Fatal error: Accessor "set" must have exactly one required by-value parameter in %s on line %d diff --git a/Zend/tests/accessors/set_invalid_params_5.phpt b/Zend/tests/accessors/set_invalid_params_5.phpt new file mode 100644 index 0000000000000..b99869ccae0eb --- /dev/null +++ b/Zend/tests/accessors/set_invalid_params_5.phpt @@ -0,0 +1,14 @@ +--TEST-- +set accessor parameter cannot have parameter type +--FILE-- + +--EXPECTF-- +Fatal error: Accessor "set" may not have a parameter type (accessor types are determined by the property type) in %s on line %d diff --git a/Zend/tests/accessors/traits.phpt b/Zend/tests/accessors/traits.phpt new file mode 100644 index 0000000000000..ad425f768ed38 --- /dev/null +++ b/Zend/tests/accessors/traits.phpt @@ -0,0 +1,24 @@ +--TEST-- +Accessor properties in traits +--FILE-- +prop; +$test->prop = 1; + +?> +--EXPECT-- +T::$prop::get +T::$prop::set diff --git a/Zend/tests/accessors/traits_abstract.phpt b/Zend/tests/accessors/traits_abstract.phpt new file mode 100644 index 0000000000000..f98d1b34bb6f5 --- /dev/null +++ b/Zend/tests/accessors/traits_abstract.phpt @@ -0,0 +1,16 @@ +--TEST-- +Abstract accessors from trait +--FILE-- + +--EXPECTF-- +Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (T::$prop::get, T::$prop::set) in %s on line %d diff --git a/Zend/tests/accessors/traits_conflict.phpt b/Zend/tests/accessors/traits_conflict.phpt new file mode 100644 index 0000000000000..0cecdcb9874d5 --- /dev/null +++ b/Zend/tests/accessors/traits_conflict.phpt @@ -0,0 +1,24 @@ +--TEST-- +Trait accessor conflict +--FILE-- + +--EXPECTF-- +Fatal error: C and T define the same accessor property ($prop) in the composition of C. Conflict resolution between accessor properties is currently not supported. Class was composed in %s on line %d diff --git a/Zend/tests/accessors/type_compatibility.phpt b/Zend/tests/accessors/type_compatibility.phpt new file mode 100644 index 0000000000000..0cde067ccbf0a --- /dev/null +++ b/Zend/tests/accessors/type_compatibility.phpt @@ -0,0 +1,17 @@ +--TEST-- +Relaxed type compatibility for read-only and write-only properties +--FILE-- + +--EXPECT-- + diff --git a/Zend/tests/accessors/type_compatibility_invalid.phpt b/Zend/tests/accessors/type_compatibility_invalid.phpt new file mode 100644 index 0000000000000..75a2a9bd784b1 --- /dev/null +++ b/Zend/tests/accessors/type_compatibility_invalid.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid type compatibility for read-only property +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::$a must be subtype of int (as in class A) in %s on line %d diff --git a/Zend/tests/accessors/type_compatibility_invalid_2.phpt b/Zend/tests/accessors/type_compatibility_invalid_2.phpt new file mode 100644 index 0000000000000..1b44b2c05bd31 --- /dev/null +++ b/Zend/tests/accessors/type_compatibility_invalid_2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid type compatibility for write-only property +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::$a must be supertype of int|float (as in class A) in %s on line %d diff --git a/Zend/tests/accessors/unknown_accessor.phpt b/Zend/tests/accessors/unknown_accessor.phpt new file mode 100644 index 0000000000000..68dddc5732ef6 --- /dev/null +++ b/Zend/tests/accessors/unknown_accessor.phpt @@ -0,0 +1,14 @@ +--TEST-- +Unknown accessor +--FILE-- + +--EXPECTF-- +Fatal error: Unknown accessor "foobar" for property Test::$prop in %s on line 5 diff --git a/Zend/tests/accessors/unset.phpt b/Zend/tests/accessors/unset.phpt new file mode 100644 index 0000000000000..3e6dbf02b938d --- /dev/null +++ b/Zend/tests/accessors/unset.phpt @@ -0,0 +1,25 @@ +--TEST-- +Accessor properties cannot be unset +--FILE-- +prop; } + set { $this->prop = $value; } + } +} + +$test = new Test; +$test->prop = 42; +try { + unset($test->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->prop); + +?> +--EXPECT-- +Cannot unset accessor property Test::$prop +int(42) diff --git a/Zend/tests/accessors/visibility.phpt b/Zend/tests/accessors/visibility.phpt new file mode 100644 index 0000000000000..20fa1fbe377af --- /dev/null +++ b/Zend/tests/accessors/visibility.phpt @@ -0,0 +1,160 @@ +--TEST-- +Accessor visibility +--FILE-- +privateSet = 42; + $this->privateGet; + $this->protectedSet = 42; + $this->protectedGet; + $this->protectedGetPrivateSet = 42; + $this->protectedSetPrivateGet; + $this->protectedGetPrivateSet; + $this->protectedSetPrivateGet = 42; + } +} + +class Test2 extends Test { + public function testProtected() { + try { + $this->privateSet = 42; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + try { + $this->privateGet; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + $this->protectedSet = 42; + $this->protectedGet; + try { + $this->protectedGetPrivateSet = 42; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + try { + $this->protectedSetPrivateGet; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + $this->protectedGetPrivateSet; + $this->protectedSetPrivateGet = 42; + } +} + +$test = new Test2; + +var_dump($test->privateSet); +$test->privateGet = 42; +var_dump($test->protectedSet); +$test->protectedGet = 42; + +$test->testPrivate(); +echo "\n"; +$test->testProtected(); +echo "\n"; + +try { + $test->privateSet = 42; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->privateGet; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->protectedSet = 42; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->protectedGet; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->protectedGetPrivateSet = 42; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->protectedSetPrivateGet; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->protectedGetPrivateSet; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->protectedSetPrivateGet = 42; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +int(42) +int(24) +Called private set() +Called private get() +Called protected set() +Called protected get() +Called private set() +Called private get() +Called protected get() +Called protecated set() + +Call to private accessor Test::$privateSet::set() from scope Test2 +Call to private accessor Test::$privateGet::get() from scope Test2 +Called protected set() +Called protected get() +Call to private accessor Test::$protectedGetPrivateSet::set() from scope Test2 +Call to private accessor Test::$protectedSetPrivateGet::get() from scope Test2 +Called protected get() +Called protecated set() + +Call to private accessor Test::$privateSet::set() from global scope +Call to private accessor Test::$privateGet::get() from global scope +Call to protected accessor Test::$protectedSet::set() from global scope +Call to protected accessor Test::$protectedGet::get() from global scope +Cannot access protected property Test2::$protectedGetPrivateSet +Cannot access protected property Test2::$protectedSetPrivateGet +Cannot access protected property Test2::$protectedGetPrivateSet +Cannot access protected property Test2::$protectedSetPrivateGet 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/errmsg_037.phpt b/Zend/tests/errmsg_037.phpt index f15fea89f3b7c..c07cf4dc346ed 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: Properties cannot be declared abstract in %s on line %d +Fatal error: Only accessor 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 d5ae50ded0f92..0000000000000 --- a/Zend/tests/errmsg_038.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -errmsg: properties cannot be final ---FILE-- - ---EXPECTF-- -Fatal error: Cannot declare property test::$var final, the final modifier is allowed only for methods and classes in %s on line %d diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 7b797e5a939c8..f7ad299c3e4e8 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4107,6 +4107,7 @@ 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->accessors = NULL; property_info->ce = ce; property_info->type = type; diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index fb6587b48cd31..e657a17014fd1 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_ACCESSOR, /* list nodes */ ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT, @@ -157,7 +158,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_ENUM_CASE, @@ -167,6 +167,7 @@ enum _zend_ast_kind { /* 4 child nodes */ ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, ZEND_AST_FOREACH, + ZEND_AST_PROP_ELEM, /* 5 child nodes */ ZEND_AST_PARAM = 5 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 884f653ed7245..75d6b212ae4ce 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7026,7 +7026,7 @@ static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_as } /* }}} */ -void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ +zend_op_array *zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *params_ast = decl->child[0]; @@ -7034,6 +7034,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ zend_ast *stmt_ast = decl->child[2]; zend_ast *return_type_ast = decl->child[3]; bool is_method = decl->kind == ZEND_AST_METHOD; + bool is_accessor = decl->kind == ZEND_AST_ACCESSOR; zend_string *method_lcname; zend_class_entry *orig_class_entry = CG(active_class_entry); @@ -7064,7 +7065,11 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ op_array->fn_flags |= ZEND_ACC_CLOSURE; } - if (is_method) { + if (is_accessor) { + 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; method_lcname = zend_begin_method_decl(op_array, decl->name, has_body); } else { @@ -7146,38 +7151,222 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ CG(active_op_array) = orig_op_array; CG(active_class_entry) = orig_class_entry; + + return op_array; } /* }}} */ +static bool is_valid_set_param(zend_ast *param) { + zend_ast *type_ast = param->child[0]; + zend_ast *default_ast = param->child[2]; + if (param->attr || default_ast) { + return false; + } + if (type_ast) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"set\" may not have a parameter type " + "(accessor types are determined by the property type)"); + } + return true; +} + +static void zend_compile_accessors( + zend_property_info *prop_info, zend_ast *prop_type_ast, zend_ast_list *accessors) +{ + zend_class_entry *ce = CG(active_class_entry); + zend_string *prop_name = prop_info->name; + + if (accessors->children == 0) { + zend_error_noreturn(E_COMPILE_ERROR, "Accessor list cannot be empty"); + } + + for (uint32_t i = 0; i < accessors->children; i++) { + zend_ast_decl *accessor = (zend_ast_decl *) accessors->child[i]; + zend_string *name = accessor->name; + zend_ast_list *param_list = + accessor->child[0] ? zend_ast_get_list(accessor->child[0]) : NULL; + zend_ast *stmt_ast = accessor->child[2]; + zend_ast **return_ast_ptr = &accessor->child[3]; + CG(zend_lineno) = accessor->start_lineno; + bool reset_return_ast = false, reset_param_type_ast = false; + + if (*return_ast_ptr) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"%s\" may not have a return type " + "(accessor types are determined by the property type)", + ZSTR_VAL(name)); + } + + if (zend_string_equals_literal_ci(name, "get")) { + if (param_list) { + if (param_list->children != 0) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"get\" may not have parameters"); + } + } else { + accessor->child[0] = zend_ast_create_list(0, ZEND_AST_PARAM_LIST); + } + + reset_return_ast = true; + *return_ast_ptr = prop_type_ast; + } else if (zend_string_equals_literal_ci(name, "set")) { + if (param_list) { + if (param_list->children != 1 || !is_valid_set_param(param_list->child[0])) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"set\" must have exactly one required by-value parameter"); + } + } else { + zend_string *param_name = zend_string_init("value", sizeof("value")-1, 0); + zend_ast *param_name_ast = zend_ast_create_zval_from_str(param_name); + zend_ast *param = zend_ast_create( + ZEND_AST_PARAM, prop_type_ast, param_name_ast, + /* expr */ NULL, /* doc_comment */ NULL, /* attributes */ NULL); + accessor->child[0] = zend_ast_create_list(1, ZEND_AST_PARAM_LIST, param); + reset_param_type_ast = true; + } + *return_ast_ptr = zend_ast_create_ex(ZEND_AST_TYPE, IS_VOID); + if (accessor->flags & ZEND_ACC_RETURN_REFERENCE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"set\" cannot return by reference"); + } + } else { + zend_error_noreturn(E_COMPILE_ERROR, + "Unknown accessor \"%s\" for property %s::$%s", + ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + + uint32_t accessor_visibility = accessor->flags & ZEND_ACC_PPP_MASK; + uint32_t prop_visibility = prop_info->flags & ZEND_ACC_PPP_MASK; + if (!accessor_visibility) { + /* Inherit the visibility of the property. */ + accessor->flags |= prop_visibility; + } else { + if (accessor_visibility < prop_visibility) { + zend_error_noreturn(E_COMPILE_ERROR, + "Visibility of accessor cannot be higher than visibility of property"); + } + if (accessor_visibility == prop_visibility) { + zend_error_noreturn(E_COMPILE_ERROR, + "Explicit accessor visibility cannot be the same as property visibility. " + "Omit the explicit accessor visibility"); + } + } + + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + if (accessor->flags & (ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor in interface cannot be protected or private"); + } + if (accessor->flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor in interface cannot be explicitly abstract. " + "All interface members are implicitly abstract"); + } + accessor->flags |= ZEND_ACC_ABSTRACT; + } + if (prop_info->flags & ZEND_ACC_ABSTRACT) { + if (accessor->flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor on abstract property cannot be explicitly abstract"); + } + accessor->flags |= ZEND_ACC_ABSTRACT; + } + + if (accessor->flags & ZEND_ACC_STATIC) { + zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be static"); + } + if ((accessor->flags & ZEND_ACC_FINAL) && (accessor->flags & ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be both final and private"); + } + if (accessor->flags & ZEND_ACC_ABSTRACT) { + if (stmt_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Abstract accessor cannot have body"); + } + if (accessor->flags & ZEND_ACC_PRIVATE) { + // TODO Trait exception? + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor cannot be both abstract and private"); + } + + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } + + zend_string *prefixed_name = zend_strpprintf(0, "$%s::%s", + zend_get_unmangled_property_name(prop_name), ZSTR_VAL(name)); + accessor->name = prefixed_name; + + zend_function *func = (zend_function *) zend_compile_func_decl( + NULL, (zend_ast *) accessor, /* toplevel */ false); + if (stmt_ast) { + ce->ce_flags |= ZEND_ACC_USE_GUARDS; + } else { + // TODO: These could use a more compact representation. + // Currently we even allocate opcodes for body-less functions. + func->common.fn_flags |= ZEND_ACC_AUTO_PROP; + } + + if (!prop_info->accessors) { + prop_info->accessors = ecalloc(1, sizeof(zend_property_accessors)); + } + + if (zend_string_equals_literal_ci(name, "get")) { + if (prop_info->accessors->get) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare accessor \"get\""); + } + prop_info->accessors->get = func; + } else if (zend_string_equals_literal_ci(name, "set")) { + if (prop_info->accessors->set) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare accessor \"set\""); + } + prop_info->accessors->set = func; + } else { + ZEND_UNREACHABLE(); + } + + zend_string_release(name); + /* Un-share type ASTs. Alternatively we could duplicate them. */ + if (reset_return_ast) { + *return_ast_ptr = NULL; + } + if (reset_param_type_ast) { + zend_ast_get_list(accessor->child[0])->child[0]->child[0] = NULL; + } + } +} + void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); 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, "Enums may not include properties"); } - if (flags & ZEND_ACC_ABSTRACT) { - zend_error_noreturn(E_COMPILE_ERROR, "Properties cannot be declared 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 *accessors_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); + if (!accessors_ast) { + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Interfaces may only include accessor properties"); + } + if (flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Only accessor properties may be declared abstract"); + } + } + if (type_ast) { type = zend_compile_typename(type_ast, /* force_allow_null */ 0); @@ -7194,12 +7383,6 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z doc_comment = zend_string_copy(zend_ast_get_str(doc_comment_ast)); } - if (flags & ZEND_ACC_FINAL) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare property %s::$%s final, " - "the final modifier is allowed only for methods and classes", - ZSTR_VAL(ce->name), ZSTR_VAL(name)); - } - if (zend_hash_exists(&ce->properties_info, name)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::$%s", ZSTR_VAL(ce->name), ZSTR_VAL(name)); @@ -7234,6 +7417,10 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type); + if (accessors_ast) { + zend_compile_accessors(info, type_ast, zend_ast_get_list(accessors_ast)); + } + if (attr_ast) { zend_compile_attributes(&info->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 6291e4397b883..88736cb559f57 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -210,14 +210,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 | | | */ /* | | | */ /* Immutable op_array and class_entries | | | */ @@ -238,6 +235,12 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ +/* Property Flags (unused: 8...) | | | */ +/* =========== | | | */ +/* | | | */ +/* Promoted property / parameter | | | */ +#define ZEND_ACC_PROMOTED (1 << 7) /* | | X | */ +/* | | | */ /* Class Flags (unused: 29...) | | | */ /* =========== | | | */ /* | | | */ @@ -301,7 +304,7 @@ typedef struct _zend_oparray_context { /* loaded from file cache to process memory | | | */ #define ZEND_ACC_FILE_CACHED (1 << 27) /* X | | | */ /* | | | */ -/* Function Flags (unused: 27-30) | | | */ +/* Function Flags (unused: 28-30) | | | */ /* ============== | | | */ /* | | | */ /* deprecation flag | | | */ @@ -357,6 +360,9 @@ typedef struct _zend_oparray_context { /* method flag used by Closure::__invoke() (int only) | | | */ #define ZEND_ACC_USER_ARG_INFO (1 << 26) /* | X | | */ /* | | | */ +/* accessor method is automatically implemented | | | */ +#define ZEND_ACC_AUTO_PROP (1 << 27) /* | X | | */ +/* | | | */ /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ @@ -372,6 +378,11 @@ typedef struct _zend_oparray_context { char *zend_visibility_string(uint32_t fn_flags); +typedef struct _zend_property_accessors { + zend_function *get; + zend_function *set; +} zend_property_accessors; + typedef struct _zend_property_info { uint32_t offset; /* property offset for object properties or property index for static properties */ @@ -381,6 +392,7 @@ typedef struct _zend_property_info { HashTable *attributes; zend_class_entry *ce; zend_type type; + zend_property_accessors *accessors; } zend_property_info; #define OBJ_PROP(obj, offset) \ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 151eb4ecc59a3..5625beb9323f2 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -2825,18 +2825,23 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c } return; } - } else if (EXPECTED(zobj->properties != NULL)) { - if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { - if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { - GC_DELREF(zobj->properties); + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { + if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { + GC_DELREF(zobj->properties); + } + zobj->properties = zend_array_dup(zobj->properties); + } + ptr = zend_hash_find_ex(zobj->properties, Z_STR_P(prop_ptr), 1); + if (EXPECTED(ptr)) { + ZVAL_INDIRECT(result, ptr); + return; } - zobj->properties = zend_array_dup(zobj->properties); - } - ptr = zend_hash_find_ex(zobj->properties, Z_STR_P(prop_ptr), 1); - if (EXPECTED(ptr)) { - ZVAL_INDIRECT(result, ptr); - return; } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } @@ -2869,7 +2874,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; } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 3edc172379a95..b4fc91c9696bc 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -31,13 +31,19 @@ ZEND_API zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces) = NULL; ZEND_API 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) = NULL; +typedef enum { + 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( zend_class_entry *ce, const zend_function *child_fn, zend_class_entry *child_scope, 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 zend_type_copy_ctor(zend_type *type, bool persistent) { if (ZEND_TYPE_HAS_LIST(*type)) { @@ -1006,7 +1012,8 @@ static zend_always_inline void do_inherit_method(zend_string *key, zend_function /* }}} */ 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; @@ -1017,10 +1024,12 @@ 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; } @@ -1032,16 +1041,82 @@ 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_accessor( + zend_class_entry *ce, zend_function **parent_ptr, zend_function **child_ptr) { + zend_function *parent = *parent_ptr; + zend_function *child = *child_ptr; + 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, /* is_interface */ false); + return; + } + + uint32_t parent_flags = parent->common.fn_flags; + if (parent_flags & ZEND_ACC_PRIVATE) { + child->common.fn_flags |= ZEND_ACC_CHANGED; + return; + } + + uint32_t child_flags = child->common.fn_flags; + if (parent_flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot override final accessor %s::%s()", + ZSTR_VAL(parent->common.scope->name), + ZSTR_VAL(parent->common.function_name)); + } + if ((child_flags & ZEND_ACC_PPP_MASK) > (parent_flags & ZEND_ACC_PPP_MASK)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Visibility of %s::%s() must be %s%s (as in class %s)", + ZSTR_VAL(child->common.scope->name), + ZSTR_VAL(child->common.function_name), + zend_visibility_string(parent_flags), + (parent_flags & ZEND_ACC_PUBLIC) ? "" : " or higher", + ZSTR_VAL(parent->common.scope->name)); + } + if ((child_flags & ZEND_ACC_RETURN_REFERENCE) < (parent_flags & ZEND_ACC_RETURN_REFERENCE)) { + zend_error_noreturn(E_COMPILE_ERROR, + "%s::%s() must return by reference (as in class %s)", + ZSTR_VAL(child->common.scope->name), + ZSTR_VAL(child->common.function_name), + ZSTR_VAL(parent->common.scope->name)); + } + + /* 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) { + if (prop_info->accessors) { + if (!prop_info->accessors->set) { + return PROP_COVARIANT; + } + if (!prop_info->accessors->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_ex(&ce->properties_info, key, 1); @@ -1052,6 +1127,10 @@ 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(ce->name), ZSTR_VAL(key)); + } if (!(parent_info->flags & ZEND_ACC_PRIVATE)) { 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", @@ -1072,13 +1151,28 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke child_info->offset = parent_info->offset; } + zend_property_accessors *parent_accessors = parent_info->accessors; + zend_property_accessors *child_accessors = child_info->accessors; + if (child_accessors) { + if (!parent_accessors) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot override plain property %s::$%s with accessor property in class %s", + ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key), ZSTR_VAL(ce->name)); + } + + inherit_accessor(ce, &parent_accessors->get, &child_accessors->get); + inherit_accessor(ce, &parent_accessors->set, &child_accessors->set); + } + + prop_variance variance = prop_get_variance(parent_info); if (UNEXPECTED(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, @@ -1089,6 +1183,15 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } } } else { + zend_property_accessors *accessors = parent_info->accessors; + if (accessors) { + if ((accessors->get && (accessors->get->common.fn_flags & ZEND_ACC_ABSTRACT)) || + (accessors->set && (accessors->set->common.fn_flags & ZEND_ACC_ABSTRACT)) + ) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } + } + _zend_hash_append_ptr(&ce->properties_info, key, parent_info); } } @@ -1479,6 +1582,7 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_function *func; zend_string *key; zend_class_constant *c; + zend_property_info *prop; ZEND_HASH_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { do_inherit_iface_constant(key, c, ce, iface); @@ -1488,6 +1592,14 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * do_inherit_method(key, func, ce, 1, 0); } 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_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); @@ -2012,7 +2124,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent if ((colliding_prop->flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC)) == (flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC)) && - 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; @@ -2058,6 +2170,16 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent ZSTR_VAL(prop_name), ZSTR_VAL(ce->name)); } + + if (colliding_prop->accessors || property_info->accessors) { + zend_error_noreturn(E_COMPILE_ERROR, + "%s and %s define the same accessor property ($%s) in the composition of %s. Conflict resolution between accessor properties is currently not supported. Class was composed", + ZSTR_VAL(find_first_definition(ce, traits, i, prop_name, colliding_prop->ce)->name), + ZSTR_VAL(property_info->ce->name), + ZSTR_VAL(prop_name), + ZSTR_VAL(ce->name)); + } + continue; } } @@ -2084,6 +2206,24 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent GC_ADDREF(new_prop->attributes); } } + if (property_info->accessors) { + zend_property_accessors *accessors = new_prop->accessors = + emalloc(sizeof(zend_property_accessors)); + memcpy(accessors, property_info->accessors, sizeof(zend_property_accessors)); + if (accessors->get) { + accessors->get = zend_duplicate_function(accessors->get, ce, false); + if (accessors->get->common.fn_flags & ZEND_ACC_ABSTRACT) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } + } + if (accessors->set) { + accessors->set = zend_duplicate_function(accessors->set, ce, false); + if (accessors->set->common.fn_flags & ZEND_ACC_ABSTRACT) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } + } + ce->ce_flags |= traits[i]->ce_flags & ZEND_ACC_USE_GUARDS; + } } ZEND_HASH_FOREACH_END(); } } @@ -2147,6 +2287,12 @@ static void zend_verify_abstract_class_function(zend_function *fn, zend_abstract } /* }}} */ +static void zend_verify_accessor(zend_function *fn, zend_abstract_info *ai) { + if (fn && (fn->common.fn_flags & ZEND_ACC_ABSTRACT)) { + zend_verify_abstract_class_function(fn, ai); + } +} + void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ { zend_function *func; @@ -2164,6 +2310,16 @@ 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->accessors) { + zend_verify_accessor(prop_info->accessors->get, &ai); + zend_verify_accessor(prop_info->accessors->set, &ai); + } + } ZEND_HASH_FOREACH_END(); + } + if (ai.cnt) { zend_error_noreturn(E_ERROR, !is_explicit_abstract ? "Class %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 ")" @@ -2200,6 +2356,7 @@ typedef struct { struct { const zend_property_info *parent_prop; const zend_property_info *child_prop; + prop_variance variance; }; }; } variance_obligation; @@ -2267,12 +2424,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); } @@ -2309,14 +2467,15 @@ static int check_variance_obligation(zval *zv) { /* Either the compatibility check was successful or only threw a warning. */ } else { ZEND_ASSERT(obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY); - inheritance_status status = - property_types_compatible(obligation->parent_prop, obligation->child_prop); + inheritance_status status = property_types_compatible( + obligation->parent_prop, obligation->child_prop, obligation->variance); if (status != INHERITANCE_SUCCESS) { if (status == INHERITANCE_UNRESOLVED) { return ZEND_HASH_APPLY_KEEP; } ZEND_ASSERT(status == INHERITANCE_ERROR); - emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); + emit_incompatible_property_error( + obligation->child_prop, obligation->parent_prop, obligation->variance); } } return ZEND_HASH_APPLY_REMOVE; @@ -2378,7 +2537,8 @@ static void report_variance_errors(zend_class_entry *ce) { &obligation->child_fn, obligation->child_scope, &obligation->parent_fn, obligation->parent_scope, status); } else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) { - emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); + emit_incompatible_property_error( + obligation->child_prop, obligation->parent_prop, obligation->variance); } else { zend_error_noreturn(E_CORE_ERROR, "Bug #78647"); } @@ -2831,7 +2991,8 @@ 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)); if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { return status; } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index fcbdcb622261b..e3844bf4018ff 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -270,10 +270,11 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type attribute_decl attribute attributes attribute_group namespace_declaration_name %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 accessor accessor_list accessor_property optional_parameter_list %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier optional_visibility_modifier -%type class_modifiers class_modifier use_type backup_fn_flags +%type class_modifiers class_modifier use_type backup_fn_flags accessor_modifiers %type backup_lex_pos %type backup_doc_comment @@ -876,6 +877,9 @@ attributed_class_statement: variable_modifiers optional_type_without_static property_list ';' { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, $3, NULL); $$->attr = $1; } + | variable_modifiers optional_type_without_static accessor_property + { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, zend_ast_create_list(1, ZEND_AST_PROP_DECL, $3), NULL); + $$->attr = $1; } | method_modifiers T_CONST class_const_list ';' { $$ = zend_ast_create(ZEND_AST_CLASS_CONST_GROUP, $3, NULL); $$->attr = $1; } @@ -983,9 +987,45 @@ 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); } +; + +accessor_property: + T_VARIABLE backup_doc_comment '{' accessor_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 '{' accessor_list '}' + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), $6); } +; + +accessor_list: + %empty { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); } + | accessor_list accessor { $$ = zend_ast_list_add($1, $2); } +; + +accessor_modifiers: + %empty { $$ = 0; } + | non_empty_member_modifiers { $$ = $1; } +; + +accessor: + accessor_modifiers returns_ref T_STRING + backup_doc_comment { $$ = CG(zend_lineno); } + optional_parameter_list return_type '{' inner_statement_list '}' + { $$ = zend_ast_create_decl( + ZEND_AST_ACCESSOR, $1 | $2, $5, $4, zend_ast_get_str($3), + $6, NULL, $9, $7, NULL); } + | accessor_modifiers returns_ref T_STRING + backup_doc_comment { $$ = CG(zend_lineno); } ';' + { $$ = zend_ast_create_decl( + ZEND_AST_ACCESSOR, $1 | $2, $5, $4, zend_ast_get_str($3), + NULL, NULL, NULL, NULL, NULL); } +; + +optional_parameter_list: + %empty { $$ = NULL; } + | '(' parameter_list ')' { $$ = $2; } ; class_const_list: @@ -1332,8 +1372,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 @@ -1370,8 +1408,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_object_handlers.c b/Zend/zend_object_handlers.c index 950863629eaa9..0b196bf5aa709 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -33,7 +33,8 @@ #define DEBUG_OBJECT_HANDLERS 0 -#define ZEND_WRONG_PROPERTY_OFFSET 0 +#define ZEND_WRONG_PROPERTY_OFFSET 0 +#define ZEND_ACCESSOR_PROPERTY_OFFSET 1 /* guard flags */ #define IN_GET (1<<0) @@ -365,6 +366,15 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c return ZEND_DYNAMIC_PROPERTY_OFFSET; } + if (property_info->accessors) { + *info_ptr = property_info; + if (cache_slot) { + CACHE_POLYMORPHIC_PTR_EX(cache_slot, ce, (void*)ZEND_ACCESSOR_PROPERTY_OFFSET); + CACHE_PTR_EX(cache_slot + 2, property_info); + } + return ZEND_ACCESSOR_PROPERTY_OFFSET; + } + offset = property_info->offset; if (EXPECTED(!ZEND_TYPE_IS_SET(property_info->type))) { property_info = NULL; @@ -379,12 +389,71 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c } /* }}} */ -static ZEND_COLD void zend_wrong_offset(zend_class_entry *ce, zend_string *member) /* {{{ */ -{ - zend_property_info *dummy; +static ZEND_COLD zend_never_inline void zend_bad_accessor_call( + zend_function *fbc, zend_class_entry *scope) { + zend_throw_error(NULL, "Call to %s accessor %s::%s() from %s%s", + zend_visibility_string(fbc->common.fn_flags), + ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name), + scope ? "scope " : "global scope", + scope ? ZSTR_VAL(scope->name) : ""); +} + +static zend_always_inline zend_function *check_accessor_visibility( + zend_property_info **prop, zend_class_entry *ce, zend_string *name, + zend_function *accessor, bool silent) { + if (!(accessor->common.fn_flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED))) { + return accessor; + } + + zend_class_entry *scope = zend_get_executed_scope(); + if (accessor->common.scope == scope) { + return accessor; + } + + if (accessor->common.fn_flags & ZEND_ACC_CHANGED) { + if (scope != ce && scope && is_derived_class(ce, scope)) { + zend_property_info *scope_prop = zend_hash_find_ptr(&scope->properties_info, name); + if (scope_prop && scope_prop->accessors) { + // TODO: Make accessors an array so index comparison is possible? + zend_function *scope_accessor; + if ((*prop)->accessors->get == accessor) { + scope_accessor = scope_prop->accessors->get; + } else { + ZEND_ASSERT((*prop)->accessors->set == accessor); + scope_accessor = scope_prop->accessors->set; + } + if ((scope_accessor->common.fn_flags & ZEND_ACC_PRIVATE) + && scope_accessor->common.scope == scope) { + *prop = scope_prop; + return scope_accessor; + } + } + } + if (accessor->common.fn_flags & ZEND_ACC_PUBLIC) { + return accessor; + } + } + + if ((accessor->common.fn_flags & ZEND_ACC_PROTECTED) && + zend_check_protected(zend_get_function_root_class(accessor), scope)) { + return accessor; + } + if (!silent) { + zend_bad_accessor_call(accessor, scope); + } + return NULL; +} + +static ZEND_COLD void zend_wrong_offset(zend_object *zobj, zend_string *member, bool read) /* {{{ */ +{ /* Trigger the correct error */ - zend_get_property_offset(ce, member, 0, NULL, &dummy); + zend_property_info *prop_info; + uint32_t offset = zend_get_property_offset(zobj->ce, member, 0, NULL, &prop_info); + if (IS_ACCESSOR_PROPERTY_OFFSET(offset)) { + zend_function *accessor = read ? prop_info->accessors->get : prop_info->accessors->set; + check_accessor_visibility(&prop_info, zobj->ce, member, accessor, /* silent */ false); + } } /* }}} */ @@ -575,6 +644,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int /* make zend_get_property_info silent if we have getter - we may want to use it */ property_offset = zend_get_property_offset(zobj->ce, name, (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot, &prop_info); +try_again: if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { retval = OBJ_PROP(zobj, property_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { @@ -612,6 +682,51 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto exit; } } + } else if (IS_ACCESSOR_PROPERTY_OFFSET(property_offset)) { + zend_function *get = prop_info->accessors->get; + if (!get) { + zend_throw_error(NULL, "Property %s::$%s is write-only", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(uninitialized_zval); + } + + bool silent = type == BP_VAR_IS || zobj->ce->__get != NULL; + get = check_accessor_visibility(&prop_info, zobj->ce, name, get, silent); + if (get) { + if (!(get->op_array.fn_flags & ZEND_ACC_AUTO_PROP)) { + guard = zend_get_property_guard(zobj, name); + if (!((*guard) & IN_GET)) { + GC_ADDREF(zobj); + *guard |= IN_GET; + zend_call_known_instance_method_with_0_params(get, zobj, rv); + *guard &= ~IN_GET; + + 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)) { + if (UNEXPECTED(Z_TYPE_P(rv) != IS_OBJECT)) { + zend_error(E_NOTICE, "Indirect modification of accessor property %s::$%s has no effect (did you mean to use \"&get\"?)", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + } + } + } else { + retval = &EG(uninitialized_zval); + } + + /* The return type is already enforced through the method return type. */ + OBJ_RELEASE(zobj); + goto exit; + } + } + + property_offset = prop_info->offset; + goto try_again; + } else if (!silent) { + return &EG(uninitialized_zval); + } + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } } else if (UNEXPECTED(EG(exception))) { retval = &EG(uninitialized_zval); goto exit; @@ -678,9 +793,10 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int OBJ_RELEASE(zobj); goto exit; - } else if (UNEXPECTED(IS_WRONG_PROPERTY_OFFSET(property_offset))) { + } else if (UNEXPECTED(IS_WRONG_PROPERTY_OFFSET(property_offset) + || IS_ACCESSOR_PROPERTY_OFFSET(property_offset))) { /* Trigger the correct error */ - zend_get_property_offset(zobj->ce, name, 0, NULL, &prop_info); + zend_wrong_offset(zobj, name, /* read */ true); ZEND_ASSERT(EG(exception)); retval = &EG(uninitialized_zval); goto exit; @@ -722,6 +838,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); +try_again: if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { variable_ptr = OBJ_PROP(zobj, property_offset); if (Z_TYPE_P(variable_ptr) != IS_UNDEF) { @@ -758,6 +875,37 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva goto found; } } + } else if (IS_ACCESSOR_PROPERTY_OFFSET(property_offset)) { + zend_function *set = prop_info->accessors->set; + if (!set) { + zend_throw_error(NULL, "Property %s::$%s is read-only", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(error_zval); + } + + bool silent = zobj->ce->__set != NULL; + set = check_accessor_visibility(&prop_info, zobj->ce, name, set, silent); + if (set) { + if (!(set->op_array.fn_flags & ZEND_ACC_AUTO_PROP)) { + uint32_t *guard = zend_get_property_guard(zobj, name); + if (!((*guard) & IN_SET)) { + GC_ADDREF(zobj); + (*guard) |= IN_SET; + zend_call_known_instance_method_with_1_params(set, zobj, NULL, value); + (*guard) &= ~IN_SET; + OBJ_RELEASE(zobj); + return value; + } + } + + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } else if (!silent) { + return &EG(error_zval); + } } else if (UNEXPECTED(EG(exception))) { variable_ptr = &EG(error_zval); goto exit; @@ -774,11 +922,12 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva (*guard) &= ~IN_SET; OBJ_RELEASE(zobj); variable_ptr = value; - } else if (EXPECTED(!IS_WRONG_PROPERTY_OFFSET(property_offset))) { + } else if (EXPECTED(!IS_WRONG_PROPERTY_OFFSET(property_offset) + && !IS_ACCESSOR_PROPERTY_OFFSET(property_offset))) { goto write_std_property; } else { /* Trigger the correct error */ - zend_wrong_offset(zobj->ce, name); + zend_wrong_offset(zobj, name, /* read */ false); ZEND_ASSERT(EG(exception)); variable_ptr = &EG(error_zval); goto exit; @@ -988,7 +1137,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam zend_error(E_WARNING, "Undefined property: %s::$%s", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); } } - } else if (zobj->ce->__get == NULL) { + } else if (!IS_ACCESSOR_PROPERTY_OFFSET(property_offset) && zobj->ce->__get == NULL) { retval = &EG(error_zval); } @@ -1036,6 +1185,12 @@ 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_ACCESSOR_PROPERTY_OFFSET(property_offset)) { + if (!zobj->ce->__unset) { + zend_throw_error(NULL, "Cannot unset accessor property %s::$%s", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return; + } } else if (UNEXPECTED(EG(exception))) { return; } @@ -1050,9 +1205,12 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void (*guard) &= ~IN_UNSET; } else if (UNEXPECTED(IS_WRONG_PROPERTY_OFFSET(property_offset))) { /* Trigger the correct error */ - zend_wrong_offset(zobj->ce, name); + zend_wrong_offset(zobj, name, /* read */ false); ZEND_ASSERT(EG(exception)); return; + } else if (UNEXPECTED(IS_ACCESSOR_PROPERTY_OFFSET(property_offset))) { + zend_throw_error(NULL, "Cannot unset accessor property %s::$%s", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); } else { /* Nothing to do: The property already does not exist. */ } @@ -1648,6 +1806,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); +try_again: if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { value = OBJ_PROP(zobj, property_offset); if (Z_TYPE_P(value) != IS_UNDEF) { @@ -1697,6 +1856,42 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has goto exit; } } + } else if (IS_ACCESSOR_PROPERTY_OFFSET(property_offset)) { + zend_function *get = prop_info->accessors->get; + if (!get) { + zend_throw_error(NULL, "Property %s::$%s is write-only", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return 0; + } + + get = check_accessor_visibility(&prop_info, zobj->ce, name, get, /* silent */ true); + if (get) { + if (has_set_exists == ZEND_PROPERTY_EXISTS) { + return 1; + } + + if (!(get->common.fn_flags & ZEND_ACC_AUTO_PROP)) { + uint32_t *guard = zend_get_property_guard(zobj, name); + if (!(*guard & IN_GET)) { + zval rv; + GC_ADDREF(zobj); + *guard |= IN_GET; + zend_call_known_instance_method_with_0_params(get, zobj, &rv); + *guard &= ~IN_GET; + OBJ_RELEASE(zobj); + + result = has_set_exists == ZEND_PROPERTY_NOT_EMPTY + ? i_zend_is_true(&rv) + : (Z_TYPE(rv) != IS_NULL + && (Z_TYPE(rv) != IS_REFERENCE || Z_TYPE_P(Z_REFVAL(rv)) != IS_NULL)); + zval_ptr_dtor(&rv); + return result; + } + } + + property_offset = prop_info->offset; + goto try_again; + } } else if (UNEXPECTED(EG(exception))) { result = 0; goto exit; diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 53eef829282ce..b17bd7f16d38b 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -27,8 +27,9 @@ struct _zend_property_info; #define ZEND_DYNAMIC_PROPERTY_OFFSET ((uintptr_t)(intptr_t)(-1)) -#define IS_VALID_PROPERTY_OFFSET(offset) ((intptr_t)(offset) > 0) +#define IS_VALID_PROPERTY_OFFSET(offset) ((intptr_t)(offset) > 1) #define IS_WRONG_PROPERTY_OFFSET(offset) ((intptr_t)(offset) == 0) +#define IS_ACCESSOR_PROPERTY_OFFSET(offset) ((intptr_t)(offset) == 1) #define IS_DYNAMIC_PROPERTY_OFFSET(offset) ((intptr_t)(offset) < 0) #define IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(offset) (offset == ZEND_DYNAMIC_PROPERTY_OFFSET) diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0386a43e09cac..c1f3825f8cecd 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -414,6 +414,15 @@ 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->accessors) { + if (prop_info->accessors->get) { + destroy_op_array(&prop_info->accessors->get->op_array); + } + if (prop_info->accessors->set) { + destroy_op_array(&prop_info->accessors->set->op_array); + } + efree(prop_info->accessors); + } } } ZEND_HASH_FOREACH_END(); zend_hash_destroy(&ce->properties_info); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 51130b6b5ad79..b4978f252d68a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2054,20 +2054,33 @@ ZEND_VM_C_LABEL(fetch_obj_r_fast_copy): ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) { + ZEND_VM_C_GOTO(fetch_obj_r_copy); + } else { + ZEND_VM_C_GOTO(fetch_obj_r_fast_copy); + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) { ZEND_VM_C_GOTO(fetch_obj_r_copy); } else { @@ -2075,18 +2088,10 @@ ZEND_VM_C_LABEL(fetch_obj_r_fast_copy): } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) { - ZEND_VM_C_GOTO(fetch_obj_r_copy); - } else { - ZEND_VM_C_GOTO(fetch_obj_r_fast_copy); - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); @@ -2210,20 +2215,33 @@ ZEND_VM_C_LABEL(fetch_obj_is_fast_copy): ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) { + ZEND_VM_C_GOTO(fetch_obj_is_copy); + } else { + ZEND_VM_C_GOTO(fetch_obj_is_fast_copy); + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) { ZEND_VM_C_GOTO(fetch_obj_is_copy); } else { @@ -2231,18 +2249,10 @@ ZEND_VM_C_LABEL(fetch_obj_is_fast_copy): } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) { - ZEND_VM_C_GOTO(fetch_obj_is_copy); - } else { - ZEND_VM_C_GOTO(fetch_obj_is_fast_copy); - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); @@ -2401,7 +2411,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)) { @@ -2450,6 +2460,9 @@ ZEND_VM_C_LABEL(fast_assign_obj): } ZEND_VM_C_GOTO(exit_assign_obj); } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 209c77650ca36..f3b41c75ccea6 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -6235,20 +6235,33 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -6256,18 +6269,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -6353,20 +6358,33 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -6374,18 +6392,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -8562,20 +8572,33 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -8583,18 +8606,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -8680,20 +8695,33 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -8701,18 +8729,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -10912,20 +10932,33 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -10933,18 +10966,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -11030,20 +11055,33 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -11051,18 +11089,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CONST & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -15331,20 +15361,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CONST_ ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -15352,18 +15395,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CONST_ } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -15449,20 +15484,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CONST ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -15470,18 +15518,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CONST } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -16751,20 +16791,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_TMPVAR ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -16772,18 +16825,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_TMPVAR } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -16869,20 +16914,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_TMPVA ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -16890,18 +16948,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_TMPVA } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -18063,20 +18113,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CV_HAN ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -18084,18 +18147,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CV_HAN } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -18181,20 +18236,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CV_HA ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -18202,18 +18270,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CV_HA } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || ((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -22789,7 +22849,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)) { @@ -22838,6 +22898,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -22931,7 +22994,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)) { @@ -22980,6 +23043,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23073,7 +23139,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)) { @@ -23122,6 +23188,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23215,7 +23284,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)) { @@ -23264,6 +23333,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -25367,7 +25439,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)) { @@ -25416,6 +25488,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -25509,7 +25584,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)) { @@ -25558,6 +25633,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -25651,7 +25729,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)) { @@ -25700,6 +25778,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -25793,7 +25874,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)) { @@ -25842,6 +25923,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -29293,7 +29377,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)) { @@ -29342,6 +29426,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -29435,7 +29522,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)) { @@ -29484,6 +29571,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -29577,7 +29667,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)) { @@ -29626,6 +29716,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -29719,7 +29812,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)) { @@ -29768,6 +29861,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -31400,20 +31496,33 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -31421,18 +31530,10 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -31561,20 +31662,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CONST ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -31582,18 +31696,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CONST } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -31717,7 +31823,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)) { @@ -31766,6 +31872,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -31859,7 +31968,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)) { @@ -31908,6 +32017,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -32001,7 +32113,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)) { @@ -32050,6 +32162,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -32143,7 +32258,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)) { @@ -32192,6 +32307,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33301,20 +33419,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_TMPVAR ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -33322,18 +33453,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_TMPVAR } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -33457,20 +33580,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_TMPVA ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -33478,18 +33614,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_TMPVA } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -33613,7 +33741,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)) { @@ -33662,6 +33790,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -33755,7 +33886,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)) { @@ -33804,6 +33935,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -33897,7 +34031,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)) { @@ -33946,6 +34080,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -34039,7 +34176,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)) { @@ -34088,6 +34225,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -35795,20 +35935,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_CV_HAN ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -35816,18 +35969,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_CV_HAN } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -35951,20 +36096,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CV_HA ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -35972,18 +36130,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CV_HA } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_UNUSED & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -36107,7 +36257,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)) { @@ -36156,6 +36306,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -36249,7 +36402,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)) { @@ -36298,6 +36451,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -36391,7 +36547,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)) { @@ -36440,6 +36596,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -36533,7 +36692,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)) { @@ -36582,6 +36741,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -39971,20 +40133,33 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -39992,18 +40167,10 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40132,20 +40299,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CONST_HAN ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -40153,18 +40333,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CONST_HAN } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40288,7 +40460,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)) { @@ -40337,6 +40509,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40430,7 +40605,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)) { @@ -40479,6 +40654,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40572,7 +40750,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)) { @@ -40621,6 +40799,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40714,7 +40895,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)) { @@ -40763,6 +40944,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -43625,20 +43809,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_TMPVAR_HAN ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -43646,18 +43843,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_TMPVAR_HAN } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -43781,20 +43970,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_TMPVAR_HA ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -43802,18 +44004,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_TMPVAR_HA } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -43937,7 +44131,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)) { @@ -43986,6 +44180,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -44079,7 +44276,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)) { @@ -44128,6 +44325,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -44221,7 +44421,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)) { @@ -44270,6 +44470,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -44363,7 +44566,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)) { @@ -44412,6 +44615,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -48656,20 +48862,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_CV_HANDLER ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_r_copy; + } else { + goto fetch_obj_r_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_r_copy; } else { @@ -48677,18 +48896,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_CV_HANDLER } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_r_copy; - } else { - goto fetch_obj_r_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -48812,20 +49023,33 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CV_HANDLE ZEND_VM_NEXT_OPCODE(); } } - } else if (EXPECTED(zobj->properties != NULL)) { - 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); - - if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { - Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); - - if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && - (EXPECTED(p->key == name) || - (EXPECTED(p->h == ZSTR_H(name)) && - EXPECTED(p->key != NULL) && - EXPECTED(zend_string_equal_content(p->key, name))))) { - retval = &p->val; + } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { + if (EXPECTED(zobj->properties != NULL)) { + 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); + + if (EXPECTED(idx < zobj->properties->nNumUsed * sizeof(Bucket))) { + Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx); + + if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF) && + (EXPECTED(p->key == name) || + (EXPECTED(p->h == ZSTR_H(name)) && + EXPECTED(p->key != NULL) && + EXPECTED(zend_string_equal_content(p->key, name))))) { + retval = &p->val; + if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { + goto fetch_obj_is_copy; + } else { + goto fetch_obj_is_fast_copy; + } + } + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); + } + retval = zend_hash_find_ex(zobj->properties, name, 1); + if (EXPECTED(retval)) { + uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; + CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { goto fetch_obj_is_copy; } else { @@ -48833,18 +49057,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CV_HANDLE } } } - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_DYNAMIC_PROPERTY_OFFSET); - } - retval = zend_hash_find_ex(zobj->properties, name, 1); - if (EXPECTED(retval)) { - uintptr_t idx = (char*)retval - (char*)zobj->properties->arData; - CACHE_PTR_EX(cache_slot + 1, (void*)ZEND_ENCODE_DYN_PROP_OFFSET(idx)); - if (0 || (IS_CV & (IS_TMP_VAR|IS_VAR)) != 0) { - goto fetch_obj_is_copy; - } else { - goto fetch_obj_is_fast_copy; - } } + } else { + /* Fall through to read_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -48968,7 +49184,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)) { @@ -49017,6 +49233,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -49110,7 +49329,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)) { @@ -49159,6 +49378,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -49252,7 +49474,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)) { @@ -49301,6 +49523,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -49394,7 +49619,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)) { @@ -49443,6 +49668,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for accessors. */ + ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 13fd3dfebf9f8..7eabc89bfd608 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -732,9 +732,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) { @@ -742,36 +741,35 @@ 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; - } else { - op_array = Z_PTR_P(zv) = zend_shared_memdup_put(op_array, sizeof(zend_internal_function)); - if (op_array->scope) { - void *persist_ptr; + return old_op_array; + } - if ((persist_ptr = zend_shared_alloc_get_xlat_entry(op_array->scope))) { - op_array->scope = (zend_class_entry*)persist_ptr; - } - if (op_array->prototype) { - if ((persist_ptr = zend_shared_alloc_get_xlat_entry(op_array->prototype))) { - op_array->prototype = (zend_function*)persist_ptr; - } + op_array = zend_shared_memdup_put(op_array, sizeof(zend_internal_function)); + if (op_array->scope) { + void *persist_ptr; + + if ((persist_ptr = zend_shared_alloc_get_xlat_entry(op_array->scope))) { + op_array->scope = (zend_class_entry*)persist_ptr; + } + if (op_array->prototype) { + if ((persist_ptr = zend_shared_alloc_get_xlat_entry(op_array->prototype))) { + op_array->prototype = (zend_function*)persist_ptr; } } } } - 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); } @@ -783,9 +781,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_LINKED) && (ce->ce_flags & ZEND_ACC_IMMUTABLE)) { @@ -800,6 +799,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) @@ -825,6 +825,18 @@ static zend_property_info *zend_persist_property_info(zend_property_info *prop) if (prop->attributes) { prop->attributes = zend_persist_attributes(prop->attributes); } + if (prop->accessors) { + prop->accessors = + zend_shared_memdup_put_free(prop->accessors, sizeof(zend_property_accessors)); + if (prop->accessors->get) { + prop->accessors->get = (zend_function *) zend_persist_class_method( + (zend_op_array *) prop->accessors->get, ce); + } + if (prop->accessors->set) { + prop->accessors->set = (zend_function *) zend_persist_class_method( + (zend_op_array *) prop->accessors->set, ce); + } + } zend_persist_type(&prop->type, ce); return prop; } @@ -905,7 +917,7 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) ZEND_HASH_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 2d951863a30e2..54f43327e4dfb 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -313,9 +313,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) { @@ -324,7 +323,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; @@ -341,8 +340,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. */ @@ -365,6 +364,15 @@ static void zend_persist_property_info_calc(zend_property_info *prop) if (prop->attributes) { zend_persist_attributes_calc(prop->attributes); } + if (prop->accessors) { + ADD_SIZE(sizeof(zend_property_accessors)); + if (prop->accessors->get) { + zend_persist_class_method_calc((zend_op_array *) prop->accessors->get); + } + if (prop->accessors->set) { + zend_persist_class_method_calc((zend_op_array *) prop->accessors->set); + } + } } static void zend_persist_class_constant_calc(zval *zv) @@ -412,7 +420,7 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) ZEND_HASH_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/tests/classes/interface_member.phpt b/tests/classes/interface_member.phpt index 04fff625f3aa4..bab98ec7cf196 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 accessor properties in %s on line %d From 6a7269ba8da1de75e9e063a98105b0f2b02c1c82 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 11:14:09 +0200 Subject: [PATCH 02/44] Support (generated) accessors in property promotion --- Zend/tests/accessors/property_promotion.phpt | 28 ++++++++++ .../accessors/property_promotion_invalid.phpt | 17 ++++++ Zend/zend_ast.c | 52 +++++++++++-------- Zend/zend_ast.h | 22 ++++---- Zend/zend_compile.c | 18 ++++++- Zend/zend_language_parser.y | 15 ++++-- 6 files changed, 112 insertions(+), 40 deletions(-) create mode 100644 Zend/tests/accessors/property_promotion.phpt create mode 100644 Zend/tests/accessors/property_promotion_invalid.phpt diff --git a/Zend/tests/accessors/property_promotion.phpt b/Zend/tests/accessors/property_promotion.phpt new file mode 100644 index 0000000000000..62e26d25cef45 --- /dev/null +++ b/Zend/tests/accessors/property_promotion.phpt @@ -0,0 +1,28 @@ +--TEST-- +Generated accessors in property promotion +--FILE-- +x, $vec->y, $vec->z); + +try { + $vec->x = 42.0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +float(0) +float(1) +float(2) +Call to private accessor ImmVec::$x::set() from global scope diff --git a/Zend/tests/accessors/property_promotion_invalid.phpt b/Zend/tests/accessors/property_promotion_invalid.phpt new file mode 100644 index 0000000000000..0363679ec7243 --- /dev/null +++ b/Zend/tests/accessors/property_promotion_invalid.phpt @@ -0,0 +1,17 @@ +--TEST-- +Complex accessors in promotion not allowed +--FILE-- + +--EXPECTF-- +Fatal error: Cannot declare complex accessors for a promoted property in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index a09c13fd89ece..568b0f2a19996 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -244,34 +244,40 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_4(zend_ast_kind kind, zend_ast return ast; } -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_ast *ast; - uint32_t lineno; - - ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 5); - ast = zend_ast_alloc(zend_ast_size(5)); +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 = 0; - ast->child[0] = child1; - ast->child[1] = child2; - ast->child[2] = child3; - ast->child[3] = child4; - ast->child[4] = child5; - if (child1) { - lineno = zend_ast_get_lineno(child1); - } else if (child2) { - lineno = zend_ast_get_lineno(child2); - } else if (child3) { - lineno = zend_ast_get_lineno(child3); - } else if (child4) { - lineno = zend_ast_get_lineno(child4); - } else if (child5) { - lineno = zend_ast_get_lineno(child5); - } else { + 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(zend_ast_kind 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, zend_ast_attr attr, ...) { + va_list va; + va_start(va, attr); + zend_ast *ast = zend_ast_create_va(kind, attr, &va); + va_end(va); return ast; } diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index e657a17014fd1..8b71451181cc3 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -170,7 +170,9 @@ enum _zend_ast_kind { 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; @@ -226,12 +228,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, _n, _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, _n, _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); @@ -239,7 +241,10 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_1(zend_ast_kind kind, zend_ast ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2); ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_3(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3); 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); +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_n(zend_ast_kind kind, ...); +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_ex_n(zend_ast_kind kind, zend_ast_attr 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); @@ -266,11 +271,6 @@ static zend_always_inline zend_ast * zend_ast_create_ex_4(zend_ast_kind kind, ze ast->attr = attr; return ast; } -static zend_always_inline zend_ast * zend_ast_create_ex_5(zend_ast_kind kind, zend_ast_attr attr, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4, zend_ast *child5) { - zend_ast *ast = zend_ast_create_5(kind, child1, child2, child3, child4, child5); - ast->attr = attr; - return ast; -} ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind); ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_1(zend_ast_kind kind, zend_ast *child); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 75d6b212ae4ce..aa2c6f486f7d8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6460,6 +6460,9 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3 } /* }}} */ +static void zend_compile_accessors( + zend_property_info *prop_info, zend_ast *prop_type_ast, zend_ast_list *accessors); + 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); @@ -6496,6 +6499,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall 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 *accessors_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; @@ -6662,6 +6666,17 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall 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, visibility | ZEND_ACC_PROMOTED, doc_comment, type); + if (accessors_ast) { + zend_ast_list *accessors = zend_ast_get_list(accessors_ast); + for (uint32_t i = 0; i < accessors->children; i++) { + zend_ast_decl *accessor = (zend_ast_decl *) accessors->child[i]; + if (accessor->child[2]) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot declare complex accessors for a promoted property"); + } + } + zend_compile_accessors(prop, type_ast, accessors); + } if (attributes_ast) { zend_compile_attributes( &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY); @@ -7220,7 +7235,8 @@ static void zend_compile_accessors( zend_ast *param_name_ast = zend_ast_create_zval_from_str(param_name); zend_ast *param = zend_ast_create( ZEND_AST_PARAM, prop_type_ast, param_name_ast, - /* expr */ NULL, /* doc_comment */ NULL, /* attributes */ NULL); + /* expr */ NULL, /* doc_comment */ NULL, /* attributes */ NULL, + /* accessors */ NULL); accessor->child[0] = zend_ast_create_list(1, ZEND_AST_PARAM_LIST, param); reset_param_type_ast = true; } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e3844bf4018ff..e47e26d76c854 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -270,7 +270,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type attribute_decl attribute attributes attribute_group namespace_declaration_name %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 accessor accessor_list accessor_property optional_parameter_list +%type accessor accessor_list accessor_property optional_parameter_list optional_accessor_list %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier optional_visibility_modifier @@ -765,15 +765,20 @@ optional_visibility_modifier: | T_PRIVATE { $$ = ZEND_ACC_PRIVATE; } ; +optional_accessor_list: + %empty { $$ = NULL; } + | '{' accessor_list '}' { $$ = $2; } +; + parameter: optional_visibility_modifier optional_type_without_static - is_reference is_variadic T_VARIABLE backup_doc_comment + is_reference is_variadic T_VARIABLE backup_doc_comment optional_accessor_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_visibility_modifier 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_accessor_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); } ; From 6f6f4a248d548735d7046735a48b3be7346b31b3 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 11:28:01 +0200 Subject: [PATCH 03/44] Suppress compile warning --- Zend/zend_object_handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 0b196bf5aa709..cd45255c566fb 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -448,7 +448,7 @@ static zend_always_inline zend_function *check_accessor_visibility( static ZEND_COLD void zend_wrong_offset(zend_object *zobj, zend_string *member, bool read) /* {{{ */ { /* Trigger the correct error */ - zend_property_info *prop_info; + zend_property_info *prop_info = NULL; uint32_t offset = zend_get_property_offset(zobj->ce, member, 0, NULL, &prop_info); if (IS_ACCESSOR_PROPERTY_OFFSET(offset)) { zend_function *accessor = read ? prop_info->accessors->get : prop_info->accessors->set; From bce7dda04b4717e2d20b6a8a765f6ea260541367 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 11:32:56 +0200 Subject: [PATCH 04/44] Add basic reflection support This doesn't properly handle auto-generated accessors yet. --- ext/reflection/php_reflection.c | 34 +++++++++++ ext/reflection/php_reflection.stub.php | 4 ++ ext/reflection/php_reflection_arginfo.h | 11 +++- ext/reflection/tests/accessors.phpt | 77 +++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 ext/reflection/tests/accessors.phpt diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 757627de47807..74c972920a2a0 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5727,6 +5727,40 @@ ZEND_METHOD(ReflectionProperty, getDefaultValue) } /* }}} */ +ZEND_METHOD(ReflectionProperty, getGet) +{ + reflection_object *intern; + property_reference *ref; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(ref); + + if (!ref->prop->accessors || !ref->prop->accessors->get) { + RETURN_NULL(); + } + + reflection_method_factory( + ref->prop->accessors->get->common.scope, ref->prop->accessors->get, NULL, return_value); +} + +ZEND_METHOD(ReflectionProperty, getSet) +{ + reflection_object *intern; + property_reference *ref; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(ref); + + if (!ref->prop->accessors || !ref->prop->accessors->set) { + RETURN_NULL(); + } + + reflection_method_factory( + ref->prop->accessors->set->common.scope, ref->prop->accessors->set, NULL, return_value); +} + /* {{{ Constructor. Throws an Exception in case the given extension does not exist */ ZEND_METHOD(ReflectionExtension, __construct) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index fe8def14b72b2..baad157660a15 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -441,6 +441,10 @@ public function getDefaultValue() {} /** @return ReflectionAttribute[] */ public function getAttributes(?string $name = null, int $flags = 0): array {} + + public function getGet(): ?ReflectionMethod {} + + public function getSet(): ?ReflectionMethod {} } class ReflectionClassConstant implements Reflector diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index a6d6bfdd726cf..9b1cf7f74afea 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: 388312e928b54992da6b7e0e0f15dec72d9290f1 */ + * Stub hash: 7bfb995bcef07303f355cd45d8a2c8c67da0ae4a */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -331,6 +331,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionProperty_getGet, 0, 0, ReflectionMethod, 1) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionProperty_getSet arginfo_class_ReflectionProperty_getGet + #define arginfo_class_ReflectionClassConstant___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionClassConstant___construct, 0, 0, 2) @@ -675,6 +680,8 @@ ZEND_METHOD(ReflectionProperty, hasType); ZEND_METHOD(ReflectionProperty, hasDefaultValue); ZEND_METHOD(ReflectionProperty, getDefaultValue); ZEND_METHOD(ReflectionProperty, getAttributes); +ZEND_METHOD(ReflectionProperty, getGet); +ZEND_METHOD(ReflectionProperty, getSet); ZEND_METHOD(ReflectionClassConstant, __construct); ZEND_METHOD(ReflectionClassConstant, __toString); ZEND_METHOD(ReflectionClassConstant, getName); @@ -944,6 +951,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { 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, getGet, arginfo_class_ReflectionProperty_getGet, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, getSet, arginfo_class_ReflectionProperty_getSet, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/tests/accessors.phpt b/ext/reflection/tests/accessors.phpt new file mode 100644 index 0000000000000..ab8f7bf473d51 --- /dev/null +++ b/ext/reflection/tests/accessors.phpt @@ -0,0 +1,77 @@ +--TEST-- +Accessor reflection +--FILE-- +getGet()); +var_dump($rp1->getSet()); +echo "\n"; + +$rp2 = new ReflectionProperty(Test::class, 'prop2'); +var_dump($g = $rp2->getGet()); +var_dump($s = $rp2->getSet()); +// TODO: This is broken. +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); +echo "\n"; + +$rp3 = new ReflectionProperty(Test::class, 'prop3'); +var_dump($g = $rp3->getGet()); +var_dump($s = $rp3->getSet()); +$g->invoke($test); +$s->invoke($test, 42); + +?> +--EXPECT-- +NULL +NULL + +object(ReflectionMethod)#4 (2) { + ["name"]=> + string(11) "$prop2::get" + ["class"]=> + string(4) "Test" +} +object(ReflectionMethod)#5 (2) { + ["name"]=> + string(11) "$prop2::set" + ["class"]=> + string(4) "Test" +} +NULL +Trying to invoke private method Test::$prop2::set() from scope ReflectionMethod +float(3.141) + +object(ReflectionMethod)#8 (2) { + ["name"]=> + string(11) "$prop3::get" + ["class"]=> + string(4) "Test" +} +object(ReflectionMethod)#4 (2) { + ["name"]=> + string(11) "$prop3::set" + ["class"]=> + string(4) "Test" +} +get +set(42) From 484dfa9be4ce75eb8f28a7639d745c0a28215074 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 12:30:22 +0200 Subject: [PATCH 05/44] Represent accessors using an array This allows us to treat all accessors uniformly in most places. This would make it easier to support additional accessor kinds in the future. --- Zend/zend_compile.c | 21 +++++------- Zend/zend_compile.h | 10 +++--- Zend/zend_inheritance.c | 59 +++++++++++++++------------------ Zend/zend_object_handlers.c | 19 ++++++----- Zend/zend_opcode.c | 9 +++-- ext/opcache/zend_persist.c | 14 ++++---- ext/opcache/zend_persist_calc.c | 11 +++--- ext/reflection/php_reflection.c | 27 ++++++--------- 8 files changed, 75 insertions(+), 95 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index aa2c6f486f7d8..36aba24186dc2 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7204,6 +7204,7 @@ static void zend_compile_accessors( zend_ast **return_ast_ptr = &accessor->child[3]; CG(zend_lineno) = accessor->start_lineno; bool reset_return_ast = false, reset_param_type_ast = false; + uint32_t accessor_kind; if (*return_ast_ptr) { zend_error_noreturn(E_COMPILE_ERROR, @@ -7213,6 +7214,7 @@ static void zend_compile_accessors( } if (zend_string_equals_literal_ci(name, "get")) { + accessor_kind = ZEND_ACCESSOR_GET; if (param_list) { if (param_list->children != 0) { zend_error_noreturn(E_COMPILE_ERROR, @@ -7225,6 +7227,7 @@ static void zend_compile_accessors( reset_return_ast = true; *return_ast_ptr = prop_type_ast; } else if (zend_string_equals_literal_ci(name, "set")) { + accessor_kind = ZEND_ACCESSOR_SET; if (param_list) { if (param_list->children != 1 || !is_valid_set_param(param_list->child[0])) { zend_error_noreturn(E_COMPILE_ERROR, @@ -7322,22 +7325,14 @@ static void zend_compile_accessors( } if (!prop_info->accessors) { - prop_info->accessors = ecalloc(1, sizeof(zend_property_accessors)); + prop_info->accessors = ecalloc(1, ZEND_ACCESSOR_STRUCT_SIZE); } - if (zend_string_equals_literal_ci(name, "get")) { - if (prop_info->accessors->get) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare accessor \"get\""); - } - prop_info->accessors->get = func; - } else if (zend_string_equals_literal_ci(name, "set")) { - if (prop_info->accessors->set) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare accessor \"set\""); - } - prop_info->accessors->set = func; - } else { - ZEND_UNREACHABLE(); + if (prop_info->accessors[accessor_kind]) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot redeclare accessor \"%s\"", ZSTR_VAL(name)); } + prop_info->accessors[accessor_kind] = func; zend_string_release(name); /* Un-share type ASTs. Alternatively we could duplicate them. */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 88736cb559f57..a63736c51beb3 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -378,10 +378,10 @@ typedef struct _zend_oparray_context { char *zend_visibility_string(uint32_t fn_flags); -typedef struct _zend_property_accessors { - zend_function *get; - zend_function *set; -} zend_property_accessors; +#define ZEND_ACCESSOR_GET 0 +#define ZEND_ACCESSOR_SET 1 +#define ZEND_ACCESSOR_COUNT 2 +#define ZEND_ACCESSOR_STRUCT_SIZE (sizeof(zend_function) * ZEND_ACCESSOR_COUNT) typedef struct _zend_property_info { uint32_t offset; /* property offset for object properties or @@ -392,7 +392,7 @@ typedef struct _zend_property_info { HashTable *attributes; zend_class_entry *ce; zend_type type; - zend_property_accessors *accessors; + zend_function **accessors; } zend_property_info; #define OBJ_PROP(obj, offset) \ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index b4fc91c9696bc..ef0c61f75c9ff 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1107,10 +1107,10 @@ static void inherit_accessor( static prop_variance prop_get_variance(zend_property_info *prop_info) { if (prop_info->accessors) { - if (!prop_info->accessors->set) { + if (!prop_info->accessors[ZEND_ACCESSOR_SET]) { return PROP_COVARIANT; } - if (!prop_info->accessors->get) { + if (!prop_info->accessors[ZEND_ACCESSOR_GET]) { return PROP_CONTRAVARIANT; } } @@ -1151,8 +1151,8 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke child_info->offset = parent_info->offset; } - zend_property_accessors *parent_accessors = parent_info->accessors; - zend_property_accessors *child_accessors = child_info->accessors; + zend_function **parent_accessors = parent_info->accessors; + zend_function **child_accessors = child_info->accessors; if (child_accessors) { if (!parent_accessors) { zend_error_noreturn(E_COMPILE_ERROR, @@ -1160,8 +1160,9 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key), ZSTR_VAL(ce->name)); } - inherit_accessor(ce, &parent_accessors->get, &child_accessors->get); - inherit_accessor(ce, &parent_accessors->set, &child_accessors->set); + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + inherit_accessor(ce, &parent_accessors[i], &child_accessors[i]); + } } prop_variance variance = prop_get_variance(parent_info); @@ -1183,12 +1184,12 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } } } else { - zend_property_accessors *accessors = parent_info->accessors; + zend_function **accessors = parent_info->accessors; if (accessors) { - if ((accessors->get && (accessors->get->common.fn_flags & ZEND_ACC_ABSTRACT)) || - (accessors->set && (accessors->set->common.fn_flags & ZEND_ACC_ABSTRACT)) - ) { - ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + if (accessors[i] && (accessors[i]->common.fn_flags & ZEND_ACC_ABSTRACT)) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } } } @@ -2207,19 +2208,15 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } } if (property_info->accessors) { - zend_property_accessors *accessors = new_prop->accessors = - emalloc(sizeof(zend_property_accessors)); - memcpy(accessors, property_info->accessors, sizeof(zend_property_accessors)); - if (accessors->get) { - accessors->get = zend_duplicate_function(accessors->get, ce, false); - if (accessors->get->common.fn_flags & ZEND_ACC_ABSTRACT) { - ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; - } - } - if (accessors->set) { - accessors->set = zend_duplicate_function(accessors->set, ce, false); - if (accessors->set->common.fn_flags & ZEND_ACC_ABSTRACT) { - ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + zend_function **accessors = new_prop->accessors = + emalloc(ZEND_ACCESSOR_STRUCT_SIZE); + memcpy(accessors, property_info->accessors, ZEND_ACCESSOR_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + if (accessors[i]) { + accessors[i] = zend_duplicate_function(accessors[i], ce, false); + if (accessors[i]->common.fn_flags & ZEND_ACC_ABSTRACT) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } } } ce->ce_flags |= traits[i]->ce_flags & ZEND_ACC_USE_GUARDS; @@ -2287,12 +2284,6 @@ static void zend_verify_abstract_class_function(zend_function *fn, zend_abstract } /* }}} */ -static void zend_verify_accessor(zend_function *fn, zend_abstract_info *ai) { - if (fn && (fn->common.fn_flags & ZEND_ACC_ABSTRACT)) { - zend_verify_abstract_class_function(fn, ai); - } -} - void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ { zend_function *func; @@ -2314,8 +2305,12 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ zend_property_info *prop_info; ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) { if (prop_info->accessors) { - zend_verify_accessor(prop_info->accessors->get, &ai); - zend_verify_accessor(prop_info->accessors->set, &ai); + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + zend_function *fn = prop_info->accessors[i]; + if (fn && (fn->common.fn_flags & ZEND_ACC_ABSTRACT)) { + zend_verify_abstract_class_function(fn, &ai); + } + } } } ZEND_HASH_FOREACH_END(); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index cd45255c566fb..307d291435b15 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -414,13 +414,13 @@ static zend_always_inline zend_function *check_accessor_visibility( if (scope != ce && scope && is_derived_class(ce, scope)) { zend_property_info *scope_prop = zend_hash_find_ptr(&scope->properties_info, name); if (scope_prop && scope_prop->accessors) { - // TODO: Make accessors an array so index comparison is possible? + // TODO: Pass accessor kind? zend_function *scope_accessor; - if ((*prop)->accessors->get == accessor) { - scope_accessor = scope_prop->accessors->get; + if ((*prop)->accessors[ZEND_ACCESSOR_GET] == accessor) { + scope_accessor = scope_prop->accessors[ZEND_ACCESSOR_GET]; } else { - ZEND_ASSERT((*prop)->accessors->set == accessor); - scope_accessor = scope_prop->accessors->set; + ZEND_ASSERT((*prop)->accessors[ZEND_ACCESSOR_SET] == accessor); + scope_accessor = scope_prop->accessors[ZEND_ACCESSOR_SET]; } if ((scope_accessor->common.fn_flags & ZEND_ACC_PRIVATE) && scope_accessor->common.scope == scope) { @@ -451,7 +451,8 @@ static ZEND_COLD void zend_wrong_offset(zend_object *zobj, zend_string *member, zend_property_info *prop_info = NULL; uint32_t offset = zend_get_property_offset(zobj->ce, member, 0, NULL, &prop_info); if (IS_ACCESSOR_PROPERTY_OFFSET(offset)) { - zend_function *accessor = read ? prop_info->accessors->get : prop_info->accessors->set; + zend_function *accessor = read + ? prop_info->accessors[ZEND_ACCESSOR_GET] : prop_info->accessors[ZEND_ACCESSOR_SET]; check_accessor_visibility(&prop_info, zobj->ce, member, accessor, /* silent */ false); } } @@ -683,7 +684,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int } } } else if (IS_ACCESSOR_PROPERTY_OFFSET(property_offset)) { - zend_function *get = prop_info->accessors->get; + zend_function *get = prop_info->accessors[ZEND_ACCESSOR_GET]; if (!get) { zend_throw_error(NULL, "Property %s::$%s is write-only", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); @@ -876,7 +877,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva } } } else if (IS_ACCESSOR_PROPERTY_OFFSET(property_offset)) { - zend_function *set = prop_info->accessors->set; + zend_function *set = prop_info->accessors[ZEND_ACCESSOR_SET]; if (!set) { zend_throw_error(NULL, "Property %s::$%s is read-only", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); @@ -1857,7 +1858,7 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has } } } else if (IS_ACCESSOR_PROPERTY_OFFSET(property_offset)) { - zend_function *get = prop_info->accessors->get; + zend_function *get = prop_info->accessors[ZEND_ACCESSOR_GET]; if (!get) { zend_throw_error(NULL, "Property %s::$%s is write-only", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index c1f3825f8cecd..9a824cd00e587 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -415,11 +415,10 @@ ZEND_API void destroy_zend_class(zval *zv) } zend_type_release(prop_info->type, /* persistent */ 0); if (prop_info->accessors) { - if (prop_info->accessors->get) { - destroy_op_array(&prop_info->accessors->get->op_array); - } - if (prop_info->accessors->set) { - destroy_op_array(&prop_info->accessors->set->op_array); + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + if (prop_info->accessors[i]) { + destroy_op_array(&prop_info->accessors[i]->op_array); + } } efree(prop_info->accessors); } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 7eabc89bfd608..8a63973f9a135 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -827,14 +827,12 @@ static zend_property_info *zend_persist_property_info(zend_property_info *prop) } if (prop->accessors) { prop->accessors = - zend_shared_memdup_put_free(prop->accessors, sizeof(zend_property_accessors)); - if (prop->accessors->get) { - prop->accessors->get = (zend_function *) zend_persist_class_method( - (zend_op_array *) prop->accessors->get, ce); - } - if (prop->accessors->set) { - prop->accessors->set = (zend_function *) zend_persist_class_method( - (zend_op_array *) prop->accessors->set, ce); + zend_shared_memdup_put_free(prop->accessors, ZEND_ACCESSOR_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + if (prop->accessors[i]) { + prop->accessors[i] = (zend_function *) zend_persist_class_method( + (zend_op_array *) prop->accessors[i], ce); + } } } zend_persist_type(&prop->type, ce); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 54f43327e4dfb..7e4581ae185d2 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -365,12 +365,11 @@ static void zend_persist_property_info_calc(zend_property_info *prop) zend_persist_attributes_calc(prop->attributes); } if (prop->accessors) { - ADD_SIZE(sizeof(zend_property_accessors)); - if (prop->accessors->get) { - zend_persist_class_method_calc((zend_op_array *) prop->accessors->get); - } - if (prop->accessors->set) { - zend_persist_class_method_calc((zend_op_array *) prop->accessors->set); + ADD_SIZE(ZEND_ACCESSOR_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + if (prop->accessors[i]) { + zend_persist_class_method_calc((zend_op_array *) prop->accessors[i]); + } } } } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 74c972920a2a0..3e1ba9653494e 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5727,7 +5727,7 @@ ZEND_METHOD(ReflectionProperty, getDefaultValue) } /* }}} */ -ZEND_METHOD(ReflectionProperty, getGet) +static void get_accessor(INTERNAL_FUNCTION_PARAMETERS, uint32_t accessor) { reflection_object *intern; property_reference *ref; @@ -5736,29 +5736,22 @@ ZEND_METHOD(ReflectionProperty, getGet) GET_REFLECTION_OBJECT_PTR(ref); - if (!ref->prop->accessors || !ref->prop->accessors->get) { + if (!ref->prop->accessors || !ref->prop->accessors[accessor]) { RETURN_NULL(); } - reflection_method_factory( - ref->prop->accessors->get->common.scope, ref->prop->accessors->get, NULL, return_value); + zend_function *fn = ref->prop->accessors[accessor]; + reflection_method_factory(fn->common.scope, fn, NULL, return_value); } -ZEND_METHOD(ReflectionProperty, getSet) +ZEND_METHOD(ReflectionProperty, getGet) { - reflection_object *intern; - property_reference *ref; - - ZEND_PARSE_PARAMETERS_NONE(); - - GET_REFLECTION_OBJECT_PTR(ref); - - if (!ref->prop->accessors || !ref->prop->accessors->set) { - RETURN_NULL(); - } + get_accessor(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACCESSOR_GET); +} - reflection_method_factory( - ref->prop->accessors->set->common.scope, ref->prop->accessors->set, NULL, return_value); +ZEND_METHOD(ReflectionProperty, getSet) +{ + get_accessor(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACCESSOR_SET); } /* {{{ Constructor. Throws an Exception in case the given extension does not exist */ From 672b615288909cb3fa532a7f5cd0638aabc64139 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 15:07:53 +0200 Subject: [PATCH 06/44] Generate proper implementations for auto accessors This allows them to be used from reflection. --- Zend/zend_compile.c | 133 ++++++++++++++++------------ ext/reflection/tests/accessors.phpt | 4 +- 2 files changed, 80 insertions(+), 57 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 36aba24186dc2..a7778db29d2ff 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7200,60 +7200,13 @@ static void zend_compile_accessors( zend_string *name = accessor->name; zend_ast_list *param_list = accessor->child[0] ? zend_ast_get_list(accessor->child[0]) : NULL; - zend_ast *stmt_ast = accessor->child[2]; + zend_ast **stmt_ast_ptr = &accessor->child[2]; zend_ast **return_ast_ptr = &accessor->child[3]; + zend_ast *orig_stmt_ast = *stmt_ast_ptr; CG(zend_lineno) = accessor->start_lineno; bool reset_return_ast = false, reset_param_type_ast = false; uint32_t accessor_kind; - if (*return_ast_ptr) { - zend_error_noreturn(E_COMPILE_ERROR, - "Accessor \"%s\" may not have a return type " - "(accessor types are determined by the property type)", - ZSTR_VAL(name)); - } - - if (zend_string_equals_literal_ci(name, "get")) { - accessor_kind = ZEND_ACCESSOR_GET; - if (param_list) { - if (param_list->children != 0) { - zend_error_noreturn(E_COMPILE_ERROR, - "Accessor \"get\" may not have parameters"); - } - } else { - accessor->child[0] = zend_ast_create_list(0, ZEND_AST_PARAM_LIST); - } - - reset_return_ast = true; - *return_ast_ptr = prop_type_ast; - } else if (zend_string_equals_literal_ci(name, "set")) { - accessor_kind = ZEND_ACCESSOR_SET; - if (param_list) { - if (param_list->children != 1 || !is_valid_set_param(param_list->child[0])) { - zend_error_noreturn(E_COMPILE_ERROR, - "Accessor \"set\" must have exactly one required by-value parameter"); - } - } else { - zend_string *param_name = zend_string_init("value", sizeof("value")-1, 0); - zend_ast *param_name_ast = zend_ast_create_zval_from_str(param_name); - zend_ast *param = zend_ast_create( - ZEND_AST_PARAM, prop_type_ast, param_name_ast, - /* expr */ NULL, /* doc_comment */ NULL, /* attributes */ NULL, - /* accessors */ NULL); - accessor->child[0] = zend_ast_create_list(1, ZEND_AST_PARAM_LIST, param); - reset_param_type_ast = true; - } - *return_ast_ptr = zend_ast_create_ex(ZEND_AST_TYPE, IS_VOID); - if (accessor->flags & ZEND_ACC_RETURN_REFERENCE) { - zend_error_noreturn(E_COMPILE_ERROR, - "Accessor \"set\" cannot return by reference"); - } - } else { - zend_error_noreturn(E_COMPILE_ERROR, - "Unknown accessor \"%s\" for property %s::$%s", - ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); - } - uint32_t accessor_visibility = accessor->flags & ZEND_ACC_PPP_MASK; uint32_t prop_visibility = prop_info->flags & ZEND_ACC_PPP_MASK; if (!accessor_visibility) { @@ -7298,7 +7251,7 @@ static void zend_compile_accessors( zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be both final and private"); } if (accessor->flags & ZEND_ACC_ABSTRACT) { - if (stmt_ast) { + if (orig_stmt_ast) { zend_error_noreturn(E_COMPILE_ERROR, "Abstract accessor cannot have body"); } if (accessor->flags & ZEND_ACC_PRIVATE) { @@ -7310,20 +7263,90 @@ static void zend_compile_accessors( ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; } + if (*return_ast_ptr) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"%s\" may not have a return type " + "(accessor types are determined by the property type)", + ZSTR_VAL(name)); + } + + bool auto_prop = !orig_stmt_ast && !(accessor->flags & ZEND_ACC_ABSTRACT); + if (zend_string_equals_literal_ci(name, "get")) { + accessor_kind = ZEND_ACCESSOR_GET; + if (param_list) { + if (param_list->children != 0) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"get\" may not have parameters"); + } + } else { + accessor->child[0] = zend_ast_create_list(0, ZEND_AST_PARAM_LIST); + } + + reset_return_ast = true; + *return_ast_ptr = prop_type_ast; + + if (auto_prop) { + zend_ast *prop_fetch = 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(prop_name)); + zend_ast *return_stmt = zend_ast_create(ZEND_AST_RETURN, prop_fetch); + *stmt_ast_ptr = zend_ast_create_list(1, ZEND_AST_STMT_LIST, return_stmt); + } + } else if (zend_string_equals_literal_ci(name, "set")) { + accessor_kind = ZEND_ACCESSOR_SET; + if (param_list) { + if (param_list->children != 1 || !is_valid_set_param(param_list->child[0])) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"set\" must have exactly one required by-value parameter"); + } + } else { + zend_string *param_name = zend_string_init("value", sizeof("value")-1, 0); + zend_ast *param_name_ast = zend_ast_create_zval_from_str(param_name); + zend_ast *param = zend_ast_create( + ZEND_AST_PARAM, prop_type_ast, param_name_ast, + /* expr */ NULL, /* doc_comment */ NULL, /* attributes */ NULL, + /* accessors */ NULL); + accessor->child[0] = zend_ast_create_list(1, ZEND_AST_PARAM_LIST, param); + reset_param_type_ast = true; + } + *return_ast_ptr = zend_ast_create_ex(ZEND_AST_TYPE, IS_VOID); + if (accessor->flags & ZEND_ACC_RETURN_REFERENCE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor \"set\" cannot return by reference"); + } + + if (auto_prop) { + zend_ast *prop_fetch = 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(prop_name)); + zend_ast *assign_stmt = zend_ast_create(ZEND_AST_ASSIGN, prop_fetch, + zend_ast_create(ZEND_AST_VAR, + zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_VALUE)))); + *stmt_ast_ptr = zend_ast_create_list(1, ZEND_AST_STMT_LIST, assign_stmt); + } + } else { + zend_error_noreturn(E_COMPILE_ERROR, + "Unknown accessor \"%s\" for property %s::$%s", + ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + zend_string *prefixed_name = zend_strpprintf(0, "$%s::%s", zend_get_unmangled_property_name(prop_name), ZSTR_VAL(name)); accessor->name = prefixed_name; zend_function *func = (zend_function *) zend_compile_func_decl( NULL, (zend_ast *) accessor, /* toplevel */ false); - if (stmt_ast) { - ce->ce_flags |= ZEND_ACC_USE_GUARDS; - } else { - // TODO: These could use a more compact representation. - // Currently we even allocate opcodes for body-less functions. + if (auto_prop) { func->common.fn_flags |= ZEND_ACC_AUTO_PROP; } + // TODO: Make this more precise. + if (!auto_prop) { + ce->ce_flags |= ZEND_ACC_USE_GUARDS; + } + if (!prop_info->accessors) { prop_info->accessors = ecalloc(1, ZEND_ACCESSOR_STRUCT_SIZE); } diff --git a/ext/reflection/tests/accessors.phpt b/ext/reflection/tests/accessors.phpt index ab8f7bf473d51..55c9c258b46a6 100644 --- a/ext/reflection/tests/accessors.phpt +++ b/ext/reflection/tests/accessors.phpt @@ -57,9 +57,9 @@ object(ReflectionMethod)#5 (2) { ["class"]=> string(4) "Test" } -NULL -Trying to invoke private method Test::$prop2::set() from scope ReflectionMethod float(3.141) +Trying to invoke private method Test::$prop2::set() from scope ReflectionMethod +int(42) object(ReflectionMethod)#8 (2) { ["name"]=> From a051fc1523029bc2ea622b1aa80ccd7c071cf035 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 15:11:43 +0200 Subject: [PATCH 07/44] Don't use mangled propery name during compilation --- .../accessors/unknown_accessor_private.phpt | 14 ++++++++++++++ Zend/zend_compile.c | 16 +++++++--------- 2 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 Zend/tests/accessors/unknown_accessor_private.phpt diff --git a/Zend/tests/accessors/unknown_accessor_private.phpt b/Zend/tests/accessors/unknown_accessor_private.phpt new file mode 100644 index 0000000000000..f7468afc1ce2d --- /dev/null +++ b/Zend/tests/accessors/unknown_accessor_private.phpt @@ -0,0 +1,14 @@ +--TEST-- +Unknown accessor (private property) +--FILE-- + +--EXPECTF-- +Fatal error: Unknown accessor "foobar" for property Test::$prop in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a7778db29d2ff..6e8521b1a726c 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6461,7 +6461,8 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3 /* }}} */ static void zend_compile_accessors( - zend_property_info *prop_info, zend_ast *prop_type_ast, zend_ast_list *accessors); + zend_property_info *prop_info, zend_string *prop_name, + zend_ast *prop_type_ast, zend_ast_list *accessors); void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fallback_return_type) /* {{{ */ { @@ -6675,7 +6676,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall "Cannot declare complex accessors for a promoted property"); } } - zend_compile_accessors(prop, type_ast, accessors); + zend_compile_accessors(prop, name, type_ast, accessors); } if (attributes_ast) { zend_compile_attributes( @@ -7186,10 +7187,10 @@ static bool is_valid_set_param(zend_ast *param) { } static void zend_compile_accessors( - zend_property_info *prop_info, zend_ast *prop_type_ast, zend_ast_list *accessors) + zend_property_info *prop_info, zend_string *prop_name, + zend_ast *prop_type_ast, zend_ast_list *accessors) { zend_class_entry *ce = CG(active_class_entry); - zend_string *prop_name = prop_info->name; if (accessors->children == 0) { zend_error_noreturn(E_COMPILE_ERROR, "Accessor list cannot be empty"); @@ -7332,10 +7333,7 @@ static void zend_compile_accessors( ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); } - zend_string *prefixed_name = zend_strpprintf(0, "$%s::%s", - zend_get_unmangled_property_name(prop_name), ZSTR_VAL(name)); - accessor->name = prefixed_name; - + accessor->name = zend_strpprintf(0, "$%s::%s", ZSTR_VAL(prop_name), ZSTR_VAL(name)); zend_function *func = (zend_function *) zend_compile_func_decl( NULL, (zend_ast *) accessor, /* toplevel */ false); if (auto_prop) { @@ -7452,7 +7450,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type); if (accessors_ast) { - zend_compile_accessors(info, type_ast, zend_ast_get_list(accessors_ast)); + zend_compile_accessors(info, name, type_ast, zend_ast_get_list(accessors_ast)); } if (attr_ast) { From 19bb00493b2d169e6bf68a2f715f8e5e945c1905 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 15:15:26 +0200 Subject: [PATCH 08/44] Explicit forbid accessors on static props This can be supported, but would require additional implementation effort. --- Zend/tests/accessors/invalid_static_prop.phpt | 12 ++++++++++++ Zend/zend_compile.c | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 Zend/tests/accessors/invalid_static_prop.phpt diff --git a/Zend/tests/accessors/invalid_static_prop.phpt b/Zend/tests/accessors/invalid_static_prop.phpt new file mode 100644 index 0000000000000..02dbd20b70651 --- /dev/null +++ b/Zend/tests/accessors/invalid_static_prop.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use accessors for static property (for now) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot declare accessors for static property in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 6e8521b1a726c..39fe399ebbe5f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7248,6 +7248,10 @@ static void zend_compile_accessors( if (accessor->flags & ZEND_ACC_STATIC) { zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be static"); } + if (prop_info->flags & ZEND_ACC_STATIC) { + // TODO: This restriction can be relaxed. + zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare accessors for static property"); + } if ((accessor->flags & ZEND_ACC_FINAL) && (accessor->flags & ZEND_ACC_PRIVATE)) { zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be both final and private"); } From aa1abc9dfafd44b4498e02e27d6d6ae1006fe721 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 15:42:10 +0200 Subject: [PATCH 09/44] Improve opcache compatibility --- Zend/zend_compile.c | 7 ++-- Zend/zend_inheritance.c | 67 +++++++++++++++++++++++--------------- Zend/zend_opcode.c | 1 - ext/opcache/zend_persist.c | 3 +- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 39fe399ebbe5f..d28fe0c8f29a3 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7294,7 +7294,7 @@ static void zend_compile_accessors( zend_ast *prop_fetch = 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(prop_name)); + zend_ast_create_zval_from_str(zend_string_copy(prop_name))); zend_ast *return_stmt = zend_ast_create(ZEND_AST_RETURN, prop_fetch); *stmt_ast_ptr = zend_ast_create_list(1, ZEND_AST_STMT_LIST, return_stmt); } @@ -7325,7 +7325,7 @@ static void zend_compile_accessors( zend_ast *prop_fetch = 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(prop_name)); + zend_ast_create_zval_from_str(zend_string_copy(prop_name))); zend_ast *assign_stmt = zend_ast_create(ZEND_AST_ASSIGN, prop_fetch, zend_ast_create(ZEND_AST_VAR, zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_VALUE)))); @@ -7350,7 +7350,8 @@ static void zend_compile_accessors( } if (!prop_info->accessors) { - prop_info->accessors = ecalloc(1, ZEND_ACCESSOR_STRUCT_SIZE); + prop_info->accessors = zend_arena_alloc(&CG(arena), ZEND_ACCESSOR_STRUCT_SIZE); + memset(prop_info->accessors, 0, ZEND_ACCESSOR_STRUCT_SIZE); } if (prop_info->accessors[accessor_kind]) { diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index ef0c61f75c9ff..a3cacb111db85 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2209,7 +2209,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } if (property_info->accessors) { zend_function **accessors = new_prop->accessors = - emalloc(ZEND_ACCESSOR_STRUCT_SIZE); + zend_arena_alloc(&CG(arena), ZEND_ACCESSOR_STRUCT_SIZE); memcpy(accessors, property_info->accessors, ZEND_ACCESSOR_STRUCT_SIZE); for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { if (accessors[i]) { @@ -2572,6 +2572,34 @@ 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); + size_t alloc_size = sizeof(zend_op_array) + sizeof(void *); + if (op_array->static_variables) { + alloc_size += sizeof(HashTable *); + } + + zend_op_array *new_op_array = zend_arena_alloc(&CG(arena), alloc_size); + memcpy(new_op_array, op_array, sizeof(zend_op_array)); + new_op_array->fn_flags &= ~ZEND_ACC_IMMUTABLE; + new_op_array->scope = ce; + + void ***run_time_cache_ptr = (void***)(new_op_array + 1); + *run_time_cache_ptr = NULL; + ZEND_MAP_PTR_INIT(new_op_array->run_time_cache, run_time_cache_ptr); + + if (op_array->static_variables) { + HashTable **static_variables_ptr = (HashTable **) (run_time_cache_ptr + 1); + *static_variables_ptr = NULL; + ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, static_variables_ptr); + } + + return new_op_array; +} + static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) { zend_class_entry *ce; @@ -2605,31 +2633,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; - void ***run_time_cache_ptr; - size_t alloc_size; - - 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); - alloc_size = sizeof(zend_op_array) + sizeof(void *); - if (op_array->static_variables) { - alloc_size += sizeof(HashTable *); - } - new_op_array = zend_arena_alloc(&CG(arena), alloc_size); - Z_PTR(p->val) = new_op_array; - memcpy(new_op_array, op_array, sizeof(zend_op_array)); - run_time_cache_ptr = (void***)(new_op_array + 1); - *run_time_cache_ptr = NULL; - new_op_array->fn_flags &= ~ZEND_ACC_IMMUTABLE; - new_op_array->scope = ce; - ZEND_MAP_PTR_INIT(new_op_array->run_time_cache, run_time_cache_ptr); - if (op_array->static_variables) { - HashTable **static_variables_ptr = (HashTable **) (run_time_cache_ptr + 1); - *static_variables_ptr = NULL; - ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, static_variables_ptr); - } + 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); @@ -2685,6 +2690,16 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) ZEND_TYPE_SET_PTR(new_prop_info->type, list); ZEND_TYPE_FULL_MASK(new_prop_info->type) |= _ZEND_TYPE_ARENA_BIT; } + if (new_prop_info->accessors) { + new_prop_info->accessors = zend_arena_alloc(&CG(arena), ZEND_ACCESSOR_STRUCT_SIZE); + memcpy(new_prop_info->accessors, prop_info->accessors, ZEND_ACCESSOR_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + if (new_prop_info->accessors[i]) { + new_prop_info->accessors[i] = (zend_function *) zend_lazy_method_load( + (zend_op_array *) new_prop_info->accessors[i], ce, pce); + } + } + } } } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 9a824cd00e587..6e68c2a3bbbd5 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -420,7 +420,6 @@ ZEND_API void destroy_zend_class(zval *zv) destroy_op_array(&prop_info->accessors[i]->op_array); } } - efree(prop_info->accessors); } } } ZEND_HASH_FOREACH_END(); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 8a63973f9a135..4ac8f15933fdb 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -826,8 +826,7 @@ static zend_property_info *zend_persist_property_info(zend_property_info *prop) prop->attributes = zend_persist_attributes(prop->attributes); } if (prop->accessors) { - prop->accessors = - zend_shared_memdup_put_free(prop->accessors, ZEND_ACCESSOR_STRUCT_SIZE); + prop->accessors = zend_shared_memdup_put(prop->accessors, ZEND_ACCESSOR_STRUCT_SIZE); for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { if (prop->accessors[i]) { prop->accessors[i] = (zend_function *) zend_persist_class_method( From e3145b4989ede57ccb3ad43063f74986836e63b5 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 16:01:22 +0200 Subject: [PATCH 10/44] Fix JIT (by bailing out) --- ext/opcache/jit/zend_jit_x86.dasc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index c027fb63773ba..c17371e1f1ae3 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -12735,10 +12735,12 @@ static zend_property_info* zend_get_known_property_info(const zend_op_array *op_ } } + // TODO: Treat accessors 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->accessors) { return NULL; } @@ -12765,10 +12767,12 @@ static bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string *memb } } + // TODO: Treat accessors 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->accessors) { return 1; } From 11aafaa4816fa8d6cdda0e38297f10bffb7d0668 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 21 Apr 2021 16:03:02 +0200 Subject: [PATCH 11/44] Fix clang build (va promotion UB) --- Zend/zend_ast.c | 4 ++-- Zend/zend_ast.h | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 568b0f2a19996..0d664b6959825 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -264,7 +264,7 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_va( return ast; } -ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_n(zend_ast_kind kind, ...) { +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); @@ -273,7 +273,7 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_n(zend_ast_kind kind, ...) { } ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_ex_n( - zend_ast_kind kind, zend_ast_attr attr, ...) { + zend_ast_kind kind, unsigned attr, ...) { va_list va; va_start(va, attr); zend_ast *ast = zend_ast_create_va(kind, attr, &va); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 8b71451181cc3..d98c8eca1adc6 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -243,8 +243,9 @@ 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_va(zend_ast_kind kind, zend_ast_attr attr, va_list *va); -ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_n(zend_ast_kind kind, ...); -ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_ex_n(zend_ast_kind kind, zend_ast_attr attr, ...); +/* 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); From d2c177d796ccacd8a4d1faa07dd3f2b41c607821 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 22 Apr 2021 10:51:33 +0200 Subject: [PATCH 12/44] Try to fix another maybe-uninitialized warning --- Zend/zend_compile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d28fe0c8f29a3..2ce8b8869c4c6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7051,7 +7051,7 @@ zend_op_array *zend_compile_func_decl(znode *result, zend_ast *ast, bool topleve zend_ast *return_type_ast = decl->child[3]; bool is_method = decl->kind == ZEND_AST_METHOD; bool is_accessor = decl->kind == ZEND_AST_ACCESSOR; - zend_string *method_lcname; + zend_string *method_lcname = NULL; zend_class_entry *orig_class_entry = CG(active_class_entry); zend_op_array *orig_op_array = CG(active_op_array); From 36dff631ea8e4d0ee7af009d694d555385b1c55d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 22 Apr 2021 11:03:12 +0200 Subject: [PATCH 13/44] Optimize reads from auto accessors --- Zend/zend_object_handlers.c | 4 +++ Zend/zend_object_handlers.h | 9 +++-- Zend/zend_vm_def.h | 6 ++++ Zend/zend_vm_execute.h | 72 +++++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 307d291435b15..42ab83095eb99 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -718,6 +718,10 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int OBJ_RELEASE(zobj); goto exit; } + } else if (cache_slot) { + /* Cache the fact that this accessor has trivial read. */ + CACHE_PTR_EX(cache_slot + 1, + (void*)(ZEND_ACCESSOR_PROPERTY_OFFSET | ZEND_ACCESSOR_SIMPLE_READ_BIT)); } property_offset = prop_info->offset; diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index b17bd7f16d38b..a4c33a06e8c6a 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -27,11 +27,16 @@ struct _zend_property_info; #define ZEND_DYNAMIC_PROPERTY_OFFSET ((uintptr_t)(intptr_t)(-1)) -#define IS_VALID_PROPERTY_OFFSET(offset) ((intptr_t)(offset) > 1) +#define IS_VALID_PROPERTY_OFFSET(offset) ((intptr_t)(offset) >= 4) #define IS_WRONG_PROPERTY_OFFSET(offset) ((intptr_t)(offset) == 0) -#define IS_ACCESSOR_PROPERTY_OFFSET(offset) ((intptr_t)(offset) == 1) +#define IS_ACCESSOR_PROPERTY_OFFSET(offset) \ + ((intptr_t)(offset) > 0 && (intptr_t)(offset) < 4) #define IS_DYNAMIC_PROPERTY_OFFSET(offset) ((intptr_t)(offset) < 0) +#define ZEND_ACCESSOR_SIMPLE_READ_BIT 2u +#define ZEND_IS_ACCESSOR_SIMPLE_READ(offset) \ + (((offset) & ZEND_ACCESSOR_SIMPLE_READ_BIT) != 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))) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b4978f252d68a..9a3a3503ee517 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2044,6 +2044,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) { @@ -2092,6 +2093,11 @@ ZEND_VM_C_LABEL(fetch_obj_r_fast_copy): } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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_r_simple); + } } } name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index f3b41c75ccea6..9f3c9b8c5a570 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -6225,6 +6225,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) { @@ -6273,6 +6274,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -8562,6 +8568,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) { @@ -8610,6 +8617,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -10922,6 +10934,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) { @@ -10970,6 +10983,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -15351,6 +15369,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) { @@ -15399,6 +15418,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CONST_ } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -16781,6 +16805,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) { @@ -16829,6 +16854,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_TMPVAR } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -18103,6 +18133,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) { @@ -18151,6 +18182,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CV_HAN } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -31486,6 +31522,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) { @@ -31534,6 +31571,11 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33409,6 +33451,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) { @@ -33457,6 +33500,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_TMPVAR } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -35925,6 +35973,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) { @@ -35973,6 +36022,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_CV_HAN } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -40123,6 +40177,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) { @@ -40171,6 +40226,11 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -43799,6 +43859,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) { @@ -43847,6 +43908,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_TMPVAR_HAN } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -48852,6 +48918,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) { @@ -48900,6 +48967,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_CV_HANDLER } else { /* Fall through to read_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); From ad8916d8f71ea66aa9dbce67b6a43d3e5842c8e5 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 22 Apr 2021 11:44:33 +0200 Subject: [PATCH 14/44] Property handle by-ref distinction for auto accessors This is ugly, but necessary. --- Zend/tests/accessors/get_by_ref_auto.phpt | 29 +++++++++++++++++++++++ Zend/zend_object_handlers.c | 28 ++++++++++++++++++---- 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/accessors/get_by_ref_auto.phpt diff --git a/Zend/tests/accessors/get_by_ref_auto.phpt b/Zend/tests/accessors/get_by_ref_auto.phpt new file mode 100644 index 0000000000000..2b7a6bc651073 --- /dev/null +++ b/Zend/tests/accessors/get_by_ref_auto.phpt @@ -0,0 +1,29 @@ +--TEST-- +Get by reference with generated accessors +--FILE-- +byRef[] = 42; +var_dump($test->byRef); + +$test->byVal[] = 42; +var_dump($test->byVal); + + +?> +--EXPECTF-- +array(1) { + [0]=> + int(42) +} + +Notice: Indirect modification of accessor property Test::$byVal has no effect (did you mean to use "&get"?) in %s on line %d +array(0) { +} diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 42ab83095eb99..599650257a10d 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -718,14 +718,34 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int OBJ_RELEASE(zobj); goto exit; } - } else if (cache_slot) { - /* Cache the fact that this accessor has trivial read. */ + + property_offset = prop_info->offset; + goto try_again; + } + + if (cache_slot) { + /* Cache the fact that this accessor has trivial read. This only applies to + * BP_VAR_R and BP_VAR_IS fetches. */ CACHE_PTR_EX(cache_slot + 1, (void*)(ZEND_ACCESSOR_PROPERTY_OFFSET | ZEND_ACCESSOR_SIMPLE_READ_BIT)); } - property_offset = prop_info->offset; - goto try_again; + retval = OBJ_PROP(zobj, prop_info->offset); + if (UNEXPECTED(Z_TYPE_P(retval) == IS_UNDEF)) { + /* As accessor 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 ((type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) + && !(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) + && Z_TYPE_P(retval) != IS_OBJECT) { + ZVAL_COPY(rv, retval); + retval = rv; + zend_error(E_NOTICE, "Indirect modification of accessor property %s::$%s has no effect (did you mean to use \"&get\"?)", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + } + goto exit; } else if (!silent) { return &EG(uninitialized_zval); } From 4c46fb1d906e5e206238653542b908c559684640 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 22 Apr 2021 12:29:29 +0200 Subject: [PATCH 15/44] Handle overloaded object semantics more precisely This matches the behavior we'd get with an implicit implementation, but also means that these would be less efficient :/ --- Zend/tests/accessors/get_by_ref.phpt | 7 +++++++ Zend/tests/accessors/get_by_ref_auto.phpt | 6 ++++++ Zend/zend_object_handlers.c | 11 +++++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Zend/tests/accessors/get_by_ref.phpt b/Zend/tests/accessors/get_by_ref.phpt index bcccf169d7e86..fb10689d0b84d 100644 --- a/Zend/tests/accessors/get_by_ref.phpt +++ b/Zend/tests/accessors/get_by_ref.phpt @@ -28,6 +28,12 @@ var_dump($test->byVal); var_dump($test->byRefType); +try { + $test->byRef =& $ref; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + ?> --EXPECTF-- array(1) { @@ -39,3 +45,4 @@ Notice: Indirect modification of accessor property Test::$byVal has no effect (d array(0) { } int(42) +Cannot assign by reference to overloaded object diff --git a/Zend/tests/accessors/get_by_ref_auto.phpt b/Zend/tests/accessors/get_by_ref_auto.phpt index 2b7a6bc651073..70257c83be5e0 100644 --- a/Zend/tests/accessors/get_by_ref_auto.phpt +++ b/Zend/tests/accessors/get_by_ref_auto.phpt @@ -16,6 +16,11 @@ var_dump($test->byRef); $test->byVal[] = 42; var_dump($test->byVal); +try { + $test->byRef =& $ref; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} ?> --EXPECTF-- @@ -27,3 +32,4 @@ array(1) { Notice: Indirect modification of accessor property Test::$byVal has no effect (did you mean to use "&get"?) in %s on line %d array(0) { } +Cannot assign by reference to overloaded object diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 599650257a10d..5b75052be32ae 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -738,12 +738,15 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto uninit_error; } - if ((type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) - && !(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) - && Z_TYPE_P(retval) != IS_OBJECT) { + if (UNEXPECTED(type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)) { + if (UNEXPECTED(!(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) + && Z_TYPE_P(retval) != IS_OBJECT)) { + zend_error(E_NOTICE, "Indirect modification of accessor property %s::$%s has no effect (did you mean to use \"&get\"?)", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + } else { + ZVAL_MAKE_REF(retval); + } ZVAL_COPY(rv, retval); retval = rv; - zend_error(E_NOTICE, "Indirect modification of accessor property %s::$%s has no effect (did you mean to use \"&get\"?)", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); } goto exit; } else if (!silent) { From a94d2cba3899b3336a4a31ff316d034501c8c394 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 22 Apr 2021 12:52:00 +0200 Subject: [PATCH 16/44] Optimize isset() with proper accessor Apparently inlining zend_is_true() adversely affects isset() here. --- Zend/zend_object_handlers.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 5b75052be32ae..fefbaaac6ba3a 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1908,10 +1908,13 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has *guard &= ~IN_GET; OBJ_RELEASE(zobj); - result = has_set_exists == ZEND_PROPERTY_NOT_EMPTY - ? i_zend_is_true(&rv) - : (Z_TYPE(rv) != IS_NULL - && (Z_TYPE(rv) != IS_REFERENCE || Z_TYPE_P(Z_REFVAL(rv)) != IS_NULL)); + 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; } From f98254927b3cb91ddbd98653351a0524f7acafba Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 22 Apr 2021 13:51:10 +0200 Subject: [PATCH 17/44] Optimize assignment to trivial accessor --- Zend/zend_object_handlers.c | 6 +- Zend/zend_object_handlers.h | 7 +- Zend/zend_vm_def.h | 12 +- Zend/zend_vm_execute.h | 432 +++++++++++++++++++++++++++++++++--- 4 files changed, 417 insertions(+), 40 deletions(-) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index fefbaaac6ba3a..33c6e8ddf3deb 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -727,7 +727,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int /* Cache the fact that this accessor has trivial read. This only applies to * BP_VAR_R and BP_VAR_IS fetches. */ CACHE_PTR_EX(cache_slot + 1, - (void*)(ZEND_ACCESSOR_PROPERTY_OFFSET | ZEND_ACCESSOR_SIMPLE_READ_BIT)); + (void*)((uintptr_t)CACHED_PTR_EX(cache_slot + 1) | ZEND_ACCESSOR_SIMPLE_READ_BIT)); } retval = OBJ_PROP(zobj, prop_info->offset); @@ -924,6 +924,10 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva OBJ_RELEASE(zobj); return value; } + } else if (cache_slot) { + /* Cache the fact that this accessor has trivial write. */ + CACHE_PTR_EX(cache_slot + 1, + (void*)((uintptr_t)CACHED_PTR_EX(cache_slot + 1) | ZEND_ACCESSOR_SIMPLE_WRITE_BIT)); } property_offset = prop_info->offset; diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index a4c33a06e8c6a..8dc651f4ccfbc 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -27,15 +27,18 @@ struct _zend_property_info; #define ZEND_DYNAMIC_PROPERTY_OFFSET ((uintptr_t)(intptr_t)(-1)) -#define IS_VALID_PROPERTY_OFFSET(offset) ((intptr_t)(offset) >= 4) +#define IS_VALID_PROPERTY_OFFSET(offset) ((intptr_t)(offset) >= 8) #define IS_WRONG_PROPERTY_OFFSET(offset) ((intptr_t)(offset) == 0) #define IS_ACCESSOR_PROPERTY_OFFSET(offset) \ - ((intptr_t)(offset) > 0 && (intptr_t)(offset) < 4) + ((intptr_t)(offset) > 0 && (intptr_t)(offset) < 8) #define IS_DYNAMIC_PROPERTY_OFFSET(offset) ((intptr_t)(offset) < 0) #define ZEND_ACCESSOR_SIMPLE_READ_BIT 2u +#define ZEND_ACCESSOR_SIMPLE_WRITE_BIT 4u #define ZEND_IS_ACCESSOR_SIMPLE_READ(offset) \ (((offset) & ZEND_ACCESSOR_SIMPLE_READ_BIT) != 0) +#define ZEND_IS_ACCESSOR_SIMPLE_WRITE(offset) \ + (((offset) & ZEND_ACCESSOR_SIMPLE_WRITE_BIT) != 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)) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 9a3a3503ee517..782396f3bdeef 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2459,7 +2459,7 @@ ZEND_VM_C_LABEL(fast_assign_obj): } else if (OP_DATA_TYPE == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -2469,6 +2469,16 @@ ZEND_VM_C_LABEL(fast_assign_obj): } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + ZEND_VM_C_GOTO(free_and_exit_assign_obj); + } else { + ZEND_VM_C_GOTO(fast_assign_obj); + } + } } } name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9f3c9b8c5a570..f8dd97110bc1c 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -22927,7 +22927,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -22937,6 +22937,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23072,7 +23082,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -23082,6 +23092,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23217,7 +23237,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -23227,6 +23247,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23362,7 +23392,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -23372,6 +23402,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -25517,7 +25557,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -25527,6 +25567,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -25662,7 +25712,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -25672,6 +25722,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -25807,7 +25867,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -25817,6 +25877,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -25952,7 +26022,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -25962,6 +26032,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -29455,7 +29535,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -29465,6 +29545,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -29600,7 +29690,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -29610,6 +29700,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -29745,7 +29845,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -29755,6 +29855,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -29890,7 +30000,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -29900,6 +30010,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -31907,7 +32027,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -31917,6 +32037,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -32052,7 +32182,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -32062,6 +32192,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -32197,7 +32337,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -32207,6 +32347,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -32342,7 +32492,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -32352,6 +32502,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33831,7 +33991,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -33841,6 +34001,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -33976,7 +34146,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -33986,6 +34156,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -34121,7 +34301,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -34131,6 +34311,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -34266,7 +34456,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -34276,6 +34466,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -36353,7 +36553,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -36363,6 +36563,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -36498,7 +36708,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -36508,6 +36718,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -36643,7 +36863,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -36653,6 +36873,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -36788,7 +37018,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -36798,6 +37028,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -40562,7 +40802,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -40572,6 +40812,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40707,7 +40957,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -40717,6 +40967,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40852,7 +41112,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -40862,6 +41122,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40997,7 +41267,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -41007,6 +41277,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -44239,7 +44519,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -44249,6 +44529,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -44384,7 +44674,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -44394,6 +44684,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -44529,7 +44829,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -44539,6 +44839,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -44674,7 +44984,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -44684,6 +44994,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -49298,7 +49618,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } else if (IS_CONST == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -49308,6 +49628,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -49443,7 +49773,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } else if (IS_TMP_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -49453,6 +49783,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -49588,7 +49928,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } else if (IS_VAR == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -49598,6 +49938,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -49733,7 +50083,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } else if (IS_CV == IS_CV) { Z_TRY_ADDREF_P(value); } - } + } zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); @@ -49743,6 +50093,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } else { /* Fall through to write_property for accessors. */ ZEND_ASSERT(IS_ACCESSOR_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_ACCESSOR_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 EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); From afcd9451cf9ac29ee0a836aeb3ac6b0f5993132d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 22 Apr 2021 16:37:36 +0200 Subject: [PATCH 18/44] Add dump test --- Zend/tests/accessors/dump.phpt | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Zend/tests/accessors/dump.phpt diff --git a/Zend/tests/accessors/dump.phpt b/Zend/tests/accessors/dump.phpt new file mode 100644 index 0000000000000..dc6b1f9d570e1 --- /dev/null +++ b/Zend/tests/accessors/dump.phpt @@ -0,0 +1,40 @@ +--TEST-- +Dumping object with accessors +--FILE-- +prop4 * 2; } + set; + } + public $prop5 { + get { return 42; } + } +} + +var_dump(new Test); + +?> +--EXPECT-- +object(Test)#1 (5) { + ["prop"]=> + int(1) + ["prop2"]=> + int(2) + ["prop3"]=> + int(3) + ["prop4"]=> + int(4) + ["prop5"]=> + NULL +} From a467ae15dc086010572fd37416357ad01e643fbe Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 22 Apr 2021 17:02:35 +0200 Subject: [PATCH 19/44] Fix trivial read/write cache This doesn't quite hold if we switch to a shadowed parent property. At the very least the error message may differ in the write case. --- Zend/zend_object_handlers.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 33c6e8ddf3deb..846bd91423f51 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -692,6 +692,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int } bool silent = type == BP_VAR_IS || zobj->ce->__get != NULL; + zend_property_info *orig_prop_info = prop_info; get = check_accessor_visibility(&prop_info, zobj->ce, name, get, silent); if (get) { if (!(get->op_array.fn_flags & ZEND_ACC_AUTO_PROP)) { @@ -723,7 +724,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto try_again; } - if (cache_slot) { + if (cache_slot && prop_info == orig_prop_info) { /* Cache the fact that this accessor has trivial read. This only applies to * BP_VAR_R and BP_VAR_IS fetches. */ CACHE_PTR_EX(cache_slot + 1, @@ -912,6 +913,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva } bool silent = zobj->ce->__set != NULL; + zend_property_info *orig_prop_info = prop_info; set = check_accessor_visibility(&prop_info, zobj->ce, name, set, silent); if (set) { if (!(set->op_array.fn_flags & ZEND_ACC_AUTO_PROP)) { @@ -924,7 +926,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva OBJ_RELEASE(zobj); return value; } - } else if (cache_slot) { + } else if (cache_slot && prop_info == orig_prop_info) { /* Cache the fact that this accessor has trivial write. */ CACHE_PTR_EX(cache_slot + 1, (void*)((uintptr_t)CACHED_PTR_EX(cache_slot + 1) | ZEND_ACCESSOR_SIMPLE_WRITE_BIT)); From f518ce0d11e102c86ce7e67418edc825ea08380e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 27 Apr 2021 14:44:53 +0200 Subject: [PATCH 20/44] Prohibit redundant final modifier To be consistent with the treatment of abstract. --- .../accessors/final_prop_final_accessor.phpt | 15 +++++++++++++++ Zend/zend_compile.c | 7 +++++++ 2 files changed, 22 insertions(+) create mode 100644 Zend/tests/accessors/final_prop_final_accessor.phpt diff --git a/Zend/tests/accessors/final_prop_final_accessor.phpt b/Zend/tests/accessors/final_prop_final_accessor.phpt new file mode 100644 index 0000000000000..7451d8f5995e9 --- /dev/null +++ b/Zend/tests/accessors/final_prop_final_accessor.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot make accessor explicitly final in final property +--FILE-- + +--EXPECTF-- +Fatal error: Accessor on final property cannot be explicitly final in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2ce8b8869c4c6..c971055f62888 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7252,6 +7252,13 @@ static void zend_compile_accessors( // TODO: This restriction can be relaxed. zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare accessors for static property"); } + if (prop_info->flags & ZEND_ACC_FINAL) { + if (accessor->flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor on final property cannot be explicitly final"); + } + accessor->flags |= ZEND_ACC_FINAL; + } if ((accessor->flags & ZEND_ACC_FINAL) && (accessor->flags & ZEND_ACC_PRIVATE)) { zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be both final and private"); } From f4f07fd78043e97b8941ba3501852ab48635d233 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 27 Apr 2021 14:59:59 +0200 Subject: [PATCH 21/44] Prohibit final private property This was previously only checked for accessors. --- Zend/tests/accessors/final_private_prop.phpt | 12 ++++++++++++ Zend/zend_compile.c | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 Zend/tests/accessors/final_private_prop.phpt diff --git a/Zend/tests/accessors/final_private_prop.phpt b/Zend/tests/accessors/final_private_prop.phpt new file mode 100644 index 0000000000000..2687d57531f11 --- /dev/null +++ b/Zend/tests/accessors/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/zend_compile.c b/Zend/zend_compile.c index c971055f62888..2a04cf8c944d9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7388,6 +7388,10 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z zend_error_noreturn(E_COMPILE_ERROR, "Enums may not include properties"); } + if ((flags & ZEND_ACC_FINAL) && (flags & ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, "Property cannot be both final and private"); + } + for (i = 0; i < children; ++i) { zend_property_info *info; zend_ast *prop_ast = list->child[i]; From a8b8d083db6f76b24a14b0d3cfac52d1674bbdb0 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 30 Apr 2021 11:13:48 +0200 Subject: [PATCH 22/44] Fix rebase fallout --- Zend/zend_inheritance.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index a3cacb111db85..514a69e817c34 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1066,7 +1066,7 @@ static void inherit_accessor( ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; } - *child_ptr = zend_duplicate_function(parent, ce, /* is_interface */ false); + *child_ptr = zend_duplicate_function(parent, ce); return; } @@ -2213,7 +2213,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent memcpy(accessors, property_info->accessors, ZEND_ACCESSOR_STRUCT_SIZE); for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { if (accessors[i]) { - accessors[i] = zend_duplicate_function(accessors[i], ce, false); + accessors[i] = zend_duplicate_function(accessors[i], ce); if (accessors[i]->common.fn_flags & ZEND_ACC_ABSTRACT) { ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; } From 7af517f4dbef2a3e65ee852057de5dc765ef286f Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 27 Apr 2021 19:19:45 +0200 Subject: [PATCH 23/44] Support accessors without backing property But don't support dynamic prop recursion for purely virtual accessors without a backing property. Can we extend this to the case of one implicit and one explicit accessor as well? --- Zend/tests/accessors/backing_prop.phpt | 24 ----- Zend/tests/accessors/cache.phpt | 5 +- Zend/tests/accessors/dump.phpt | 4 +- Zend/tests/accessors/isset.phpt | 35 +------ Zend/tests/accessors/no_backing_prop.phpt | 76 ++++++++++++++ Zend/tests/accessors/unset.phpt | 5 +- Zend/zend_API.c | 115 ++++++++++++---------- Zend/zend_compile.c | 26 ++++- Zend/zend_compile.h | 5 +- Zend/zend_inheritance.c | 57 ++++++----- Zend/zend_object_handlers.c | 23 ++++- 11 files changed, 231 insertions(+), 144 deletions(-) delete mode 100644 Zend/tests/accessors/backing_prop.phpt create mode 100644 Zend/tests/accessors/no_backing_prop.phpt diff --git a/Zend/tests/accessors/backing_prop.phpt b/Zend/tests/accessors/backing_prop.phpt deleted file mode 100644 index 87c4a247c279f..0000000000000 --- a/Zend/tests/accessors/backing_prop.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- -Backing property ---FILE-- -prop * 2; } - set { $this->prop = $value * 2; } - } -} - -$test = new Test; -$test->prop = 10; -var_dump($test->prop); -var_dump($test); - -?> ---EXPECT-- -int(40) -object(Test)#1 (1) { - ["prop"]=> - int(20) -} diff --git a/Zend/tests/accessors/cache.phpt b/Zend/tests/accessors/cache.phpt index 135266b886983..c658f9a6d05e0 100644 --- a/Zend/tests/accessors/cache.phpt +++ b/Zend/tests/accessors/cache.phpt @@ -4,9 +4,10 @@ Test caching of accessor property kind prop; } - set { echo __METHOD__, "\n"; $this->prop = $value; } + &get { echo __METHOD__, "\n"; return $this->_prop; } + set { echo __METHOD__, "\n"; $this->_prop = $value; } } } diff --git a/Zend/tests/accessors/dump.phpt b/Zend/tests/accessors/dump.phpt index dc6b1f9d570e1..f38437cc9d208 100644 --- a/Zend/tests/accessors/dump.phpt +++ b/Zend/tests/accessors/dump.phpt @@ -26,7 +26,7 @@ var_dump(new Test); ?> --EXPECT-- -object(Test)#1 (5) { +object(Test)#1 (4) { ["prop"]=> int(1) ["prop2"]=> @@ -35,6 +35,4 @@ object(Test)#1 (5) { int(3) ["prop4"]=> int(4) - ["prop5"]=> - NULL } diff --git a/Zend/tests/accessors/isset.phpt b/Zend/tests/accessors/isset.phpt index a1f062854e743..8d98aef1a9182 100644 --- a/Zend/tests/accessors/isset.phpt +++ b/Zend/tests/accessors/isset.phpt @@ -10,12 +10,7 @@ class Test { set { $this->_prop1 = $value; } } - public $prop2 { - get { return $this->prop2; } - set { $this->prop2 = $value; } - } - - public array $prop3 { private get; } + public array $prop2 { private get; } } $test = new Test; @@ -33,24 +28,11 @@ var_dump(isset($test->prop1)); var_dump(!empty($test->prop1)); echo "\n"; -$test->prop2 = true; -var_dump(isset($test->prop2)); -var_dump(!empty($test->prop2)); -echo "\n", -$test->prop2 = false; -var_dump(isset($test->prop2)); -var_dump(!empty($test->prop2)); -echo "\n", -$test->prop2 = null; +// Private accessor should simply report as false, not throw var_dump(isset($test->prop2)); var_dump(!empty($test->prop2)); -echo "\n"; - -// Private accessor should simply report as false, not throw -var_dump(isset($test->prop3)); -var_dump(!empty($test->prop3)); -var_dump(isset($test->prop3[0])); -var_dump(!empty($test->prop3[0])); +var_dump(isset($test->prop2[0])); +var_dump(!empty($test->prop2[0])); ?> --EXPECT-- @@ -63,15 +45,6 @@ bool(false) bool(false) bool(false) -bool(true) -bool(true) - -bool(true) -bool(false) - -bool(false) -bool(false) - bool(false) bool(false) bool(false) diff --git a/Zend/tests/accessors/no_backing_prop.phpt b/Zend/tests/accessors/no_backing_prop.phpt new file mode 100644 index 0000000000000..69a4fe21342ed --- /dev/null +++ b/Zend/tests/accessors/no_backing_prop.phpt @@ -0,0 +1,76 @@ +--TEST-- +Recursion behavior of properties without backing property +--FILE-- +prop * 2; } + set { $this->prop = $value * 2; } + } + + // Edge-case where recursion happens via isset(). + public int $prop2 { + get { return isset($this->prop2); } + set { } + } + + public int $prop3 { + get { return $this->prop3 * 2; } + set; + } + + public int $prop4 { + get; + set { $this->prop4 = $value * 2; } + } +} + +$test = new Test; +try { + $test->prop = 10; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +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"; +} +try { + var_dump(isset($test->prop2)); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$test->prop3 = 1; +var_dump($test->prop3); +var_dump(isset($test->prop3)); + +$test->prop4 = 1; +var_dump($test->prop4); +var_dump(isset($test->prop4)); + +var_dump($test); + +?> +--EXPECT-- +Cannot recursively write Test::$prop in accessor without backing property +Cannot recursively read Test::$prop in accessor without backing property +Cannot recursively read Test::$prop in accessor without backing property +Cannot recursively read Test::$prop2 in accessor without backing property +int(2) +bool(true) +int(2) +bool(true) +object(Test)#1 (2) { + ["prop3"]=> + int(1) + ["prop4"]=> + int(2) +} diff --git a/Zend/tests/accessors/unset.phpt b/Zend/tests/accessors/unset.phpt index 3e6dbf02b938d..66e1ce3c47696 100644 --- a/Zend/tests/accessors/unset.phpt +++ b/Zend/tests/accessors/unset.phpt @@ -4,9 +4,10 @@ Accessor properties cannot be unset prop; } - set { $this->prop = $value; } + get { return $this->_prop; } + set { $this->_prop = $value; } } } diff --git a/Zend/zend_API.c b/Zend/zend_API.c index f7ad299c3e4e8..3ccda09fde3a3 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4020,78 +4020,85 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z property_info = pemalloc(sizeof(zend_property_info), 1); } else { property_info = zend_arena_alloc(&CG(arena), sizeof(zend_property_info)); - if (Z_TYPE_P(property) == IS_CONSTANT_AST) { - ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; - if (access_type & ZEND_ACC_STATIC) { - ce->ce_flags |= ZEND_ACC_HAS_AST_STATICS; - } else { - ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES; - } - } - } - - if (Z_TYPE_P(property) == IS_STRING && !ZSTR_IS_INTERNED(Z_STR_P(property))) { - zval_make_interned_string(property); } if (!(access_type & ZEND_ACC_PPP_MASK)) { access_type |= ZEND_ACC_PUBLIC; } - if (access_type & ZEND_ACC_STATIC) { - if ((property_info_ptr = zend_hash_find_ptr(&ce->properties_info, name)) != NULL && - (property_info_ptr->flags & ZEND_ACC_STATIC) != 0) { - property_info->offset = property_info_ptr->offset; - zval_ptr_dtor(&ce->default_static_members_table[property_info->offset]); - zend_hash_del(&ce->properties_info, name); + if (!(access_type & ZEND_ACC_VIRTUAL)) { + if (Z_TYPE_P(property) == IS_STRING && !ZSTR_IS_INTERNED(Z_STR_P(property))) { + zval_make_interned_string(property); + } + if (ce->type == ZEND_INTERNAL_CLASS) { + if (Z_REFCOUNTED_P(property)) { + zend_error_noreturn(E_CORE_ERROR, "Internal zvals cannot be refcounted"); + } } else { - property_info->offset = ce->default_static_members_count++; - ce->default_static_members_table = perealloc(ce->default_static_members_table, sizeof(zval) * ce->default_static_members_count, ce->type == ZEND_INTERNAL_CLASS); + if (Z_TYPE_P(property) == IS_CONSTANT_AST) { + ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + if (access_type & ZEND_ACC_STATIC) { + ce->ce_flags |= ZEND_ACC_HAS_AST_STATICS; + } else { + ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES; + } + } } - ZVAL_COPY_VALUE(&ce->default_static_members_table[property_info->offset], property); - if (!ZEND_MAP_PTR(ce->static_members_table)) { - ZEND_ASSERT(ce->type == ZEND_INTERNAL_CLASS); - if (!EG(current_execute_data)) { - ZEND_MAP_PTR_NEW(ce->static_members_table); + + if (access_type & ZEND_ACC_STATIC) { + if ((property_info_ptr = zend_hash_find_ptr(&ce->properties_info, name)) != NULL && + (property_info_ptr->flags & ZEND_ACC_STATIC) != 0) { + property_info->offset = property_info_ptr->offset; + zval_ptr_dtor(&ce->default_static_members_table[property_info->offset]); + zend_hash_del(&ce->properties_info, name); } else { - /* internal class loaded by dl() */ - ZEND_MAP_PTR_INIT(ce->static_members_table, &ce->default_static_members_table); + property_info->offset = ce->default_static_members_count++; + ce->default_static_members_table = perealloc(ce->default_static_members_table, sizeof(zval) * ce->default_static_members_count, ce->type == ZEND_INTERNAL_CLASS); + } + ZVAL_COPY_VALUE(&ce->default_static_members_table[property_info->offset], property); + if (!ZEND_MAP_PTR(ce->static_members_table)) { + ZEND_ASSERT(ce->type == ZEND_INTERNAL_CLASS); + if (!EG(current_execute_data)) { + ZEND_MAP_PTR_NEW(ce->static_members_table); + } else { + /* internal class loaded by dl() */ + ZEND_MAP_PTR_INIT(ce->static_members_table, &ce->default_static_members_table); + } } - } - } else { - zval *property_default_ptr; - if ((property_info_ptr = zend_hash_find_ptr(&ce->properties_info, name)) != NULL && - (property_info_ptr->flags & ZEND_ACC_STATIC) == 0) { - property_info->offset = property_info_ptr->offset; - zval_ptr_dtor(&ce->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]); - zend_hash_del(&ce->properties_info, name); - - ZEND_ASSERT(ce->type == ZEND_INTERNAL_CLASS); - ZEND_ASSERT(ce->properties_info_table != NULL); - ce->properties_info_table[OBJ_PROP_TO_NUM(property_info->offset)] = property_info; } else { - property_info->offset = OBJ_PROP_TO_OFFSET(ce->default_properties_count); - ce->default_properties_count++; - ce->default_properties_table = perealloc(ce->default_properties_table, sizeof(zval) * ce->default_properties_count, ce->type == ZEND_INTERNAL_CLASS); - - /* For user classes this is handled during linking */ - if (ce->type == ZEND_INTERNAL_CLASS) { - ce->properties_info_table = perealloc(ce->properties_info_table, sizeof(zend_property_info *) * ce->default_properties_count, 1); - ce->properties_info_table[ce->default_properties_count - 1] = property_info; + zval *property_default_ptr; + if ((property_info_ptr = zend_hash_find_ptr(&ce->properties_info, name)) != NULL && + (property_info_ptr->flags & ZEND_ACC_STATIC) == 0) { + property_info->offset = property_info_ptr->offset; + zval_ptr_dtor(&ce->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]); + zend_hash_del(&ce->properties_info, name); + + ZEND_ASSERT(ce->type == ZEND_INTERNAL_CLASS); + ZEND_ASSERT(ce->properties_info_table != NULL); + ce->properties_info_table[OBJ_PROP_TO_NUM(property_info->offset)] = property_info; + } else { + property_info->offset = OBJ_PROP_TO_OFFSET(ce->default_properties_count); + ce->default_properties_count++; + ce->default_properties_table = perealloc(ce->default_properties_table, sizeof(zval) * ce->default_properties_count, ce->type == ZEND_INTERNAL_CLASS); + + /* For user classes this is handled during linking */ + if (ce->type == ZEND_INTERNAL_CLASS) { + ce->properties_info_table = perealloc(ce->properties_info_table, sizeof(zend_property_info *) * ce->default_properties_count, 1); + ce->properties_info_table[ce->default_properties_count - 1] = property_info; + } } + property_default_ptr = &ce->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; + ZVAL_COPY_VALUE(property_default_ptr, property); + Z_PROP_FLAG_P(property_default_ptr) = Z_ISUNDEF_P(property) ? IS_PROP_UNINIT : 0; } - property_default_ptr = &ce->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; - ZVAL_COPY_VALUE(property_default_ptr, property); - Z_PROP_FLAG_P(property_default_ptr) = Z_ISUNDEF_P(property) ? IS_PROP_UNINIT : 0; + } else { + /* Virtual properties have no backing storage, the offset should never be used. */ + property_info->offset = (uint32_t)-1; } if (ce->type & ZEND_INTERNAL_CLASS) { /* Must be interned to avoid ZTS data races */ if (is_persistent_class(ce)) { name = zend_new_interned_string(zend_string_copy(name)); } - - if (Z_REFCOUNTED_P(property)) { - zend_error_noreturn(E_CORE_ERROR, "Internal zvals cannot be refcounted"); - } } if (access_type & ZEND_ACC_PUBLIC) { diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2a04cf8c944d9..a9a742ee6c35f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6464,6 +6464,25 @@ static void zend_compile_accessors( zend_property_info *prop_info, zend_string *prop_name, zend_ast *prop_type_ast, zend_ast_list *accessors); +static uint32_t get_virtual_flag(zend_ast *accessors_ast) { + if (!accessors_ast) { + /* If no accessors are used, a backing property is certainly needed. */ + return 0; + } + + /* If any of the accessors are auto-generated, a backing property is needed. */ + zend_ast_list *accessors = zend_ast_get_list(accessors_ast); + for (uint32_t i = 0; i < accessors->children; i++) { + zend_ast_decl *accessor = (zend_ast_decl *) accessors->child[i]; + if (!accessor->child[2]) { + return 0; + } + } + + /* This property is purely virtual. */ + return ZEND_ACC_VIRTUAL; +} + 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); @@ -6666,7 +6685,9 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall 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, visibility | ZEND_ACC_PROMOTED, doc_comment, type); + scope, name, &default_value, + visibility | get_virtual_flag(accessors_ast) | ZEND_ACC_PROMOTED, + doc_comment, type); if (accessors_ast) { zend_ast_list *accessors = zend_ast_get_list(accessors_ast); for (uint32_t i = 0; i < accessors->children; i++) { @@ -7463,7 +7484,8 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z ZVAL_UNDEF(&value_zv); } - info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type); + info = zend_declare_typed_property( + ce, name, &value_zv, flags | get_virtual_flag(accessors_ast), doc_comment, type); if (accessors_ast) { zend_compile_accessors(info, name, type_ast, zend_ast_get_list(accessors_ast)); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index a63736c51beb3..e2ab1c7c5a47e 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -235,12 +235,15 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ -/* Property Flags (unused: 8...) | | | */ +/* Property Flags (unused: 9...) | | | */ /* =========== | | | */ /* | | | */ /* Promoted property / parameter | | | */ #define ZEND_ACC_PROMOTED (1 << 7) /* | | X | */ /* | | | */ +/* Virtual property without backing storage | | | */ +#define ZEND_ACC_VIRTUAL (1 << 8) /* | | X | */ +/* | | | */ /* Class Flags (unused: 29...) | | | */ /* =========== | | | */ /* | | | */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 514a69e817c34..7247cec5edbc8 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1140,15 +1140,20 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke 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]); + } else if ((child_info->flags & ZEND_ACC_STATIC) == 0 + && !(parent_info->flags & ZEND_ACC_VIRTUAL)) { + if (!(child_info->flags & ZEND_ACC_VIRTUAL)) { + 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_accessors = parent_info->accessors; @@ -1307,7 +1312,8 @@ void zend_build_properties_info_table(zend_class_entry *ce) } ZEND_HASH_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(); @@ -2123,6 +2129,15 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } else { not_compatible = 1; + if (colliding_prop->accessors || property_info->accessors) { + zend_error_noreturn(E_COMPILE_ERROR, + "%s and %s define the same accessor property ($%s) in the composition of %s. Conflict resolution between accessor properties is currently not supported. Class was composed", + ZSTR_VAL(find_first_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 & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC)) == (flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC)) && property_types_compatible(property_info, colliding_prop, PROP_INVARIANT) == INHERITANCE_SUCCESS @@ -2172,28 +2187,22 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent ZSTR_VAL(ce->name)); } - if (colliding_prop->accessors || property_info->accessors) { - zend_error_noreturn(E_COMPILE_ERROR, - "%s and %s define the same accessor property ($%s) in the composition of %s. Conflict resolution between accessor properties is currently not supported. Class was composed", - ZSTR_VAL(find_first_definition(ce, traits, i, prop_name, colliding_prop->ce)->name), - ZSTR_VAL(property_info->ce->name), - ZSTR_VAL(prop_name), - ZSTR_VAL(ce->name)); - } - continue; } } /* 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); + 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 = NULL; } - - 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; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 846bd91423f51..db03dd75f0526 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -706,7 +706,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int 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)) { + (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)) { if (UNEXPECTED(Z_TYPE_P(rv) != IS_OBJECT)) { zend_error(E_NOTICE, "Indirect modification of accessor property %s::$%s has no effect (did you mean to use \"&get\"?)", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); } @@ -720,6 +720,13 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto exit; } + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, + "Cannot recursively read %s::$%s in accessor without backing property", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(uninitialized_zval); + } + property_offset = prop_info->offset; goto try_again; } @@ -932,6 +939,13 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva (void*)((uintptr_t)CACHED_PTR_EX(cache_slot + 1) | ZEND_ACCESSOR_SIMPLE_WRITE_BIT)); } + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, + "Cannot recursively write %s::$%s in accessor without backing property", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(error_zval); + } + property_offset = prop_info->offset; if (!ZEND_TYPE_IS_SET(prop_info->type)) { prop_info = NULL; @@ -1926,6 +1940,13 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has } } + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, + "Cannot recursively read %s::$%s in accessor without backing property", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return 0; + } + property_offset = prop_info->offset; goto try_again; } From 5a499744a2516aaaafcfb247cd037bd2bbb54bd3 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 30 Apr 2021 12:15:30 +0200 Subject: [PATCH 24/44] Blanket forbid recursive accessor read/writes Don't make this dependent on a backing property, that just makes it unnecessarily confusing. --- .../{no_backing_prop.phpt => recursion.phpt} | 39 ++++-- Zend/zend_object_handlers.c | 123 ++++++++---------- 2 files changed, 82 insertions(+), 80 deletions(-) rename Zend/tests/accessors/{no_backing_prop.phpt => recursion.phpt} (59%) diff --git a/Zend/tests/accessors/no_backing_prop.phpt b/Zend/tests/accessors/recursion.phpt similarity index 59% rename from Zend/tests/accessors/no_backing_prop.phpt rename to Zend/tests/accessors/recursion.phpt index 69a4fe21342ed..92962e3625ba0 100644 --- a/Zend/tests/accessors/no_backing_prop.phpt +++ b/Zend/tests/accessors/recursion.phpt @@ -1,5 +1,5 @@ --TEST-- -Recursion behavior of properties without backing property +Recursion behavior of accessors --FILE-- prop4 = $value * 2; } } @@ -49,10 +49,22 @@ try { } $test->prop3 = 1; -var_dump($test->prop3); -var_dump(isset($test->prop3)); +try { + var_dump($test->prop3); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(isset($test->prop3)); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} -$test->prop4 = 1; +try { + $test->prop4 = 1; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} var_dump($test->prop4); var_dump(isset($test->prop4)); @@ -60,17 +72,18 @@ var_dump($test); ?> --EXPECT-- -Cannot recursively write Test::$prop in accessor without backing property -Cannot recursively read Test::$prop in accessor without backing property -Cannot recursively read Test::$prop in accessor without backing property -Cannot recursively read Test::$prop2 in accessor without backing property -int(2) -bool(true) -int(2) +Cannot recursively write Test::$prop in accessor +Cannot recursively read Test::$prop in accessor +Cannot recursively read Test::$prop in accessor +Cannot recursively read Test::$prop2 in accessor +Cannot recursively read Test::$prop3 in accessor +Cannot recursively read Test::$prop3 in accessor +Cannot recursively write Test::$prop4 in accessor +int(0) bool(true) object(Test)#1 (2) { ["prop3"]=> int(1) ["prop4"]=> - int(2) + int(0) } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index db03dd75f0526..1725ba7875ffc 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -645,7 +645,6 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int /* make zend_get_property_info silent if we have getter - we may want to use it */ property_offset = zend_get_property_offset(zobj->ce, name, (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot, &prop_info); -try_again: if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { retval = OBJ_PROP(zobj, property_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { @@ -697,38 +696,32 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int if (get) { if (!(get->op_array.fn_flags & ZEND_ACC_AUTO_PROP)) { guard = zend_get_property_guard(zobj, name); - if (!((*guard) & IN_GET)) { - GC_ADDREF(zobj); - *guard |= IN_GET; - zend_call_known_instance_method_with_0_params(get, zobj, rv); - *guard &= ~IN_GET; - - 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)) { - if (UNEXPECTED(Z_TYPE_P(rv) != IS_OBJECT)) { - zend_error(E_NOTICE, "Indirect modification of accessor property %s::$%s has no effect (did you mean to use \"&get\"?)", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); - } - } - } else { - retval = &EG(uninitialized_zval); - } - - /* The return type is already enforced through the method return type. */ - OBJ_RELEASE(zobj); - goto exit; - } - - if (prop_info->flags & ZEND_ACC_VIRTUAL) { - zend_throw_error(NULL, - "Cannot recursively read %s::$%s in accessor without backing property", + if (UNEXPECTED((*guard) & IN_GET)) { + zend_throw_error(NULL, "Cannot recursively read %s::$%s in accessor", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); return &EG(uninitialized_zval); } - property_offset = prop_info->offset; - goto try_again; + GC_ADDREF(zobj); + *guard |= IN_GET; + zend_call_known_instance_method_with_0_params(get, zobj, rv); + *guard &= ~IN_GET; + + 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)) { + if (UNEXPECTED(Z_TYPE_P(rv) != IS_OBJECT)) { + zend_error(E_NOTICE, "Indirect modification of accessor property %s::$%s has no effect (did you mean to use \"&get\"?)", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + } + } + } else { + retval = &EG(uninitialized_zval); + } + + /* The return type is already enforced through the method return type. */ + OBJ_RELEASE(zobj); + goto exit; } if (cache_slot && prop_info == orig_prop_info) { @@ -925,27 +918,26 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva if (set) { if (!(set->op_array.fn_flags & ZEND_ACC_AUTO_PROP)) { uint32_t *guard = zend_get_property_guard(zobj, name); - if (!((*guard) & IN_SET)) { - GC_ADDREF(zobj); - (*guard) |= IN_SET; - zend_call_known_instance_method_with_1_params(set, zobj, NULL, value); - (*guard) &= ~IN_SET; - OBJ_RELEASE(zobj); - return value; + if (UNEXPECTED((*guard) & IN_SET)) { + zend_throw_error(NULL, "Cannot recursively write %s::$%s in accessor", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(error_zval); } - } else if (cache_slot && prop_info == orig_prop_info) { + + GC_ADDREF(zobj); + (*guard) |= IN_SET; + zend_call_known_instance_method_with_1_params(set, zobj, NULL, value); + (*guard) &= ~IN_SET; + OBJ_RELEASE(zobj); + return value; + } + + if (cache_slot && prop_info == orig_prop_info) { /* Cache the fact that this accessor has trivial write. */ CACHE_PTR_EX(cache_slot + 1, (void*)((uintptr_t)CACHED_PTR_EX(cache_slot + 1) | ZEND_ACCESSOR_SIMPLE_WRITE_BIT)); } - if (prop_info->flags & ZEND_ACC_VIRTUAL) { - zend_throw_error(NULL, - "Cannot recursively write %s::$%s in accessor without backing property", - ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); - return &EG(error_zval); - } - property_offset = prop_info->offset; if (!ZEND_TYPE_IS_SET(prop_info->type)) { prop_info = NULL; @@ -1920,31 +1912,28 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has if (!(get->common.fn_flags & ZEND_ACC_AUTO_PROP)) { uint32_t *guard = zend_get_property_guard(zobj, name); - if (!(*guard & IN_GET)) { - zval rv; - GC_ADDREF(zobj); - *guard |= IN_GET; - zend_call_known_instance_method_with_0_params(get, zobj, &rv); - *guard &= ~IN_GET; - OBJ_RELEASE(zobj); - - 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; + if (UNEXPECTED(*guard & IN_GET)) { + zend_throw_error(NULL, "Cannot recursively read %s::$%s in accessor", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return 0; } - } - if (prop_info->flags & ZEND_ACC_VIRTUAL) { - zend_throw_error(NULL, - "Cannot recursively read %s::$%s in accessor without backing property", - ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); - return 0; + zval rv; + GC_ADDREF(zobj); + *guard |= IN_GET; + zend_call_known_instance_method_with_0_params(get, zobj, &rv); + *guard &= ~IN_GET; + OBJ_RELEASE(zobj); + + 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; } property_offset = prop_info->offset; From afddb9d48358f9599c273eb4ec81151c3cbbc18b Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 30 Apr 2021 12:30:47 +0200 Subject: [PATCH 25/44] Don't allow default value on virtual accessors --- Zend/tests/accessors/default_on_virtual.phpt | 16 ++++++++++++++++ Zend/zend_compile.c | 9 +++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/accessors/default_on_virtual.phpt diff --git a/Zend/tests/accessors/default_on_virtual.phpt b/Zend/tests/accessors/default_on_virtual.phpt new file mode 100644 index 0000000000000..3c08fdcf0ca48 --- /dev/null +++ b/Zend/tests/accessors/default_on_virtual.phpt @@ -0,0 +1,16 @@ +--TEST-- +Purely virtual accessors cannot have default value +--FILE-- +_prop; } + set { $this->_prop = $value; } + } +} + +?> +--EXPECTF-- +Fatal error: Cannot specify default value for property with explicit accessors in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a9a742ee6c35f..fde56773e63e2 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7424,6 +7424,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z zend_string *doc_comment = NULL; zval value_zv; zend_type type = ZEND_TYPE_INIT_NONE(0); + flags |= get_virtual_flag(accessors_ast); if (!accessors_ast) { if (ce->ce_flags & ZEND_ACC_INTERFACE) { @@ -7478,14 +7479,18 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(str)); } } + + if (flags & ZEND_ACC_VIRTUAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot specify default value for property with explicit accessors"); + } } else if (!ZEND_TYPE_IS_SET(type)) { ZVAL_NULL(&value_zv); } else { ZVAL_UNDEF(&value_zv); } - info = zend_declare_typed_property( - ce, name, &value_zv, flags | get_virtual_flag(accessors_ast), doc_comment, type); + info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type); if (accessors_ast) { zend_compile_accessors(info, name, type_ast, zend_ast_get_list(accessors_ast)); From d5e61988528475c33f2700b04195398bedbc12f1 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 30 Apr 2021 12:49:35 +0200 Subject: [PATCH 26/44] Don't allow mixing implicit and explicit accessors These can still be mixed through inheritance, but at least we shouldn't allow having a property that directly defines one accessor as implicit and the other as explicit. --- Zend/tests/accessors/dump.phpt | 17 +------- .../invalid_implicit_explicit_mix.phpt | 15 +++++++ .../accessors/property_promotion_invalid.phpt | 2 +- Zend/tests/accessors/recursion.phpt | 42 +------------------ Zend/zend_compile.c | 25 +++++++---- 5 files changed, 36 insertions(+), 65 deletions(-) create mode 100644 Zend/tests/accessors/invalid_implicit_explicit_mix.phpt diff --git a/Zend/tests/accessors/dump.phpt b/Zend/tests/accessors/dump.phpt index f38437cc9d208..0cc4a44be4bee 100644 --- a/Zend/tests/accessors/dump.phpt +++ b/Zend/tests/accessors/dump.phpt @@ -5,19 +5,10 @@ Dumping object with accessors // The dump always only shows the contents of backing properties, // not any computed properties. -// TODO: prop5 shouldn't have a backing property. class Test { public $prop = 1; public $prop2 = 2 { get; set; } - public $prop3 = 3 { - get; - set {} - } - public $prop4 = 4 { - get { return $this->prop4 * 2; } - set; - } - public $prop5 { + public $prop3 { get { return 42; } } } @@ -26,13 +17,9 @@ var_dump(new Test); ?> --EXPECT-- -object(Test)#1 (4) { +object(Test)#1 (2) { ["prop"]=> int(1) ["prop2"]=> int(2) - ["prop3"]=> - int(3) - ["prop4"]=> - int(4) } diff --git a/Zend/tests/accessors/invalid_implicit_explicit_mix.phpt b/Zend/tests/accessors/invalid_implicit_explicit_mix.phpt new file mode 100644 index 0000000000000..2572977982823 --- /dev/null +++ b/Zend/tests/accessors/invalid_implicit_explicit_mix.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot mix implicit and explicit get/set accessors +--FILE-- + +--EXPECTF-- +Fatal error: Cannot specify both implicit and explicit accessors for the same property in %s on line %d diff --git a/Zend/tests/accessors/property_promotion_invalid.phpt b/Zend/tests/accessors/property_promotion_invalid.phpt index 0363679ec7243..3acafcfaa7b1b 100644 --- a/Zend/tests/accessors/property_promotion_invalid.phpt +++ b/Zend/tests/accessors/property_promotion_invalid.phpt @@ -14,4 +14,4 @@ class Test { ?> --EXPECTF-- -Fatal error: Cannot declare complex accessors for a promoted property in %s on line %d +Fatal error: Can only use implicit accessors for a promoted property in %s on line %d diff --git a/Zend/tests/accessors/recursion.phpt b/Zend/tests/accessors/recursion.phpt index 92962e3625ba0..d78e44658e294 100644 --- a/Zend/tests/accessors/recursion.phpt +++ b/Zend/tests/accessors/recursion.phpt @@ -14,16 +14,6 @@ class Test { get { return isset($this->prop2); } set { } } - - public int $prop3 { - get { return $this->prop3 * 2; } - set; - } - - public int $prop4 = 0 { - get; - set { $this->prop4 = $value * 2; } - } } $test = new Test; @@ -47,27 +37,6 @@ try { } catch (Error $e) { echo $e->getMessage(), "\n"; } - -$test->prop3 = 1; -try { - var_dump($test->prop3); -} catch (Error $e) { - echo $e->getMessage(), "\n"; -} -try { - var_dump(isset($test->prop3)); -} catch (Error $e) { - echo $e->getMessage(), "\n"; -} - -try { - $test->prop4 = 1; -} catch (Error $e) { - echo $e->getMessage(), "\n"; -} -var_dump($test->prop4); -var_dump(isset($test->prop4)); - var_dump($test); ?> @@ -76,14 +45,5 @@ Cannot recursively write Test::$prop in accessor Cannot recursively read Test::$prop in accessor Cannot recursively read Test::$prop in accessor Cannot recursively read Test::$prop2 in accessor -Cannot recursively read Test::$prop3 in accessor -Cannot recursively read Test::$prop3 in accessor -Cannot recursively write Test::$prop4 in accessor -int(0) -bool(true) -object(Test)#1 (2) { - ["prop3"]=> - int(1) - ["prop4"]=> - int(0) +object(Test)#1 (0) { } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index fde56773e63e2..ae1c9eeea6b04 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6694,7 +6694,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall zend_ast_decl *accessor = (zend_ast_decl *) accessors->child[i]; if (accessor->child[2]) { zend_error_noreturn(E_COMPILE_ERROR, - "Cannot declare complex accessors for a promoted property"); + "Can only use implicit accessors for a promoted property"); } } zend_compile_accessors(prop, name, type_ast, accessors); @@ -7217,6 +7217,8 @@ static void zend_compile_accessors( zend_error_noreturn(E_COMPILE_ERROR, "Accessor list cannot be empty"); } + bool has_any_implicit_accessor = false; + bool has_any_explicit_accessor = false; for (uint32_t i = 0; i < accessors->children; i++) { zend_ast_decl *accessor = (zend_ast_decl *) accessors->child[i]; zend_string *name = accessor->name; @@ -7303,7 +7305,9 @@ static void zend_compile_accessors( ZSTR_VAL(name)); } - bool auto_prop = !orig_stmt_ast && !(accessor->flags & ZEND_ACC_ABSTRACT); + /* Abstract properties are neither explicit nor implicit. */ + bool implicit_prop = !orig_stmt_ast && !(accessor->flags & ZEND_ACC_ABSTRACT); + bool explicit_prop = orig_stmt_ast && !(accessor->flags & ZEND_ACC_ABSTRACT); if (zend_string_equals_literal_ci(name, "get")) { accessor_kind = ZEND_ACCESSOR_GET; if (param_list) { @@ -7318,7 +7322,7 @@ static void zend_compile_accessors( reset_return_ast = true; *return_ast_ptr = prop_type_ast; - if (auto_prop) { + if (implicit_prop) { zend_ast *prop_fetch = zend_ast_create(ZEND_AST_PROP, zend_ast_create(ZEND_AST_VAR, zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS))), @@ -7349,7 +7353,7 @@ static void zend_compile_accessors( "Accessor \"set\" cannot return by reference"); } - if (auto_prop) { + if (implicit_prop) { zend_ast *prop_fetch = zend_ast_create(ZEND_AST_PROP, zend_ast_create(ZEND_AST_VAR, zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS))), @@ -7368,12 +7372,12 @@ static void zend_compile_accessors( accessor->name = zend_strpprintf(0, "$%s::%s", ZSTR_VAL(prop_name), ZSTR_VAL(name)); zend_function *func = (zend_function *) zend_compile_func_decl( NULL, (zend_ast *) accessor, /* toplevel */ false); - if (auto_prop) { + if (implicit_prop) { + has_any_implicit_accessor = true; func->common.fn_flags |= ZEND_ACC_AUTO_PROP; } - - // TODO: Make this more precise. - if (!auto_prop) { + if (explicit_prop) { + has_any_explicit_accessor = true; ce->ce_flags |= ZEND_ACC_USE_GUARDS; } @@ -7397,6 +7401,11 @@ static void zend_compile_accessors( zend_ast_get_list(accessor->child[0])->child[0]->child[0] = NULL; } } + + if (has_any_explicit_accessor && has_any_implicit_accessor) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot specify both implicit and explicit accessors for the same property"); + } } void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */ From af100cc1bad2a6c83031c50f10ae53f352ac3f23 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 30 Apr 2021 15:30:32 +0200 Subject: [PATCH 27/44] Forbid implicit set without get --- Zend/tests/accessors/implicit_set_without_get.phpt | 12 ++++++++++++ .../accessors/type_compatibility_invalid_2.phpt | 4 ++-- Zend/zend_compile.c | 12 +++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/accessors/implicit_set_without_get.phpt diff --git a/Zend/tests/accessors/implicit_set_without_get.phpt b/Zend/tests/accessors/implicit_set_without_get.phpt new file mode 100644 index 0000000000000..3042e8f41796f --- /dev/null +++ b/Zend/tests/accessors/implicit_set_without_get.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot have implicit set without get +--FILE-- + +--EXPECTF-- +Fatal error: Cannot have implicit set without get in %s on line %d diff --git a/Zend/tests/accessors/type_compatibility_invalid_2.phpt b/Zend/tests/accessors/type_compatibility_invalid_2.phpt index 1b44b2c05bd31..903dd30cd9e10 100644 --- a/Zend/tests/accessors/type_compatibility_invalid_2.phpt +++ b/Zend/tests/accessors/type_compatibility_invalid_2.phpt @@ -4,10 +4,10 @@ Invalid type compatibility for write-only property diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ae1c9eeea6b04..a7bf0ac82daa9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7217,7 +7217,8 @@ static void zend_compile_accessors( zend_error_noreturn(E_COMPILE_ERROR, "Accessor list cannot be empty"); } - bool has_any_implicit_accessor = false; + bool has_implicit_get = false; + bool has_implicit_set = false; bool has_any_explicit_accessor = false; for (uint32_t i = 0; i < accessors->children; i++) { zend_ast_decl *accessor = (zend_ast_decl *) accessors->child[i]; @@ -7323,6 +7324,7 @@ static void zend_compile_accessors( *return_ast_ptr = prop_type_ast; if (implicit_prop) { + has_implicit_get = true; zend_ast *prop_fetch = zend_ast_create(ZEND_AST_PROP, zend_ast_create(ZEND_AST_VAR, zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS))), @@ -7354,6 +7356,7 @@ static void zend_compile_accessors( } if (implicit_prop) { + has_implicit_set = true; zend_ast *prop_fetch = zend_ast_create(ZEND_AST_PROP, zend_ast_create(ZEND_AST_VAR, zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS))), @@ -7373,7 +7376,6 @@ static void zend_compile_accessors( zend_function *func = (zend_function *) zend_compile_func_decl( NULL, (zend_ast *) accessor, /* toplevel */ false); if (implicit_prop) { - has_any_implicit_accessor = true; func->common.fn_flags |= ZEND_ACC_AUTO_PROP; } if (explicit_prop) { @@ -7402,10 +7404,14 @@ static void zend_compile_accessors( } } - if (has_any_explicit_accessor && has_any_implicit_accessor) { + if (has_any_explicit_accessor && (has_implicit_get || has_implicit_set)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot specify both implicit and explicit accessors for the same property"); } + + if (has_implicit_set && !prop_info->accessors[ZEND_ACCESSOR_GET]) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot have implicit set without get"); + } } void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */ From b79cbdb408191eee31d018ce75fbef7afb9d4ca9 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 30 Apr 2021 15:43:27 +0200 Subject: [PATCH 28/44] Support init-only properties If only implicit get is specified, allow an initialization assignment. --- Zend/tests/accessors/init_only.phpt | 31 +++++++++++++++++++++++++++++ Zend/zend_object_handlers.c | 14 ++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/accessors/init_only.phpt diff --git a/Zend/tests/accessors/init_only.phpt b/Zend/tests/accessors/init_only.phpt new file mode 100644 index 0000000000000..b054f9af14482 --- /dev/null +++ b/Zend/tests/accessors/init_only.phpt @@ -0,0 +1,31 @@ +--TEST-- +Accessor with only implicit get (init-only) +--FILE-- +prop = $prop; + } +} + +$test = new Test(42); +var_dump($test->prop); +try { + $test->prop = 24; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->prop += 24; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +int(42) +Property Test::$prop is read-only +Property Test::$prop is read-only diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 1725ba7875ffc..6fa70b05a7dfd 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -907,6 +907,18 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva } else if (IS_ACCESSOR_PROPERTY_OFFSET(property_offset)) { zend_function *set = prop_info->accessors[ZEND_ACCESSOR_SET]; if (!set) { + zend_function *get = prop_info->accessors[ZEND_ACCESSOR_GET]; + if (get && (get->common.fn_flags & ZEND_ACC_AUTO_PROP)) { + /* Auto-property with only get allows initialization assignment. */ + property_offset = prop_info->offset; + variable_ptr = OBJ_PROP(zobj, property_offset); + if (Z_TYPE_P(variable_ptr) == IS_UNDEF) { + ZEND_ASSERT(Z_PROP_FLAG_P(variable_ptr) == IS_PROP_UNINIT); + Z_PROP_FLAG_P(variable_ptr) = 0; + goto write_std_property; + } + } + zend_throw_error(NULL, "Property %s::$%s is read-only", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); return &EG(error_zval); @@ -916,7 +928,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva zend_property_info *orig_prop_info = prop_info; set = check_accessor_visibility(&prop_info, zobj->ce, name, set, silent); if (set) { - if (!(set->op_array.fn_flags & ZEND_ACC_AUTO_PROP)) { + if (!(set->common.fn_flags & ZEND_ACC_AUTO_PROP)) { uint32_t *guard = zend_get_property_guard(zobj, name); if (UNEXPECTED((*guard) & IN_SET)) { zend_throw_error(NULL, "Cannot recursively write %s::$%s in accessor", From afa6e7ef2fafeecb4ccc9a0a9a9ad2bd980a9ecc Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 30 Apr 2021 16:04:46 +0200 Subject: [PATCH 29/44] Never give accessors an implicit null default value --- Zend/tests/accessors/init_only.phpt | 42 +++++++++++++++-- Zend/tests/accessors/unserialize.phpt | 46 +++++++++++++++++++ .../mixed_property_strict_success.phpt | 2 +- .../mixed_property_weak_success.phpt | 2 +- .../typed_properties_002.phpt | 2 +- .../typed_properties_026.phpt | 2 +- .../typed_properties_047.phpt | 2 +- .../typed_properties_083.phpt | 2 +- .../typed_properties_magic_set.phpt | 2 +- Zend/zend_compile.c | 4 +- Zend/zend_object_handlers.c | 5 +- 11 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 Zend/tests/accessors/unserialize.phpt diff --git a/Zend/tests/accessors/init_only.phpt b/Zend/tests/accessors/init_only.phpt index b054f9af14482..b9d56366c5dfd 100644 --- a/Zend/tests/accessors/init_only.phpt +++ b/Zend/tests/accessors/init_only.phpt @@ -5,27 +5,59 @@ Accessor with only implicit get (init-only) class Test { public int $prop { get; } + public $prop2 { get; } + public $prop3 { get; } - public function __construct(int $prop) { + public function __construct(int $prop, $prop2) { $this->prop = $prop; + $this->prop2 = $prop2; } } -$test = new Test(42); +$test = new Test(1, 2); var_dump($test->prop); try { - $test->prop = 24; + $test->prop = 3; } catch (Error $e) { echo $e->getMessage(), "\n"; } try { - $test->prop += 24; + $test->prop += 4; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +echo "\n"; + +var_dump($test->prop2); +try { + $test->prop2 = 5; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +echo "\n"; + +try { + var_dump($test->prop3); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +$test->prop3 = 6; +var_dump($test->prop3); +try { + $test->prop3 = 7; } catch (Error $e) { echo $e->getMessage(), "\n"; } ?> --EXPECT-- -int(42) +int(1) Property Test::$prop is read-only Property Test::$prop is read-only + +int(2) +Property Test::$prop2 is read-only + +Property Test::$prop3 must not be accessed before initialization +int(6) +Property Test::$prop3 is read-only diff --git a/Zend/tests/accessors/unserialize.phpt b/Zend/tests/accessors/unserialize.phpt new file mode 100644 index 0000000000000..65c41ca08ca7c --- /dev/null +++ b/Zend/tests/accessors/unserialize.phpt @@ -0,0 +1,46 @@ +--TEST-- +unserialize() with accessors +--FILE-- +prop3; +$test2->prop3 = 42; + +// TODO: This should probably be forbidden. +$s = 'O:4:"Test":1:{s:5:"prop3";i:42;}'; +var_dump($test3 = unserialize($s)); +$test3->prop3; +$test3->prop3 = 42; + +?> +--EXPECT-- +string(47) "O:4:"Test":2:{s:5:"prop1";i:1;s:5:"prop2";i:2;}" +object(Test)#2 (2) { + ["prop1"]=> + int(1) + ["prop2"]=> + int(2) +} +Test::$prop3::get +Test::$prop3::set +object(Test)#3 (1) { + ["prop3"]=> + int(42) +} +Test::$prop3::get +Test::$prop3::set diff --git a/Zend/tests/type_declarations/mixed/validation/mixed_property_strict_success.phpt b/Zend/tests/type_declarations/mixed/validation/mixed_property_strict_success.phpt index 8a2c60002054e..587087b6cb929 100644 --- a/Zend/tests/type_declarations/mixed/validation/mixed_property_strict_success.phpt +++ b/Zend/tests/type_declarations/mixed/validation/mixed_property_strict_success.phpt @@ -33,4 +33,4 @@ try { ?> --EXPECT-- -Typed property Foo::$property1 must not be accessed before initialization +Property Foo::$property1 must not be accessed before initialization diff --git a/Zend/tests/type_declarations/mixed/validation/mixed_property_weak_success.phpt b/Zend/tests/type_declarations/mixed/validation/mixed_property_weak_success.phpt index e83023d54cf28..8c6e48b365906 100644 --- a/Zend/tests/type_declarations/mixed/validation/mixed_property_weak_success.phpt +++ b/Zend/tests/type_declarations/mixed/validation/mixed_property_weak_success.phpt @@ -32,4 +32,4 @@ try { ?> --EXPECT-- -Typed property Foo::$property1 must not be accessed before initialization +Property Foo::$property1 must not be accessed before initialization diff --git a/Zend/tests/type_declarations/typed_properties_002.phpt b/Zend/tests/type_declarations/typed_properties_002.phpt index b0d1e1c0f2795..c5be86ebcbfc4 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: 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..34eb26d3238ee 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: 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/tests/type_declarations/typed_properties_047.phpt b/Zend/tests/type_declarations/typed_properties_047.phpt index 5de7b254d9934..85801608934f7 100644 --- a/Zend/tests/type_declarations/typed_properties_047.phpt +++ b/Zend/tests/type_declarations/typed_properties_047.phpt @@ -36,5 +36,5 @@ object(Foo)#1 (1) { NULL int(5) NULL -Typed property Foo::$foo must not be accessed before initialization +Property Foo::$foo must not be accessed before initialization Cannot assign string to property Foo::$foo of type ?int diff --git a/Zend/tests/type_declarations/typed_properties_083.phpt b/Zend/tests/type_declarations/typed_properties_083.phpt index f67eec302a4ec..d3f15cfe06ac2 100644 --- a/Zend/tests/type_declarations/typed_properties_083.phpt +++ b/Zend/tests/type_declarations/typed_properties_083.phpt @@ -61,7 +61,7 @@ array(1) { int(1) } string(71) "Cannot auto-initialize an array inside property Foo::$p of type ?string" -string(65) "Typed property Foo::$p must not be accessed before initialization" +string(59) "Property Foo::$p must not be accessed before initialization" string(71) "Cannot auto-initialize an array inside property Foo::$p of type ?string" NULL array(1) { diff --git a/Zend/tests/type_declarations/typed_properties_magic_set.phpt b/Zend/tests/type_declarations/typed_properties_magic_set.phpt index 70a7d4fe6f07b..4e91c6dff64d1 100644 --- a/Zend/tests/type_declarations/typed_properties_magic_set.phpt +++ b/Zend/tests/type_declarations/typed_properties_magic_set.phpt @@ -61,7 +61,7 @@ $test->foo = 42; ?> --EXPECT-- -Typed property Test::$foo must not be accessed before initialization +Property Test::$foo must not be accessed before initialization bool(false) int(42) __set foo = 42 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a7bf0ac82daa9..8093a11191235 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6676,7 +6676,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall /* 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. */ zval default_value; - if (ZEND_TYPE_IS_SET(type)) { + if (ZEND_TYPE_IS_SET(type) || accessors_ast) { ZVAL_UNDEF(&default_value); } else { ZVAL_NULL(&default_value); @@ -7499,7 +7499,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z zend_error_noreturn(E_COMPILE_ERROR, "Cannot specify default value for property with explicit accessors"); } - } else if (!ZEND_TYPE_IS_SET(type)) { + } else if (!ZEND_TYPE_IS_SET(type) && !accessors_ast) { ZVAL_NULL(&value_zv); } else { ZVAL_UNDEF(&value_zv); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 6fa70b05a7dfd..5cb8a980cd6dc 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -835,7 +835,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int uninit_error: if (type != BP_VAR_IS) { if (UNEXPECTED(prop_info)) { - zend_throw_error(NULL, "Typed property %s::$%s must not be accessed before initialization", + zend_throw_error(NULL, "Property %s::$%s must not be accessed before initialization", ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(name)); } else { @@ -915,6 +915,9 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva if (Z_TYPE_P(variable_ptr) == IS_UNDEF) { ZEND_ASSERT(Z_PROP_FLAG_P(variable_ptr) == IS_PROP_UNINIT); Z_PROP_FLAG_P(variable_ptr) = 0; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } goto write_std_property; } } From 17defb479a6977a193ad523ddb718f8c0e3d3fff Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 3 May 2021 10:40:35 +0200 Subject: [PATCH 30/44] Forbig isolated implicit &get --- .../tests/accessors/invalid_implicit_get_by_ref.phpt | 12 ++++++++++++ Zend/zend_compile.c | 10 +++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/accessors/invalid_implicit_get_by_ref.phpt diff --git a/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt b/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt new file mode 100644 index 0000000000000..80b328a667496 --- /dev/null +++ b/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot have only implicit &get +--FILE-- + +--EXPECTF-- +Fatal error: Cannot have implicit &get without set. Either remove the "&" or add "set" accessor in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8093a11191235..7fea4a39e9de6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7409,9 +7409,17 @@ static void zend_compile_accessors( "Cannot specify both implicit and explicit accessors for the same property"); } - if (has_implicit_set && !prop_info->accessors[ZEND_ACCESSOR_GET]) { + zend_function *get = prop_info->accessors[ZEND_ACCESSOR_GET]; + if (has_implicit_set && !get) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot have implicit set without get"); } + + if (has_implicit_get && !has_implicit_set + && (get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot have implicit &get without set. " + "Either remove the \"&\" or add \"set\" accessor"); + } } void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */ From 8f5ad8f38dd02c2f050e4f7e404a6d9a6434207e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 3 May 2021 11:47:40 +0200 Subject: [PATCH 31/44] Add test for different types of indirect modification --- .../accessors/indirect_modification.phpt | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 Zend/tests/accessors/indirect_modification.phpt diff --git a/Zend/tests/accessors/indirect_modification.phpt b/Zend/tests/accessors/indirect_modification.phpt new file mode 100644 index 0000000000000..f05b5fd75d671 --- /dev/null +++ b/Zend/tests/accessors/indirect_modification.phpt @@ -0,0 +1,81 @@ +--TEST-- +Different kinds of indirect modification with by-val and by-ref getters +--FILE-- +_byVal; } + set { + echo __METHOD__, "\n"; + $this->_byVal = $value; + } + } + + private $_byRef; + public $byRef { + &get { return $this->_byRef; } + set { + echo __METHOD__, "\n"; + $this->_byRef = $value; + } + } +} + +$test = new Test; + +$test->byVal = 0; +$test->byVal++; +++$test->byVal; +$test->byVal += 1; +var_dump($test->byVal); +$test->byVal = []; +$test->byVal[] = 1; +var_dump($test->byVal); +$ref =& $test->byVal; +$ref = 42; +var_dump($test->byVal); +echo "\n"; + +$test->byRef = 0; +$test->byRef++; +++$test->byRef; +$test->byRef += 1; +var_dump($test->byRef); +$test->byRef = []; +$test->byRef[] = 1; +var_dump($test->byRef); +$ref =& $test->byRef; +$ref = 42; +var_dump($test->byRef); + +?> +--EXPECTF-- +Test::$byVal::set +Test::$byVal::set +Test::$byVal::set +Test::$byVal::set +int(3) +Test::$byVal::set + +Notice: Indirect modification of accessor property Test::$byVal has no effect (did you mean to use "&get"?) in %s on line %d +array(0) { +} + +Notice: Indirect modification of accessor property Test::$byVal has no effect (did you mean to use "&get"?) in %s on line %d +array(0) { +} + +Test::$byRef::set +Test::$byRef::set +Test::$byRef::set +Test::$byRef::set +int(3) +Test::$byRef::set +array(1) { + [0]=> + int(1) +} +int(42) From 373f4539df327b70979c0c6da0233543706eea62 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 3 May 2021 11:57:13 +0200 Subject: [PATCH 32/44] Forbid isolated &get even if not implicit --- Zend/tests/accessors/get_by_ref.phpt | 3 +++ Zend/tests/accessors/inheritance_ref_mismatch.phpt | 4 ++-- Zend/tests/accessors/invalid_get_by_ref.phpt | 12 ++++++++++++ .../tests/accessors/invalid_implicit_get_by_ref.phpt | 4 ++-- Zend/zend_compile.c | 6 +++--- 5 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/accessors/invalid_get_by_ref.phpt diff --git a/Zend/tests/accessors/get_by_ref.phpt b/Zend/tests/accessors/get_by_ref.phpt index fb10689d0b84d..e25981f77b67f 100644 --- a/Zend/tests/accessors/get_by_ref.phpt +++ b/Zend/tests/accessors/get_by_ref.phpt @@ -7,15 +7,18 @@ class Test { public $_byVal = []; public $byVal { get { return $this->_byVal; } + set { $this->_byVal = $value; } } public $_byRef = []; public $byRef { &get { return $this->_byRef; } + set { $this->_byRef = $value; } } public int $byRefType { &get { $var = "42"; return $var; } + set { } } } diff --git a/Zend/tests/accessors/inheritance_ref_mismatch.phpt b/Zend/tests/accessors/inheritance_ref_mismatch.phpt index f85cd7b9daef9..a2d5e8565995f 100644 --- a/Zend/tests/accessors/inheritance_ref_mismatch.phpt +++ b/Zend/tests/accessors/inheritance_ref_mismatch.phpt @@ -4,10 +4,10 @@ By-ref return mismatch during inheritance diff --git a/Zend/tests/accessors/invalid_get_by_ref.phpt b/Zend/tests/accessors/invalid_get_by_ref.phpt new file mode 100644 index 0000000000000..178c46ae5a295 --- /dev/null +++ b/Zend/tests/accessors/invalid_get_by_ref.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot have only &get (explicit) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot have &get without set. Either remove the "&" or add "set" accessor in %s on line %d diff --git a/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt b/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt index 80b328a667496..cefe4bc443e51 100644 --- a/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt +++ b/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt @@ -1,5 +1,5 @@ --TEST-- -Cannot have only implicit &get +Cannot have only &get (implicit) --FILE-- --EXPECTF-- -Fatal error: Cannot have implicit &get without set. Either remove the "&" or add "set" accessor in %s on line %d +Fatal error: Cannot have &get without set. Either remove the "&" or add "set" accessor in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 7fea4a39e9de6..c78a357d973b7 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7414,10 +7414,10 @@ static void zend_compile_accessors( zend_error_noreturn(E_COMPILE_ERROR, "Cannot have implicit set without get"); } - if (has_implicit_get && !has_implicit_set - && (get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { + if (get && (get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) + && !prop_info->accessors[ZEND_ACCESSOR_SET]) { zend_error_noreturn(E_COMPILE_ERROR, - "Cannot have implicit &get without set. " + "Cannot have &get without set. " "Either remove the \"&\" or add \"set\" accessor"); } } From 45147020b7e9ad0c156c36a4579c65ae3ed470ab Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 3 May 2021 14:57:28 +0200 Subject: [PATCH 33/44] Extend dump test --- Zend/tests/accessors/dump.phpt | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Zend/tests/accessors/dump.phpt b/Zend/tests/accessors/dump.phpt index 0cc4a44be4bee..e5c33779ad41a 100644 --- a/Zend/tests/accessors/dump.phpt +++ b/Zend/tests/accessors/dump.phpt @@ -13,7 +13,14 @@ class Test { } } -var_dump(new Test); +$test = new Test; +var_dump($test); +var_dump(get_object_vars($test)); +var_dump((array) $test); + +foreach ($test as $prop => $value) { + echo "$prop => $value\n"; +} ?> --EXPECT-- @@ -23,3 +30,17 @@ object(Test)#1 (2) { ["prop2"]=> int(2) } +array(2) { + ["prop"]=> + int(1) + ["prop2"]=> + int(2) +} +array(2) { + ["prop"]=> + int(1) + ["prop2"]=> + int(2) +} +prop => 1 +prop2 => 2 From 976f2db25c50789f3f82cfcb458c8f61a5ef522e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 3 May 2021 18:35:51 +0200 Subject: [PATCH 34/44] Allow final property with private accessor --- Zend/tests/accessors/final_prop_private_set.phpt | 16 ++++++++++++++++ Zend/zend_compile.c | 11 ++++------- 2 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/accessors/final_prop_private_set.phpt diff --git a/Zend/tests/accessors/final_prop_private_set.phpt b/Zend/tests/accessors/final_prop_private_set.phpt new file mode 100644 index 0000000000000..5505259c3e592 --- /dev/null +++ b/Zend/tests/accessors/final_prop_private_set.phpt @@ -0,0 +1,16 @@ +--TEST-- +Final property with private set +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property B::$prop in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c78a357d973b7..ad311f3c5bd71 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7276,16 +7276,13 @@ static void zend_compile_accessors( // TODO: This restriction can be relaxed. zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare accessors for static property"); } - if (prop_info->flags & ZEND_ACC_FINAL) { - if (accessor->flags & ZEND_ACC_FINAL) { - zend_error_noreturn(E_COMPILE_ERROR, - "Accessor on final property cannot be explicitly final"); - } - accessor->flags |= ZEND_ACC_FINAL; - } if ((accessor->flags & ZEND_ACC_FINAL) && (accessor->flags & ZEND_ACC_PRIVATE)) { zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be both final and private"); } + if ((prop_info->flags & ZEND_ACC_FINAL) && (accessor->flags & ZEND_ACC_FINAL)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Accessor on final property cannot be explicitly final"); + } if (accessor->flags & ZEND_ACC_ABSTRACT) { if (orig_stmt_ast) { zend_error_noreturn(E_COMPILE_ERROR, "Abstract accessor cannot have body"); From 04dad4a5bf231c3202ab928c4e3182b12ef0ac89 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 3 May 2021 18:53:26 +0200 Subject: [PATCH 35/44] Expose isFinal() and isAbstract() in reflection --- ext/reflection/php_reflection.c | 14 +++++++++- ext/reflection/php_reflection.stub.php | 6 ++++ ext/reflection/php_reflection_arginfo.h | 10 ++++++- ext/reflection/tests/accessors.phpt | 37 +++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 3e1ba9653494e..ff53990437eaf 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5381,6 +5381,16 @@ ZEND_METHOD(ReflectionProperty, isStatic) } /* }}} */ +ZEND_METHOD(ReflectionProperty, isAbstract) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_ABSTRACT); +} + +ZEND_METHOD(ReflectionProperty, isFinal) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL); +} + /* {{{ Returns whether this property is default (declared at compilation time). */ ZEND_METHOD(ReflectionProperty, isDefault) { @@ -5407,7 +5417,7 @@ ZEND_METHOD(ReflectionProperty, getModifiers) { reflection_object *intern; property_reference *ref; - uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC; + uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_FINAL | ZEND_ACC_ABSTRACT; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); @@ -6979,6 +6989,8 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_PUBLIC", ZEND_ACC_PUBLIC); REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_PROTECTED", ZEND_ACC_PROTECTED); REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_PRIVATE", ZEND_ACC_PRIVATE); + REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_ABSTRACT", ZEND_ACC_ABSTRACT); + REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_FINAL", ZEND_ACC_FINAL); reflection_class_constant_ptr = register_class_ReflectionClassConstant(reflector_ptr); reflection_init_class_handlers(reflection_class_constant_ptr); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index baad157660a15..832aa0039a866 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -411,6 +411,12 @@ public function isProtected() {} /** @return bool */ public function isStatic() {} + /** @return bool */ + public function isAbstract() {} + + /** @return bool */ + public function isFinal() {} + /** @return bool */ public function isDefault() {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 9b1cf7f74afea..48b48c5764f0d 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: 7bfb995bcef07303f355cd45d8a2c8c67da0ae4a */ + * Stub hash: dc739b4c643af886db4eb97647f6d6174ee2980a */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -309,6 +309,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionProperty_isAbstract arginfo_class_ReflectionFunctionAbstract_inNamespace + +#define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace + #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionClass_isEnum @@ -669,6 +673,8 @@ ZEND_METHOD(ReflectionProperty, isPublic); ZEND_METHOD(ReflectionProperty, isPrivate); ZEND_METHOD(ReflectionProperty, isProtected); ZEND_METHOD(ReflectionProperty, isStatic); +ZEND_METHOD(ReflectionProperty, isAbstract); +ZEND_METHOD(ReflectionProperty, isFinal); ZEND_METHOD(ReflectionProperty, isDefault); ZEND_METHOD(ReflectionProperty, isPromoted); ZEND_METHOD(ReflectionProperty, getModifiers); @@ -940,6 +946,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, isPrivate, arginfo_class_ReflectionProperty_isPrivate, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isProtected, arginfo_class_ReflectionProperty_isProtected, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isStatic, arginfo_class_ReflectionProperty_isStatic, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isAbstract, arginfo_class_ReflectionProperty_isAbstract, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isFinal, arginfo_class_ReflectionProperty_isFinal, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isDefault, arginfo_class_ReflectionProperty_isDefault, 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) diff --git a/ext/reflection/tests/accessors.phpt b/ext/reflection/tests/accessors.phpt index 55c9c258b46a6..0ba7cf0fb174b 100644 --- a/ext/reflection/tests/accessors.phpt +++ b/ext/reflection/tests/accessors.phpt @@ -6,23 +6,41 @@ Accessor reflection class Test { public $prop1; public $prop2 = 3.141 { get; private set; } - public $prop3 { + final public $prop3 { get { echo "get\n"; } set { echo "set($value)\n"; } } } +abstract class Test2 { + abstract public $prop4 { get; set; } +} + +function dumpFlags(ReflectionProperty $rp) { + $modifiers = $rp->getModifiers(); + echo "Final: "; + echo $rp->isFinal() ? "true" : "false"; + echo " "; + echo $modifiers & ReflectionProperty::IS_FINAL ? "true" : "false"; + echo "\n"; + + 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->getGet()); var_dump($rp1->getSet()); +dumpFlags($rp1); echo "\n"; $rp2 = new ReflectionProperty(Test::class, 'prop2'); var_dump($g = $rp2->getGet()); var_dump($s = $rp2->getSet()); -// TODO: This is broken. var_dump($g->invoke($test)); try { $s->invoke($test, 42); @@ -32,6 +50,7 @@ try { $s->setAccessible(true); $s->invoke($test, 42); var_dump($test->prop2); +dumpFlags($rp2); echo "\n"; $rp3 = new ReflectionProperty(Test::class, 'prop3'); @@ -39,11 +58,18 @@ var_dump($g = $rp3->getGet()); var_dump($s = $rp3->getSet()); $g->invoke($test); $s->invoke($test, 42); +dumpFlags($rp3); +echo "\n"; + +$rp4 = new ReflectionProperty(Test2::class, 'prop4'); +dumpFlags($rp4); ?> --EXPECT-- NULL NULL +Final: false false +Abstract: false false object(ReflectionMethod)#4 (2) { ["name"]=> @@ -60,6 +86,8 @@ object(ReflectionMethod)#5 (2) { float(3.141) Trying to invoke private method Test::$prop2::set() from scope ReflectionMethod int(42) +Final: false false +Abstract: false false object(ReflectionMethod)#8 (2) { ["name"]=> @@ -75,3 +103,8 @@ object(ReflectionMethod)#4 (2) { } get set(42) +Final: true true +Abstract: false false + +Final: false false +Abstract: true true From 1857bb98afaebc98526472ff5b795b2582b0d48a Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 3 May 2021 18:56:31 +0200 Subject: [PATCH 36/44] Use proper return types for newly added methods --- ext/reflection/php_reflection.stub.php | 6 ++---- ext/reflection/php_reflection_arginfo.h | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 832aa0039a866..fd013b750e29b 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -411,11 +411,9 @@ public function isProtected() {} /** @return bool */ public function isStatic() {} - /** @return bool */ - public function isAbstract() {} + public function isAbstract(): bool {} - /** @return bool */ - public function isFinal() {} + public function isFinal(): bool {} /** @return bool */ public function isDefault() {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 48b48c5764f0d..c1801de2d2cac 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: dc739b4c643af886db4eb97647f6d6174ee2980a */ + * Stub hash: f21ae2d5e8eb144d1ad66b3dec244f5736f24e09 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -309,9 +309,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionProperty_isAbstract arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionProperty_isAbstract arginfo_class_ReflectionClass_isEnum -#define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace From 5069793f08a34d11b6ef046e9092eb1d04b0b488 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 3 May 2021 19:09:10 +0200 Subject: [PATCH 37/44] Test one more private + final combination --- .../tests/accessors/private_prop_final_accessor.phpt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Zend/tests/accessors/private_prop_final_accessor.phpt diff --git a/Zend/tests/accessors/private_prop_final_accessor.phpt b/Zend/tests/accessors/private_prop_final_accessor.phpt new file mode 100644 index 0000000000000..9e058097587b2 --- /dev/null +++ b/Zend/tests/accessors/private_prop_final_accessor.phpt @@ -0,0 +1,12 @@ +--TEST-- +Private property with final accessor +--FILE-- + +--EXPECTF-- +Fatal error: Accessor cannot be both final and private in %s on line %d From 2a2880ba4a7c9db1611eb8f9884761a37031e2ac Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 4 May 2021 10:30:41 +0200 Subject: [PATCH 38/44] Add ref source for by-ref auto-prop --- .../accessors/by_ref_get_typed_prop.phpt | 37 +++++++++++++++++++ Zend/zend_object_handlers.c | 7 +++- 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/accessors/by_ref_get_typed_prop.phpt diff --git a/Zend/tests/accessors/by_ref_get_typed_prop.phpt b/Zend/tests/accessors/by_ref_get_typed_prop.phpt new file mode 100644 index 0000000000000..893ffae01d35f --- /dev/null +++ b/Zend/tests/accessors/by_ref_get_typed_prop.phpt @@ -0,0 +1,37 @@ +--TEST-- +By-ref getter with a typed property +--FILE-- +prop[] = 42; +var_dump($test->prop); +$ref =& $test->prop; +$ref = [1, 2, 3]; +var_dump($test->prop); + +try { + $ref = 42; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +array(1) { + [0]=> + int(42) +} +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} +Cannot assign int to reference held by property Test::$prop of type array diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 5cb8a980cd6dc..02de61663d6da 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -743,8 +743,11 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int if (UNEXPECTED(!(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) && Z_TYPE_P(retval) != IS_OBJECT)) { zend_error(E_NOTICE, "Indirect modification of accessor property %s::$%s has no effect (did you mean to use \"&get\"?)", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); - } else { - ZVAL_MAKE_REF(retval); + } else if (!Z_ISREF_P(retval)) { + ZVAL_NEW_REF(retval, retval); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(retval), prop_info); + } } ZVAL_COPY(rv, retval); retval = rv; From 00323aba5f9fd368cef3589207aa773006d6aca1 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 4 May 2021 10:54:40 +0200 Subject: [PATCH 39/44] Fix constant updating with virtual properties --- .../accessors/update_constants_virtual_prop.phpt | 16 ++++++++++++++++ Zend/zend_API.c | 3 +++ 2 files changed, 19 insertions(+) create mode 100644 Zend/tests/accessors/update_constants_virtual_prop.phpt diff --git a/Zend/tests/accessors/update_constants_virtual_prop.phpt b/Zend/tests/accessors/update_constants_virtual_prop.phpt new file mode 100644 index 0000000000000..fcead21f4fd27 --- /dev/null +++ b/Zend/tests/accessors/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/zend_API.c b/Zend/zend_API.c index 3ccda09fde3a3..deae3e5437881 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1411,6 +1411,9 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) / if (ce_flags & (ZEND_ACC_HAS_AST_PROPERTIES|ZEND_ACC_HAS_AST_STATICS)) { ZEND_HASH_FOREACH_PTR(&class_type->properties_info, prop_info) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + continue; + } if (prop_info->flags & ZEND_ACC_STATIC) { val = static_members_table + prop_info->offset; } else { From 91ca0742fe2d4b01ffe44b6bd4ca6500fa7d117d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 4 May 2021 14:57:31 +0200 Subject: [PATCH 40/44] Fix unset of uninit flag if initialization fails --- .../accessors/failed_prop_initialization.phpt | 22 +++++++++++++++++++ ..._properties_failed_assign_is_not_init.phpt | 4 ++-- Zend/zend_object_handlers.c | 1 - 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/accessors/failed_prop_initialization.phpt diff --git a/Zend/tests/accessors/failed_prop_initialization.phpt b/Zend/tests/accessors/failed_prop_initialization.phpt new file mode 100644 index 0000000000000..efffd67296d58 --- /dev/null +++ b/Zend/tests/accessors/failed_prop_initialization.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test the case where the initializing property assignment fails +--FILE-- +prop = "foo"; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $b->prop; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Cannot assign string to property Test::$prop of type int +Property Test::$prop must not be accessed before initialization diff --git a/Zend/tests/type_declarations/typed_properties_failed_assign_is_not_init.phpt b/Zend/tests/type_declarations/typed_properties_failed_assign_is_not_init.phpt index 5754591ce7d55..5cdef1451335f 100644 --- a/Zend/tests/type_declarations/typed_properties_failed_assign_is_not_init.phpt +++ b/Zend/tests/type_declarations/typed_properties_failed_assign_is_not_init.phpt @@ -31,6 +31,6 @@ try { ?> --EXPECT-- -Typed property Test::$prop must not be accessed before initialization +Property Test::$prop must not be accessed before initialization Cannot assign string to property Test::$prop of type int -Typed property Test::$prop must not be accessed before initialization +Property Test::$prop must not be accessed before initialization diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 02de61663d6da..29fac335007fa 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -917,7 +917,6 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva variable_ptr = OBJ_PROP(zobj, property_offset); if (Z_TYPE_P(variable_ptr) == IS_UNDEF) { ZEND_ASSERT(Z_PROP_FLAG_P(variable_ptr) == IS_PROP_UNINIT); - Z_PROP_FLAG_P(variable_ptr) = 0; if (!ZEND_TYPE_IS_SET(prop_info->type)) { prop_info = NULL; } From eb7052123d7b35a7f1c7d9cdc76fd02ecada3f8c Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 5 May 2021 11:01:24 +0200 Subject: [PATCH 41/44] Forbid final in interface The abstract + final combination is usually prohibited when the flags are parsed in the first place, but here the abstract is implicit, so we need to check it explicitly. --- .../accessors/interface_explicit_abstract.phpt | 12 ++++++++++++ .../accessors/interface_final_accessor.phpt | 10 ++++++++++ Zend/tests/accessors/interface_final_prop.phpt | 10 ++++++++++ Zend/tests/accessors/interface_not_public.phpt | 3 --- .../accessors/interface_not_public_2.phpt | 3 --- Zend/zend_compile.c | 18 ++++++++++++++++-- 6 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/accessors/interface_explicit_abstract.phpt create mode 100644 Zend/tests/accessors/interface_final_accessor.phpt create mode 100644 Zend/tests/accessors/interface_final_prop.phpt diff --git a/Zend/tests/accessors/interface_explicit_abstract.phpt b/Zend/tests/accessors/interface_explicit_abstract.phpt new file mode 100644 index 0000000000000..fa7a7337260a5 --- /dev/null +++ b/Zend/tests/accessors/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/accessors/interface_final_accessor.phpt b/Zend/tests/accessors/interface_final_accessor.phpt new file mode 100644 index 0000000000000..62705dc9966cb --- /dev/null +++ b/Zend/tests/accessors/interface_final_accessor.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cannot declare final accessor in interface +--FILE-- + +--EXPECTF-- +Fatal error: Accessor cannot be both abstract and final in %s on line %d diff --git a/Zend/tests/accessors/interface_final_prop.phpt b/Zend/tests/accessors/interface_final_prop.phpt new file mode 100644 index 0000000000000..ef664da7ab71a --- /dev/null +++ b/Zend/tests/accessors/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/accessors/interface_not_public.phpt b/Zend/tests/accessors/interface_not_public.phpt index aee807e3a4401..86c13311a537d 100644 --- a/Zend/tests/accessors/interface_not_public.phpt +++ b/Zend/tests/accessors/interface_not_public.phpt @@ -7,9 +7,6 @@ interface I { public $prop { get; private set; } } -class C implements I { -} - ?> --EXPECTF-- Fatal error: Accessor in interface cannot be protected or private in %s on line %d diff --git a/Zend/tests/accessors/interface_not_public_2.phpt b/Zend/tests/accessors/interface_not_public_2.phpt index 778004f8a60d7..f74c8bc1ab9d3 100644 --- a/Zend/tests/accessors/interface_not_public_2.phpt +++ b/Zend/tests/accessors/interface_not_public_2.phpt @@ -7,9 +7,6 @@ interface I { protected $prop { get; set; } } -class C implements I { -} - ?> --EXPECTF-- Fatal error: Accessor in interface cannot be protected or private in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ad311f3c5bd71..4d9e8d5f0e19a 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7260,8 +7260,7 @@ static void zend_compile_accessors( "All interface members are implicitly abstract"); } accessor->flags |= ZEND_ACC_ABSTRACT; - } - if (prop_info->flags & ZEND_ACC_ABSTRACT) { + } else if (prop_info->flags & ZEND_ACC_ABSTRACT) { if (accessor->flags & ZEND_ACC_ABSTRACT) { zend_error_noreturn(E_COMPILE_ERROR, "Accessor on abstract property cannot be explicitly abstract"); @@ -7292,6 +7291,9 @@ static void zend_compile_accessors( zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be both abstract and private"); } + if (accessor->flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, "Accessor cannot be both abstract and final"); + } ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; } @@ -7433,6 +7435,18 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z 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_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]; From e97adc6204eba8361e0aa0cd283e6eb773611289 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 5 May 2021 11:03:43 +0200 Subject: [PATCH 42/44] Fix class name in final property error --- Zend/tests/accessors/final_prop.phpt | 2 +- Zend/tests/accessors/final_prop_2.phpt | 2 +- Zend/tests/accessors/final_prop_private_set.phpt | 2 +- Zend/zend_inheritance.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/tests/accessors/final_prop.phpt b/Zend/tests/accessors/final_prop.phpt index 40ff9b39f52d6..bc54bfc1ce34e 100644 --- a/Zend/tests/accessors/final_prop.phpt +++ b/Zend/tests/accessors/final_prop.phpt @@ -13,4 +13,4 @@ class B extends A { ?> --EXPECTF-- -Fatal error: Cannot override final property B::$prop in %s on line %d +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/accessors/final_prop_2.phpt b/Zend/tests/accessors/final_prop_2.phpt index 3f4fd59e36150..9713532867cdd 100644 --- a/Zend/tests/accessors/final_prop_2.phpt +++ b/Zend/tests/accessors/final_prop_2.phpt @@ -13,4 +13,4 @@ class B extends A { ?> --EXPECTF-- -Fatal error: Cannot override final property B::$prop in %s on line %d +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/accessors/final_prop_private_set.phpt b/Zend/tests/accessors/final_prop_private_set.phpt index 5505259c3e592..9356fbb6cd159 100644 --- a/Zend/tests/accessors/final_prop_private_set.phpt +++ b/Zend/tests/accessors/final_prop_private_set.phpt @@ -13,4 +13,4 @@ class B extends A { ?> --EXPECTF-- -Fatal error: Cannot override final property B::$prop in %s on line %d +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 7247cec5edbc8..653f3efff6724 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1129,7 +1129,7 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } if (parent_info->flags & ZEND_ACC_FINAL) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot override final property %s::$%s", - ZSTR_VAL(ce->name), ZSTR_VAL(key)); + ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key)); } if (!(parent_info->flags & ZEND_ACC_PRIVATE)) { if (UNEXPECTED((parent_info->flags & ZEND_ACC_STATIC) != (child_info->flags & ZEND_ACC_STATIC))) { From a151ec5c4a83b33823a759a5fef340045341140e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 5 May 2021 11:08:16 +0200 Subject: [PATCH 43/44] List supported accessors in error message --- Zend/tests/accessors/unknown_accessor.phpt | 2 +- Zend/tests/accessors/unknown_accessor_private.phpt | 2 +- Zend/zend_compile.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/tests/accessors/unknown_accessor.phpt b/Zend/tests/accessors/unknown_accessor.phpt index 68dddc5732ef6..5bda1fc3800b6 100644 --- a/Zend/tests/accessors/unknown_accessor.phpt +++ b/Zend/tests/accessors/unknown_accessor.phpt @@ -11,4 +11,4 @@ class Test { ?> --EXPECTF-- -Fatal error: Unknown accessor "foobar" for property Test::$prop in %s on line 5 +Fatal error: Unknown accessor "foobar" for property Test::$prop, expected "get" or "set" in %s on line %d diff --git a/Zend/tests/accessors/unknown_accessor_private.phpt b/Zend/tests/accessors/unknown_accessor_private.phpt index f7468afc1ce2d..07c3254b4b5ca 100644 --- a/Zend/tests/accessors/unknown_accessor_private.phpt +++ b/Zend/tests/accessors/unknown_accessor_private.phpt @@ -11,4 +11,4 @@ class Test { ?> --EXPECTF-- -Fatal error: Unknown accessor "foobar" for property Test::$prop in %s on line %d +Fatal error: Unknown accessor "foobar" for property Test::$prop, expected "get" or "set" in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 4d9e8d5f0e19a..e4d54462dbfc7 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7367,7 +7367,7 @@ static void zend_compile_accessors( } } else { zend_error_noreturn(E_COMPILE_ERROR, - "Unknown accessor \"%s\" for property %s::$%s", + "Unknown accessor \"%s\" for property %s::$%s, expected \"get\" or \"set\"", ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); } From 43d2b5c3bc26c48573ae37a4410b27c8e2f2b5c9 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 5 May 2021 11:37:52 +0200 Subject: [PATCH 44/44] Don't allow unserializing to virtual property However, it is allowed if there is a parent for which the property was non-virtual. --- Zend/tests/accessors/unserialize.phpt | 50 ++++++++++++++++++++------- ext/standard/var_unserializer.re | 33 +++++++++--------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/Zend/tests/accessors/unserialize.phpt b/Zend/tests/accessors/unserialize.phpt index 65c41ca08ca7c..f258ec6347807 100644 --- a/Zend/tests/accessors/unserialize.phpt +++ b/Zend/tests/accessors/unserialize.phpt @@ -17,18 +17,32 @@ class Test { $test = new Test(1, 2); var_dump($s = serialize($test)); -var_dump($test2 = unserialize($s)); -$test2->prop3; -$test2->prop3 = 42; +var_dump($test_u = unserialize($s)); +$test_u->prop3; +$test_u->prop3 = 42; -// TODO: This should probably be forbidden. $s = 'O:4:"Test":1:{s:5:"prop3";i:42;}'; -var_dump($test3 = unserialize($s)); -$test3->prop3; -$test3->prop3 = 42; +var_dump(unserialize($s)); +echo "\n"; + +// Override implicit accessor 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)); ?> ---EXPECT-- +--EXPECTF-- string(47) "O:4:"Test":2:{s:5:"prop1";i:1;s:5:"prop2";i:2;}" object(Test)#2 (2) { ["prop1"]=> @@ -38,9 +52,21 @@ object(Test)#2 (2) { } Test::$prop3::get Test::$prop3::set -object(Test)#3 (1) { - ["prop3"]=> + +Warning: unserialize(): Cannot unserialize value for property Test::$prop3 with explicit accessors in %s on line %d + +Notice: unserialize(): Error at offset 26 of 32 bytes in %s on line %d +bool(false) + +string(48) "O:5:"Test2":2:{s:5:"prop1";i:1;s:5:"prop2";i:2;}" +object(Test2)#4 (2) { + ["prop1"]=> + int(1) + ["prop2"]=> + int(2) +} +Test2::$prop1::get +object(Test2)#5 (1) { + ["prop1"]=> int(42) } -Test::$prop3::get -Test::$prop3::set diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 0ac8c38c9dcab..3f69db9569852 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -538,7 +538,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; @@ -550,22 +550,23 @@ 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; - } + } 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) { + if (existing_propinfo->flags & ZEND_ACC_VIRTUAL) { + php_error_docref(NULL, E_WARNING, + "Cannot unserialize value for property %s::$%s with explicit accessors", + ZSTR_VAL(existing_propinfo->ce->name), Z_STRVAL_P(key)); + return -1; } + + zval_ptr_dtor_str(key); + ZVAL_STR_COPY(key, existing_propinfo->name); + return 1; } } return 0;