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/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/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/tests/accessors/cache.phpt b/Zend/tests/accessors/cache.phpt new file mode 100644 index 0000000000000..c658f9a6d05e0 --- /dev/null +++ b/Zend/tests/accessors/cache.phpt @@ -0,0 +1,55 @@ +--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/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/tests/accessors/dump.phpt b/Zend/tests/accessors/dump.phpt new file mode 100644 index 0000000000000..e5c33779ad41a --- /dev/null +++ b/Zend/tests/accessors/dump.phpt @@ -0,0 +1,46 @@ +--TEST-- +Dumping object with accessors +--FILE-- + $value) { + echo "$prop => $value\n"; +} + +?> +--EXPECT-- +object(Test)#1 (2) { + ["prop"]=> + int(1) + ["prop2"]=> + int(2) +} +array(2) { + ["prop"]=> + int(1) + ["prop2"]=> + int(2) +} +array(2) { + ["prop"]=> + int(1) + ["prop2"]=> + int(2) +} +prop => 1 +prop2 => 2 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/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/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_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/tests/accessors/final_prop.phpt b/Zend/tests/accessors/final_prop.phpt new file mode 100644 index 0000000000000..bc54bfc1ce34e --- /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 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 new file mode 100644 index 0000000000000..9713532867cdd --- /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 A::$prop in %s on line %d 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/tests/accessors/final_prop_private_set.phpt b/Zend/tests/accessors/final_prop_private_set.phpt new file mode 100644 index 0000000000000..9356fbb6cd159 --- /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 A::$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..e25981f77b67f --- /dev/null +++ b/Zend/tests/accessors/get_by_ref.phpt @@ -0,0 +1,51 @@ +--TEST-- +Get accessor by ref and indirect modification +--FILE-- +_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 { } + } +} + +$test = new Test; +$test->byRef[] = 42; +var_dump($test->byRef); + +$test->byVal[] = 42; +var_dump($test->byVal); + +var_dump($test->byRefType); + +try { + $test->byRef =& $ref; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--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) +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 new file mode 100644 index 0000000000000..70257c83be5e0 --- /dev/null +++ b/Zend/tests/accessors/get_by_ref_auto.phpt @@ -0,0 +1,35 @@ +--TEST-- +Get by reference with generated accessors +--FILE-- +byRef[] = 42; +var_dump($test->byRef); + +$test->byVal[] = 42; +var_dump($test->byVal); + +try { + $test->byRef =& $ref; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--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) { +} +Cannot assign by reference to overloaded object 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/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/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) 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..a2d5e8565995f --- /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/init_only.phpt b/Zend/tests/accessors/init_only.phpt new file mode 100644 index 0000000000000..b9d56366c5dfd --- /dev/null +++ b/Zend/tests/accessors/init_only.phpt @@ -0,0 +1,63 @@ +--TEST-- +Accessor with only implicit get (init-only) +--FILE-- +prop = $prop; + $this->prop2 = $prop2; + } +} + +$test = new Test(1, 2); +var_dump($test->prop); +try { + $test->prop = 3; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $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(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/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_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_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..86c13311a537d --- /dev/null +++ b/Zend/tests/accessors/interface_not_public.phpt @@ -0,0 +1,12 @@ +--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..f74c8bc1ab9d3 --- /dev/null +++ b/Zend/tests/accessors/interface_not_public_2.phpt @@ -0,0 +1,12 @@ +--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_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_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/invalid_implicit_get_by_ref.phpt b/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt new file mode 100644 index 0000000000000..cefe4bc443e51 --- /dev/null +++ b/Zend/tests/accessors/invalid_implicit_get_by_ref.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot have only &get (implicit) +--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_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/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/tests/accessors/isset.phpt b/Zend/tests/accessors/isset.phpt new file mode 100644 index 0000000000000..8d98aef1a9182 --- /dev/null +++ b/Zend/tests/accessors/isset.phpt @@ -0,0 +1,51 @@ +--TEST-- +isset() and empty() call get accessor +--FILE-- +_prop1; } + set { $this->_prop1 = $value; } + } + + public array $prop2 { 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"; + +// Private accessor should simply report as false, not throw +var_dump(isset($test->prop2)); +var_dump(!empty($test->prop2)); +var_dump(isset($test->prop2[0])); +var_dump(!empty($test->prop2[0])); + +?> +--EXPECT-- +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/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 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..3acafcfaa7b1b --- /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: 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 new file mode 100644 index 0000000000000..d78e44658e294 --- /dev/null +++ b/Zend/tests/accessors/recursion.phpt @@ -0,0 +1,49 @@ +--TEST-- +Recursion behavior of accessors +--FILE-- +prop * 2; } + set { $this->prop = $value * 2; } + } + + // Edge-case where recursion happens via isset(). + public int $prop2 { + get { return isset($this->prop2); } + set { } + } +} + +$test = new Test; +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"; +} +var_dump($test); + +?> +--EXPECT-- +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 +object(Test)#1 (0) { +} 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..903dd30cd9e10 --- /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..5bda1fc3800b6 --- /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, 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 new file mode 100644 index 0000000000000..07c3254b4b5ca --- /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, expected "get" or "set" in %s on line %d diff --git a/Zend/tests/accessors/unserialize.phpt b/Zend/tests/accessors/unserialize.phpt new file mode 100644 index 0000000000000..f258ec6347807 --- /dev/null +++ b/Zend/tests/accessors/unserialize.phpt @@ -0,0 +1,72 @@ +--TEST-- +unserialize() with accessors +--FILE-- +prop3; +$test_u->prop3 = 42; + +$s = 'O:4:"Test":1:{s:5:"prop3";i: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)); + +?> +--EXPECTF-- +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 + +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) +} diff --git a/Zend/tests/accessors/unset.phpt b/Zend/tests/accessors/unset.phpt new file mode 100644 index 0000000000000..66e1ce3c47696 --- /dev/null +++ b/Zend/tests/accessors/unset.phpt @@ -0,0 +1,26 @@ +--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/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/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/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_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/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_API.c b/Zend/zend_API.c index 7b797e5a939c8..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 { @@ -4020,78 +4023,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) { @@ -4107,6 +4117,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.c b/Zend/zend_ast.c index a09c13fd89ece..0d664b6959825 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(unsigned kind, ...) { + va_list va; + va_start(va, kind); + zend_ast *ast = zend_ast_create_va(kind, 0, &va); + va_end(va); + return ast; +} + +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_ex_n( + zend_ast_kind kind, unsigned attr, ...) { + va_list va; + va_start(va, attr); + zend_ast *ast = zend_ast_create_va(kind, attr, &va); + va_end(va); return ast; } diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index fb6587b48cd31..d98c8eca1adc6 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,9 +167,12 @@ 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, + + /* 6 child nodes */ + ZEND_AST_PARAM = 6 << ZEND_AST_NUM_CHILDREN_SHIFT, }; typedef uint16_t zend_ast_kind; @@ -225,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); @@ -238,7 +241,11 @@ 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); +/* 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); @@ -265,11 +272,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 884f653ed7245..e4d54462dbfc7 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6460,6 +6460,29 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3 } /* }}} */ +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); @@ -6496,6 +6519,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; @@ -6652,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); @@ -6661,7 +6685,20 @@ 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++) { + zend_ast_decl *accessor = (zend_ast_decl *) accessors->child[i]; + if (accessor->child[2]) { + zend_error_noreturn(E_COMPILE_ERROR, + "Can only use implicit accessors for a promoted property"); + } + } + zend_compile_accessors(prop, name, type_ast, accessors); + } if (attributes_ast) { zend_compile_attributes( &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY); @@ -7026,7 +7063,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,7 +7071,8 @@ 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; - zend_string *method_lcname; + bool is_accessor = decl->kind == ZEND_AST_ACCESSOR; + 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); @@ -7064,7 +7102,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,25 +7188,263 @@ 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_string *prop_name, + zend_ast *prop_type_ast, zend_ast_list *accessors) +{ + zend_class_entry *ce = CG(active_class_entry); + + if (accessors->children == 0) { + zend_error_noreturn(E_COMPILE_ERROR, "Accessor list cannot be empty"); + } + + 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]; + 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_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; + + 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; + } 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"); + } + accessor->flags |= ZEND_ACC_ABSTRACT; + } + + 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"); + } + 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"); + } + if (accessor->flags & ZEND_ACC_PRIVATE) { + // TODO Trait exception? + 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; + } + + 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)); + } + + /* 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) { + 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 (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))), + 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); + } + } 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 (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))), + 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)))); + *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, expected \"get\" or \"set\"", + ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_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 (implicit_prop) { + func->common.fn_flags |= ZEND_ACC_AUTO_PROP; + } + if (explicit_prop) { + has_any_explicit_accessor = true; + ce->ce_flags |= ZEND_ACC_USE_GUARDS; + } + + if (!prop_info->accessors) { + 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]) { + 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. */ + 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; + } + } + + 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"); + } + + 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 (get && (get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) + && !prop_info->accessors[ZEND_ACCESSOR_SET]) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot have &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) /* {{{ */ { 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"); + if ((flags & ZEND_ACC_FINAL) && (flags & ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, "Property cannot be both final and private"); + } + + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + if (flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, "Property in interface cannot be final"); + } + if (flags & ZEND_ACC_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) { @@ -7173,10 +7453,23 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z 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); + flags |= get_virtual_flag(accessors_ast); + + 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 +7487,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)); @@ -7226,7 +7513,12 @@ 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)); } } - } else if (!ZEND_TYPE_IS_SET(type)) { + + 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) && !accessors_ast) { ZVAL_NULL(&value_zv); } else { ZVAL_UNDEF(&value_zv); @@ -7234,6 +7526,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, name, 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..e2ab1c7c5a47e 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,15 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ +/* 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...) | | | */ /* =========== | | | */ /* | | | */ @@ -301,7 +307,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 +363,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 +381,11 @@ typedef struct _zend_oparray_context { char *zend_visibility_string(uint32_t fn_flags); +#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 property index for static properties */ @@ -381,6 +395,7 @@ typedef struct _zend_property_info { HashTable *attributes; zend_class_entry *ce; zend_type type; + zend_function **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..653f3efff6724 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); + 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[ZEND_ACCESSOR_SET]) { + return PROP_COVARIANT; + } + if (!prop_info->accessors[ZEND_ACCESSOR_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(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))) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s%s::$%s as %s%s::$%s", @@ -1061,24 +1140,45 @@ 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; + zend_function **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)); + } + + 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); 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 +1189,15 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } } } else { + zend_function **accessors = parent_info->accessors; + if (accessors) { + 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; + } + } + } + _zend_hash_append_ptr(&ce->properties_info, key, parent_info); } } @@ -1203,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(); @@ -1479,6 +1589,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 +1599,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); @@ -2010,9 +2129,18 @@ 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) == 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,19 +2186,23 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent 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; @@ -2084,6 +2216,20 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent GC_ADDREF(new_prop->attributes); } } + if (property_info->accessors) { + zend_function **accessors = new_prop->accessors = + 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]) { + 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; + } + } + } + ce->ce_flags |= traits[i]->ce_flags & ZEND_ACC_USE_GUARDS; + } } ZEND_HASH_FOREACH_END(); } } @@ -2164,6 +2310,20 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ } } ZEND_HASH_FOREACH_END(); + if (!is_explicit_abstract) { + zend_property_info *prop_info; + ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) { + if (prop_info->accessors) { + 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(); + } + 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 +2360,7 @@ typedef struct { struct { const zend_property_info *parent_prop; const zend_property_info *child_prop; + prop_variance variance; }; }; } variance_obligation; @@ -2267,12 +2428,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 +2471,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 +2541,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"); } @@ -2417,6 +2581,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; @@ -2450,31 +2642,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); @@ -2530,6 +2699,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); + } + } + } } } @@ -2831,7 +3010,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..e47e26d76c854 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 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 -%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 @@ -764,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); } ; @@ -876,6 +882,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 +992,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 +1377,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 +1413,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..29fac335007fa 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,72 @@ 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: Pass accessor kind? + zend_function *scope_accessor; + if ((*prop)->accessors[ZEND_ACCESSOR_GET] == accessor) { + scope_accessor = scope_prop->accessors[ZEND_ACCESSOR_GET]; + } else { + 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) { + *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 = 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[ZEND_ACCESSOR_GET] : prop_info->accessors[ZEND_ACCESSOR_SET]; + check_accessor_visibility(&prop_info, zobj->ce, member, accessor, /* silent */ false); + } } /* }}} */ @@ -612,6 +682,83 @@ 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[ZEND_ACCESSOR_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; + 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)) { + guard = zend_get_property_guard(zobj, name); + 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); + } + + 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) { + /* 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*)((uintptr_t)CACHED_PTR_EX(cache_slot + 1) | ZEND_ACCESSOR_SIMPLE_READ_BIT)); + } + + 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 (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 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; + } + goto exit; + } 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 +825,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; @@ -690,7 +838,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 { @@ -722,6 +870,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 +907,62 @@ 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[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); + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + 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); + } + + 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->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", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(error_zval); + } + + 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)); + } + + 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 +979,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 +1194,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 +1242,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 +1262,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 +1863,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 +1913,49 @@ 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[ZEND_ACCESSOR_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 (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; + } + + 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; + 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..8dc651f4ccfbc 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -27,10 +27,19 @@ 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) >= 8) #define IS_WRONG_PROPERTY_OFFSET(offset) ((intptr_t)(offset) == 0) +#define IS_ACCESSOR_PROPERTY_OFFSET(offset) \ + ((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)) #define ZEND_ENCODE_DYN_PROP_OFFSET(offset) ((uintptr_t)(-((intptr_t)(offset) + 2))) diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0386a43e09cac..6e68c2a3bbbd5 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -414,6 +414,13 @@ ZEND_API void destroy_zend_class(zval *zv) zend_hash_release(prop_info->attributes); } zend_type_release(prop_info->type, /* persistent */ 0); + if (prop_info->accessors) { + for (uint32_t i = 0; i < ZEND_ACCESSOR_COUNT; i++) { + if (prop_info->accessors[i]) { + destroy_op_array(&prop_info->accessors[i]->op_array); + } + } + } } } 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..782396f3bdeef 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) { @@ -2054,20 +2055,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,17 +2089,14 @@ 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)); + 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); } } } @@ -2210,20 +2221,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 +2255,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 +2417,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)) { @@ -2443,13 +2459,26 @@ 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); } ZEND_VM_C_GOTO(exit_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 209c77650ca36..f8dd97110bc1c 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) { @@ -6235,20 +6236,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,17 +6270,14 @@ 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)); + 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; } } } @@ -6353,20 +6364,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 +6398,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)); @@ -8552,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) { @@ -8562,20 +8579,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,17 +8613,14 @@ 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)); + 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; } } } @@ -8680,20 +8707,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 +8741,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)); @@ -10902,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) { @@ -10912,20 +10945,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,17 +10979,14 @@ 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)); + 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; } } } @@ -11030,20 +11073,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 +11107,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)); @@ -15321,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) { @@ -15331,20 +15380,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,17 +15414,14 @@ 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)); + 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; } } } @@ -15449,20 +15508,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 +15542,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)); @@ -16741,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) { @@ -16751,20 +16816,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,17 +16850,14 @@ 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)); + 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; } } } @@ -16869,20 +16944,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 +16978,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)); @@ -18053,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) { @@ -18063,20 +18144,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,17 +18178,14 @@ 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)); + 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; } } } @@ -18181,20 +18272,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 +18306,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 +22885,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)) { @@ -22831,13 +22927,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -22931,7 +23040,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)) { @@ -22973,13 +23082,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23073,7 +23195,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)) { @@ -23115,13 +23237,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23215,7 +23350,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)) { @@ -23257,13 +23392,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -25367,7 +25515,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)) { @@ -25409,13 +25557,26 @@ 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); } goto exit_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); + 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)); @@ -25509,7 +25670,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)) { @@ -25551,13 +25712,26 @@ 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); } goto exit_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); + 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)); @@ -25651,7 +25825,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)) { @@ -25693,13 +25867,26 @@ 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); } goto exit_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); + 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)); @@ -25793,7 +25980,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)) { @@ -25835,13 +26022,26 @@ 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); } goto exit_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); + 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)); @@ -29293,7 +29493,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)) { @@ -29335,13 +29535,26 @@ 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); } goto exit_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); + 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)); @@ -29435,7 +29648,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)) { @@ -29477,13 +29690,26 @@ 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); } goto exit_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); + 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)); @@ -29577,7 +29803,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)) { @@ -29619,13 +29845,26 @@ 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); } goto exit_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); + 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)); @@ -29719,7 +29958,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)) { @@ -29761,13 +30000,26 @@ 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); } goto exit_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); + 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)); @@ -31390,6 +31642,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) { @@ -31400,20 +31653,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,17 +31687,14 @@ 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)); + 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; } } } @@ -31561,20 +31824,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 +31858,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 +31985,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)) { @@ -31759,13 +32027,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -31859,7 +32140,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)) { @@ -31901,13 +32182,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -32001,7 +32295,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)) { @@ -32043,13 +32337,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -32143,7 +32450,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)) { @@ -32185,13 +32492,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33291,6 +33611,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) { @@ -33301,20 +33622,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,17 +33656,14 @@ 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)); + 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; } } } @@ -33457,20 +33788,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 +33822,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 +33949,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)) { @@ -33655,13 +33991,26 @@ 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); } goto exit_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); + 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)); @@ -33755,7 +34104,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)) { @@ -33797,13 +34146,26 @@ 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); } goto exit_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); + 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)); @@ -33897,7 +34259,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)) { @@ -33939,13 +34301,26 @@ 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); } goto exit_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); + 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)); @@ -34039,7 +34414,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)) { @@ -34081,13 +34456,26 @@ 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); } goto exit_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); + 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)); @@ -35785,6 +36173,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) { @@ -35795,20 +36184,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,17 +36218,14 @@ 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)); + 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; } } } @@ -35951,20 +36350,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 +36384,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 +36511,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)) { @@ -36149,13 +36553,26 @@ 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); } goto exit_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); + 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)); @@ -36249,7 +36666,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)) { @@ -36291,13 +36708,26 @@ 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); } goto exit_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); + 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)); @@ -36391,7 +36821,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)) { @@ -36433,13 +36863,26 @@ 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); } goto exit_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); + 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)); @@ -36533,7 +36976,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)) { @@ -36575,13 +37018,26 @@ 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); } goto exit_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); + 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)); @@ -39961,6 +40417,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) { @@ -39971,20 +40428,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,17 +40462,14 @@ 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)); + 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; } } } @@ -40132,20 +40599,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 +40633,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 +40760,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)) { @@ -40330,13 +40802,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40430,7 +40915,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)) { @@ -40472,13 +40957,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40572,7 +41070,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)) { @@ -40614,13 +41112,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -40714,7 +41225,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)) { @@ -40756,13 +41267,26 @@ 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); } goto exit_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); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -43615,6 +44139,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) { @@ -43625,20 +44150,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,17 +44184,14 @@ 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)); + 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; } } } @@ -43781,20 +44316,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 +44350,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 +44477,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)) { @@ -43979,13 +44519,26 @@ 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); } goto exit_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); + 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)); @@ -44079,7 +44632,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)) { @@ -44121,13 +44674,26 @@ 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); } goto exit_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); + 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)); @@ -44221,7 +44787,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)) { @@ -44263,13 +44829,26 @@ 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); } goto exit_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); + 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)); @@ -44363,7 +44942,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)) { @@ -44405,13 +44984,26 @@ 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); } goto exit_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); + 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)); @@ -48646,6 +49238,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) { @@ -48656,20 +49249,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,17 +49283,14 @@ 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)); + 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; } } } @@ -48812,20 +49415,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 +49449,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 +49576,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)) { @@ -49010,13 +49618,26 @@ 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); } goto exit_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); + 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)); @@ -49110,7 +49731,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)) { @@ -49152,13 +49773,26 @@ 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); } goto exit_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); + 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)); @@ -49252,7 +49886,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)) { @@ -49294,13 +49928,26 @@ 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); } goto exit_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); + 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)); @@ -49394,7 +50041,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)) { @@ -49436,13 +50083,26 @@ 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); } goto exit_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); + 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)); 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; } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 13fd3dfebf9f8..4ac8f15933fdb 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,15 @@ 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(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); return prop; } @@ -905,7 +914,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..7e4581ae185d2 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,14 @@ 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(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]); + } + } + } } static void zend_persist_class_constant_calc(zval *zv) @@ -412,7 +419,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/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 757627de47807..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(); @@ -5727,6 +5737,33 @@ ZEND_METHOD(ReflectionProperty, getDefaultValue) } /* }}} */ +static void get_accessor(INTERNAL_FUNCTION_PARAMETERS, uint32_t accessor) +{ + reflection_object *intern; + property_reference *ref; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(ref); + + if (!ref->prop->accessors || !ref->prop->accessors[accessor]) { + RETURN_NULL(); + } + + zend_function *fn = ref->prop->accessors[accessor]; + reflection_method_factory(fn->common.scope, fn, NULL, return_value); +} + +ZEND_METHOD(ReflectionProperty, getGet) +{ + get_accessor(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACCESSOR_GET); +} + +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 */ ZEND_METHOD(ReflectionExtension, __construct) { @@ -6952,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 fe8def14b72b2..fd013b750e29b 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -411,6 +411,10 @@ public function isProtected() {} /** @return bool */ public function isStatic() {} + public function isAbstract(): bool {} + + public function isFinal(): bool {} + /** @return bool */ public function isDefault() {} @@ -441,6 +445,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..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: 388312e928b54992da6b7e0e0f15dec72d9290f1 */ + * 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,6 +309,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionProperty_isAbstract arginfo_class_ReflectionClass_isEnum + +#define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionClass_isEnum + #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionClass_isEnum @@ -331,6 +335,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) @@ -664,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); @@ -675,6 +686,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); @@ -933,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) @@ -944,6 +959,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..0ba7cf0fb174b --- /dev/null +++ b/ext/reflection/tests/accessors.phpt @@ -0,0 +1,110 @@ +--TEST-- +Accessor reflection +--FILE-- +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()); +var_dump($g->invoke($test)); +try { + $s->invoke($test, 42); +} catch (ReflectionException $e) { + echo $e->getMessage(), "\n"; +} +$s->setAccessible(true); +$s->invoke($test, 42); +var_dump($test->prop2); +dumpFlags($rp2); +echo "\n"; + +$rp3 = new ReflectionProperty(Test::class, 'prop3'); +var_dump($g = $rp3->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"]=> + string(11) "$prop2::get" + ["class"]=> + string(4) "Test" +} +object(ReflectionMethod)#5 (2) { + ["name"]=> + string(11) "$prop2::set" + ["class"]=> + string(4) "Test" +} +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"]=> + string(11) "$prop3::get" + ["class"]=> + string(4) "Test" +} +object(ReflectionMethod)#4 (2) { + ["name"]=> + string(11) "$prop3::set" + ["class"]=> + string(4) "Test" +} +get +set(42) +Final: true true +Abstract: false false + +Final: false false +Abstract: true true 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; 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