diff --git a/.gitattributes b/.gitattributes index f8b91505bc983..49d5471ff6646 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,7 +22,7 @@ # Collapse generated files within a pull request. **/*_arginfo.h linguist-generated /Zend/zend_vm_execute.h linguist-generated -/Zend/zend_vm_opcodes.{h,c} linguist-generated +/Zend/zend_vm_opcodes.[ch] linguist-generated # The OSS fuzz files are bunary /ext/date/tests/ossfuzz*.txt binary diff --git a/NEWS b/NEWS index 53e4fe73519b2..808f3ed806101 100644 --- a/NEWS +++ b/NEWS @@ -44,6 +44,9 @@ PHP NEWS - Date: . Implement More Appropriate Date/Time Exceptions RFC. (Derick) +- DOM: + . Fix bug GH-11308 (getElementsByTagName() is O(N^2)). (nielsdos) + - Exif: . Removed unneeded codepaths in exif_process_TIFF_in_JPEG(). (nielsdos) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index b4675e22215e9..6db2d99ec59b9 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -116,6 +116,29 @@ PHP 8.3 INTERNALS UPGRADE NOTES - The PHPAPI spl_iterator_apply() function now returns zend_result instead of int. There are no functional changes. + f. ext/dom + - A new function dom_get_doc_props_read_only() is added to gather the document + properties in a read-only way. This function avoids allocation when there are + no document properties changed yet. + - The node list returned by DOMNode::getElementsByTagName() and + DOMNode::getElementsByTagNameNS() now caches the length and the last requested item. + This means that the length and the last requested item are not recalculated + when the node list is iterated over multiple times. + If you do not use the internal PHP dom APIs to modify the document, you need to + manually invalidate the cache using php_libxml_invalidate_node_list_cache_from_doc(). + Furthermore, the following internal APIs were added to handle the cache: + . php_dom_is_cache_tag_stale_from_doc_ptr() + . php_dom_is_cache_tag_stale_from_node() + . php_dom_mark_cache_tag_up_to_date_from_node() + - The function dom_get_elements_by_tag_name_ns_raw() has an additional parameter to indicate + the base node of the node list. This function also no longer accepts -1 as the index argument. + - The function dom_namednode_iter() has additional arguments to avoid recomputing the length of + the strings. + + g. ext/libxml + - Two new functions: php_libxml_invalidate_node_list_cache_from_doc() and + php_libxml_invalidate_node_list_cache() were added to invalidate the cache of a node list. + ======================== 4. OpCode changes ======================== diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index 9248f0b822441..068deb7c9cdeb 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -197,6 +197,9 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx LITERAL_INFO(opline->op2.constant, 2); } break; + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: + LITERAL_INFO(opline->op1.constant, 1); + break; case ZEND_CATCH: LITERAL_INFO(opline->op1.constant, 2); break; diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c index 200b5a6ff83f4..1b37c3cbea778 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -47,6 +47,7 @@ static void zend_delete_call_instructions(zend_op_array *op_array, zend_op *opli case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_FCALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: if (call == 0) { MAKE_NOP(opline); return; @@ -95,6 +96,11 @@ static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_o return; } + if (fcall->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL) { + /* Don't inline property hook method */ + return; + } + for (i = 0; i < num_args; i++) { /* Don't inline functions with by-reference arguments. This would require * correct handling of INDIRECT arguments. */ @@ -168,6 +174,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) case ZEND_INIT_METHOD_CALL: case ZEND_INIT_FCALL: case ZEND_NEW: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: /* The argument passing optimizations are valid for prototypes as well, * as inheritance cannot change between ref <-> non-ref arguments. */ call_stack[call].func = zend_optimizer_get_called_func( @@ -211,6 +218,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) } } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL || fcall->opcode == ZEND_INIT_METHOD_CALL + || fcall->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL || fcall->opcode == ZEND_NEW) { /* We don't have specialized opcodes for this, do nothing */ } else { diff --git a/Zend/Optimizer/zend_call_graph.c b/Zend/Optimizer/zend_call_graph.c index c2b7b00cbee13..3fad0e5d9c13b 100644 --- a/Zend/Optimizer/zend_call_graph.c +++ b/Zend/Optimizer/zend_call_graph.c @@ -61,6 +61,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32 case ZEND_INIT_FCALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: call_stack[call] = call_info; func = zend_optimizer_get_called_func( script, op_array, opline, &is_prototype); diff --git a/Zend/Optimizer/zend_dump.c b/Zend/Optimizer/zend_dump.c index bc697ba8ba9e5..9eaca19f18f57 100644 --- a/Zend/Optimizer/zend_dump.c +++ b/Zend/Optimizer/zend_dump.c @@ -23,6 +23,7 @@ #include "zend_func_info.h" #include "zend_call_graph.h" #include "zend_dump.h" +#include "ext/standard/php_string.h" void zend_dump_ht(HashTable *ht) { @@ -65,8 +66,12 @@ void zend_dump_const(const zval *zv) case IS_DOUBLE: fprintf(stderr, " float(%g)", Z_DVAL_P(zv)); break; - case IS_STRING: - fprintf(stderr, " string(\"%s\")", Z_STRVAL_P(zv)); + case IS_STRING:; + zend_string *escaped_string = php_addcslashes(Z_STR_P(zv), "\"\\", 2); + + fprintf(stderr, " string(\"%s\")", ZSTR_VAL(escaped_string)); + + zend_string_release(escaped_string); break; case IS_ARRAY: fprintf(stderr, " array(...)"); diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 463bbbfa84b45..bd4f0790341e0 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -910,6 +910,27 @@ zend_function *zend_optimizer_get_called_func( } } break; + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:; + zend_class_entry *scope = op_array->scope; + ZEND_ASSERT(scope != NULL); + if (scope && (scope->ce_flags & ZEND_ACC_LINKED) && scope->parent) { + zend_class_entry *parent_scope = scope->parent; + zend_string *prop_name = Z_STR_P(CRT_CONSTANT(opline->op1)); + zend_property_hook_kind hook_kind = opline->op2.num; + zend_property_info *prop_info = zend_get_property_info(parent_scope, prop_name, /* silent */ true); + + if (prop_info + && prop_info != ZEND_WRONG_PROPERTY_INFO + && !(prop_info->flags & ZEND_ACC_PRIVATE) + && prop_info->hooks) { + zend_function *fbc = prop_info->hooks[hook_kind]; + if (fbc) { + *is_prototype = true; + return fbc; + } + } + } + break; case ZEND_NEW: { zend_class_entry *ce = zend_optimizer_get_class_entry_from_op1( @@ -1425,6 +1446,7 @@ void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void { zval *zv; zend_op_array *op_array; + zend_property_info *property; zend_foreach_op_array_helper(&script->main_op_array, func, context); @@ -1445,6 +1467,17 @@ void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void zend_foreach_op_array_helper(op_array, func, context); } } ZEND_HASH_FOREACH_END(); + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property) { + zend_function **hooks = property->hooks; + if (hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + zend_function *hook = hooks[i]; + if (hook && hook->common.scope == ce) { + zend_foreach_op_array_helper(&hooks[i]->op_array, func, context); + } + } + } + } ZEND_HASH_FOREACH_END(); } ZEND_HASH_FOREACH_END(); } diff --git a/Zend/tests/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/alternative_offset_syntax_compile_error_in_const_expr.phpt b/Zend/tests/alternative_offset_syntax_compile_error_in_const_expr.phpt index 7001d924e8bd7..db7c152b86d2b 100644 --- a/Zend/tests/alternative_offset_syntax_compile_error_in_const_expr.phpt +++ b/Zend/tests/alternative_offset_syntax_compile_error_in_const_expr.phpt @@ -6,4 +6,4 @@ const FOO_COMPILE_ERROR = "BAR"{0}; var_dump(FOO_COMPILE_ERROR); ?> --EXPECTF-- -Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 2 +Parse error: syntax error, unexpected token "{", expecting "," or ";" in %s on line %d diff --git a/Zend/tests/alternative_offset_syntax_compile_error_outside_const_expr.phpt b/Zend/tests/alternative_offset_syntax_compile_error_outside_const_expr.phpt index c5e5848b6c400..df0dc78cb0638 100644 --- a/Zend/tests/alternative_offset_syntax_compile_error_outside_const_expr.phpt +++ b/Zend/tests/alternative_offset_syntax_compile_error_outside_const_expr.phpt @@ -6,4 +6,4 @@ $foo = 'BAR'; var_dump($foo{0}); ?> --EXPECTF-- -Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 3 +Parse error: syntax error, unexpected token "{", expecting ")" in %s on line %d diff --git a/Zend/tests/alternative_offset_syntax_in_encaps_string.phpt b/Zend/tests/alternative_offset_syntax_in_encaps_string.phpt index 2771b33afa89c..83163487082cf 100644 --- a/Zend/tests/alternative_offset_syntax_in_encaps_string.phpt +++ b/Zend/tests/alternative_offset_syntax_in_encaps_string.phpt @@ -3,4 +3,4 @@ Alternative offset syntax should emit only E_COMPILE_ERROR in string interpolati --FILE-- --EXPECTF-- -Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 1 +Parse error: syntax error, unexpected token "{", expecting "->" or "?->" or "[" in %s on line %d diff --git a/Zend/tests/errmsg_037.phpt b/Zend/tests/errmsg_037.phpt index 259eaa45106c3..00df06b13ddea 100644 --- a/Zend/tests/errmsg_037.phpt +++ b/Zend/tests/errmsg_037.phpt @@ -10,4 +10,4 @@ class test { echo "Done\n"; ?> --EXPECTF-- -Fatal error: Cannot use the abstract modifier on a property in %s on line %d +Fatal error: Only hooked properties may be declared abstract in %s on line %d diff --git a/Zend/tests/errmsg_038.phpt b/Zend/tests/errmsg_038.phpt deleted file mode 100644 index 060dc984dab03..0000000000000 --- a/Zend/tests/errmsg_038.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -errmsg: properties cannot be final ---FILE-- - ---EXPECTF-- -Fatal error: Cannot use the final modifier on a property in %s on line %d diff --git a/Zend/tests/gh11320_1.phpt b/Zend/tests/gh11320_1.phpt new file mode 100644 index 0000000000000..f9beef76ccf6d --- /dev/null +++ b/Zend/tests/gh11320_1.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-11320: Array literals can contain list() assignments +--FILE-- + list($x, $y) = getList()]); +var_dump([$index => [$x, $y] = getList()]); +?> +--EXPECT-- +array(1) { + [1]=> + array(2) { + [0]=> + int(2) + [1]=> + int(3) + } +} +array(1) { + [1]=> + array(2) { + [0]=> + int(2) + [1]=> + int(3) + } +} diff --git a/Zend/tests/gh11320_2.phpt b/Zend/tests/gh11320_2.phpt new file mode 100644 index 0000000000000..5173c518f387f --- /dev/null +++ b/Zend/tests/gh11320_2.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-11320: list() expressions can contain magic constants +--FILE-- + $foo) = [__FILE__ => 'foo']]; +var_dump($foo); +[[__FILE__ => $foo] = [__FILE__ => 'foo']]; +var_dump($foo); +?> +--EXPECT-- +string(3) "foo" +string(3) "foo" diff --git a/Zend/tests/gh11320_3.phpt b/Zend/tests/gh11320_3.phpt new file mode 100644 index 0000000000000..3c3ed336d0b72 --- /dev/null +++ b/Zend/tests/gh11320_3.phpt @@ -0,0 +1,8 @@ +--TEST-- +GH-11320: list() must not appear as a standalone array element +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use list() as standalone expression in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_hook.phpt b/Zend/tests/property_hooks/abstract_hook.phpt new file mode 100644 index 0000000000000..f79d229177e02 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_hook.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract hooks compile successfully +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/abstract_hook_in_non_abstract_class.phpt b/Zend/tests/property_hooks/abstract_hook_in_non_abstract_class.phpt new file mode 100644 index 0000000000000..4a7f137845464 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_hook_in_non_abstract_class.phpt @@ -0,0 +1,15 @@ +--TEST-- +Abstract hooks in non-abstract class gives an error +--FILE-- + +--EXPECTF-- +Fatal error: Class Test contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Test::$prop::get) in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_hook_not_implemented.phpt b/Zend/tests/property_hooks/abstract_hook_not_implemented.phpt new file mode 100644 index 0000000000000..010a8b79ea471 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_hook_not_implemented.phpt @@ -0,0 +1,17 @@ +--TEST-- +Abstract hooks that are not implemented throw an error +--FILE-- + +--EXPECTF-- +Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_prop_abstract_hook.phpt b/Zend/tests/property_hooks/abstract_prop_abstract_hook.phpt new file mode 100644 index 0000000000000..40c2f22fedfc9 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_abstract_hook.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract property with abstract hook is redundant so invalid +--FILE-- + +--EXPECTF-- +Fatal error: Property hook on abstract property cannot be explicitly abstract in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_prop_hooks.phpt b/Zend/tests/property_hooks/abstract_prop_hooks.phpt new file mode 100644 index 0000000000000..ea331a43ee64d --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_hooks.phpt @@ -0,0 +1,17 @@ +--TEST-- +Explicit hooked property satisfies abstract property +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/abstract_prop_not_implemented.phpt b/Zend/tests/property_hooks/abstract_prop_not_implemented.phpt new file mode 100644 index 0000000000000..8e0b150795628 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_not_implemented.phpt @@ -0,0 +1,14 @@ +--TEST-- +Abstract property not implemented throws an error +--FILE-- + +--EXPECTF-- +Fatal error: Class A contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (A::$prop::get, A::$prop::set) in %s on line %d diff --git a/Zend/tests/property_hooks/abstract_prop_plain.phpt b/Zend/tests/property_hooks/abstract_prop_plain.phpt new file mode 100644 index 0000000000000..181ed2b202e8d --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_plain.phpt @@ -0,0 +1,17 @@ +--TEST-- +Plain property satisfies abstract property +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/property_hooks/abstract_prop_without_hooks.phpt b/Zend/tests/property_hooks/abstract_prop_without_hooks.phpt new file mode 100644 index 0000000000000..e1ee2b9a58a06 --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_without_hooks.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract property without hook is illegal +--FILE-- + +--EXPECTF-- +Fatal error: Only hooked properties may be declared abstract in %s on line %d diff --git a/Zend/tests/property_hooks/array_access.phpt b/Zend/tests/property_hooks/array_access.phpt new file mode 100644 index 0000000000000..d9ad934a11238 --- /dev/null +++ b/Zend/tests/property_hooks/array_access.phpt @@ -0,0 +1,49 @@ +--TEST-- +Array offset on ArrayAccess object in virtual property is allowed +--FILE-- + $this->collection; + } +} + +$c = new C(); +var_dump($c->prop['foo']); +var_dump($c->prop[] = 'foo'); +var_dump(isset($c->prop['foo'])); +unset($c->prop['foo']); + +?> +--EXPECT-- +Collection::offsetGet +bool(true) +Collection::offsetSet +string(3) "foo" +Collection::offsetExists +bool(true) +Collection::offsetUnset diff --git a/Zend/tests/property_hooks/ast_printing.phpt b/Zend/tests/property_hooks/ast_printing.phpt new file mode 100644 index 0000000000000..81ef5bd702d26 --- /dev/null +++ b/Zend/tests/property_hooks/ast_printing.phpt @@ -0,0 +1,53 @@ +--TEST-- +Hook AST printing +--FILE-- +prop1 = 42; + } + } + public $prop3 = 1 { + get => 42; + } + public $prop4 => 42; + }); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +assert(false && new class { + public $prop1 { + get; + set; + } + public $prop2 { + get { + return parent::$prop1::get(); + } + final set { + echo 'Foo'; + $this->prop1 = 42; + } + } + public $prop3 = 1 { + get { + return 42; + } + } + public $prop4 { + get { + return 42; + } + } +}) diff --git a/Zend/tests/property_hooks/attributes.phpt b/Zend/tests/property_hooks/attributes.phpt new file mode 100644 index 0000000000000..a429a77c7719e --- /dev/null +++ b/Zend/tests/property_hooks/attributes.phpt @@ -0,0 +1,40 @@ +--TEST-- +Hooks accept method-targeted attributes +--FILE-- +getHook('get')->getAttributes()[0]; +var_dump($getAttr->getName()); +var_dump($getAttr->getArguments()); +var_dump($getAttr->newInstance()); + +$setAttr = (new ReflectionProperty(C::class, 'prop'))->getHook('set')->getAttributes()[0]; +var_dump($setAttr->getName()); +var_dump($setAttr->getArguments()); +var_dump($setAttr->newInstance()); + +?> +--EXPECT-- +string(1) "A" +array(0) { +} +object(A)#2 (0) { +} +string(1) "B" +array(0) { +} +object(B)#3 (0) { +} diff --git a/Zend/tests/property_hooks/backed_implicit_get.phpt b/Zend/tests/property_hooks/backed_implicit_get.phpt new file mode 100644 index 0000000000000..3223471c758b8 --- /dev/null +++ b/Zend/tests/property_hooks/backed_implicit_get.phpt @@ -0,0 +1,22 @@ +--TEST-- +Backed property with implicit get +--FILE-- +prop = 42; +var_dump($c->prop); + +?> +--EXPECT-- +C::$prop::set +int(42) diff --git a/Zend/tests/property_hooks/backed_implicit_set.phpt b/Zend/tests/property_hooks/backed_implicit_set.phpt new file mode 100644 index 0000000000000..9c2d89ff78801 --- /dev/null +++ b/Zend/tests/property_hooks/backed_implicit_set.phpt @@ -0,0 +1,22 @@ +--TEST-- +Backed property with implicit set +--FILE-- +prop = 42; +var_dump($c->prop); + +?> +--EXPECT-- +C::$prop::get +int(42) diff --git a/Zend/tests/property_hooks/cache.phpt b/Zend/tests/property_hooks/cache.phpt new file mode 100644 index 0000000000000..ec6613cc0046a --- /dev/null +++ b/Zend/tests/property_hooks/cache.phpt @@ -0,0 +1,64 @@ +--TEST-- +Test caching of hooked property +--FILE-- +prop; } + set { echo __METHOD__, "\n"; $this->prop = $value; } + } +} + +function doTest(Test $test) { + $test->prop = null; + $test->prop; + $test->prop = 1; + $test->prop += 1; + $test->prop = []; + try { + $test->prop[] = 1; + } catch (\Error $e) { + echo $e->getMessage(), "\n"; + } + isset($test->prop); + isset($test->prop[0]); + try { + unset($test->prop); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +} + +$test = new Test; +$test->dyn = 1; +doTest($test); +echo "\n"; +doTest($test); + +?> +--EXPECTF-- +Deprecated: Creation of dynamic property Test::$dyn is deprecated in %s on line %d +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::set +Test::$prop::get +Cannot acquire reference to hooked property Test::$prop +Test::$prop::get +Test::$prop::get +Cannot unset hooked property Test::$prop + +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::get +Test::$prop::set +Test::$prop::set +Test::$prop::get +Cannot acquire reference to hooked property Test::$prop +Test::$prop::get +Test::$prop::get +Cannot unset hooked property Test::$prop diff --git a/Zend/tests/property_hooks/computed_shorthand.phpt b/Zend/tests/property_hooks/computed_shorthand.phpt new file mode 100644 index 0000000000000..ac2ecb3d18604 --- /dev/null +++ b/Zend/tests/property_hooks/computed_shorthand.phpt @@ -0,0 +1,15 @@ +--TEST-- +Computed property shorthand +--FILE-- + strtoupper(__PROPERTY__); +} + +$test = new Test; +var_dump($test->prop); + +?> +--EXPECT-- +string(4) "PROP" diff --git a/Zend/tests/property_hooks/cpp.phpt b/Zend/tests/property_hooks/cpp.phpt new file mode 100644 index 0000000000000..2f4443a95e0fc --- /dev/null +++ b/Zend/tests/property_hooks/cpp.phpt @@ -0,0 +1,34 @@ +--TEST-- +Constructor property promotion +--FILE-- + print("Getting\n"); + set => print("Setting\n"); + } + ) { + echo "Constructor\n"; + } +} + +echo "Pre-test\n"; +$test = new Test; +$test->prop; +$test->prop = 42; + +$r = (new ReflectionProperty(Test::class, 'prop')); +var_dump($r->hasDefaultValue()); +var_dump($r->getDefaultValue()); + +?> +--EXPECT-- +Pre-test +Setting +Constructor +Getting +Setting +bool(false) +NULL diff --git a/Zend/tests/property_hooks/default_on_hooks.phpt b/Zend/tests/property_hooks/default_on_hooks.phpt new file mode 100644 index 0000000000000..9001cef2fe631 --- /dev/null +++ b/Zend/tests/property_hooks/default_on_hooks.phpt @@ -0,0 +1,39 @@ +--TEST-- +Backed property may have default value +--FILE-- +prop); +$b->prop = 43; +var_dump($b->prop); + +?> +--EXPECT-- +object(B)#1 (1) { + ["prop"]=> + int(42) +} +B::$prop::get +int(42) +B::$prop::set +B::$prop::get +int(43) diff --git a/Zend/tests/property_hooks/default_on_virtual.phpt b/Zend/tests/property_hooks/default_on_virtual.phpt new file mode 100644 index 0000000000000..90a19ee2a1e85 --- /dev/null +++ b/Zend/tests/property_hooks/default_on_virtual.phpt @@ -0,0 +1,15 @@ +--TEST-- +Virtual properties cannot have default value +--FILE-- + +--EXPECTF-- +Fatal error: Cannot specify default value for hooked property Test::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/default_on_virtual_with_inheritance.phpt b/Zend/tests/property_hooks/default_on_virtual_with_inheritance.phpt new file mode 100644 index 0000000000000..04c295c97441c --- /dev/null +++ b/Zend/tests/property_hooks/default_on_virtual_with_inheritance.phpt @@ -0,0 +1,19 @@ +--TEST-- +Virtual property cannot have default value +--FILE-- + +--EXPECTF-- +Fatal error: Cannot specify default value for hooked property B::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/direct_hook_call.phpt b/Zend/tests/property_hooks/direct_hook_call.phpt new file mode 100644 index 0000000000000..d97ef4c469a4b --- /dev/null +++ b/Zend/tests/property_hooks/direct_hook_call.phpt @@ -0,0 +1,28 @@ +--TEST-- +Call property hooks by name +--FILE-- +{'$prop::get'}(); +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +try { + $test->{'$prop::set'}('foo'); +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Call to undefined method Test::$prop::get() +Call to undefined method Test::$prop::set() diff --git a/Zend/tests/property_hooks/dump.phpt b/Zend/tests/property_hooks/dump.phpt new file mode 100644 index 0000000000000..aa2ac32431e5e --- /dev/null +++ b/Zend/tests/property_hooks/dump.phpt @@ -0,0 +1,172 @@ +--TEST-- +Dumping object with property hooks +--FILE-- +dumpTest(); +(new Child)->dumpChild(); + +?> +--EXPECT-- +object(Test)#1 (4) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["private":"Test":private]=> + string(7) "private" + ["changed":"Test":private]=> + string(12) "changed Test" +} +array(5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["virtual"]=> + string(7) "VIRTUAL" + ["backed"]=> + string(6) "BACKED" +} +\Test::__set_state(array( + 'addedHooks' => 'addedHooks', + 'backed' => 'backed', + 'private' => 'private', + 'changed' => 'changed Test', +)) +{"addedHooks":"addedHooks","virtual":"VIRTUAL","backed":"BACKED"} +object(Child)#1 (5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["private":"Test":private]=> + string(7) "private" + ["changed":"Test":private]=> + string(12) "changed Test" + ["changed":"Child":private]=> + string(13) "changed Child" +} +array(5) { + ["addedHooks"]=> + string(10) "ADDEDHOOKS" + ["virtual"]=> + string(7) "VIRTUAL" + ["backed"]=> + string(6) "BACKED" +} +\Child::__set_state(array( + 'addedHooks' => 'addedHooks', + 'backed' => 'backed', + 'private' => 'private', + 'changed' => 'changed Test', + 'changed' => 'changed Child', +)) +{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"} +object(Child)#1 (5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["private":"Test":private]=> + string(7) "private" + ["changed":"Test":private]=> + string(12) "changed Test" + ["changed":"Child":private]=> + string(13) "changed Child" +} +array(5) { + ["addedHooks"]=> + string(10) "ADDEDHOOKS" + ["virtual"]=> + string(7) "VIRTUAL" + ["backed"]=> + string(6) "BACKED" +} +\Child::__set_state(array( + 'addedHooks' => 'addedHooks', + 'backed' => 'backed', + 'private' => 'private', + 'changed' => 'changed Test', + 'changed' => 'changed Child', +)) +{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"} +object(Child)#1 (5) { + ["addedHooks"]=> + string(10) "addedHooks" + ["backed"]=> + string(6) "backed" + ["private":"Test":private]=> + string(7) "private" + ["changed":"Test":private]=> + string(12) "changed Test" + ["changed":"Child":private]=> + string(13) "changed Child" +} +array(6) { + ["addedHooks"]=> + string(10) "ADDEDHOOKS" + ["virtual"]=> + string(7) "VIRTUAL" + ["backed"]=> + string(6) "BACKED" + ["changed"]=> + string(13) "changed Child" +} +\Child::__set_state(array( + 'addedHooks' => 'addedHooks', + 'backed' => 'backed', + 'private' => 'private', + 'changed' => 'changed Test', + 'changed' => 'changed Child', +)) +{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"} diff --git a/Zend/tests/property_hooks/duplicate_hook.phpt b/Zend/tests/property_hooks/duplicate_hook.phpt new file mode 100644 index 0000000000000..43dffcdb91183 --- /dev/null +++ b/Zend/tests/property_hooks/duplicate_hook.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot declare same property hook twice +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare property hook "get" in %s on line %d diff --git a/Zend/tests/property_hooks/explicit_set_value_parameter.phpt b/Zend/tests/property_hooks/explicit_set_value_parameter.phpt new file mode 100644 index 0000000000000..59297130051df --- /dev/null +++ b/Zend/tests/property_hooks/explicit_set_value_parameter.phpt @@ -0,0 +1,19 @@ +--TEST-- +Explicit set property hook $value parameter +--FILE-- +prop = 42; + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/explicit_set_value_parameter_type.phpt b/Zend/tests/property_hooks/explicit_set_value_parameter_type.phpt new file mode 100644 index 0000000000000..b20beab57eb4a --- /dev/null +++ b/Zend/tests/property_hooks/explicit_set_value_parameter_type.phpt @@ -0,0 +1,28 @@ +--TEST-- +Explicit set property hook $value parameter +--FILE-- +prop = is_array($prop) ? join(', ', $prop) : $prop; + } + } +} + +$test = new Test(); +var_dump($test->prop = 'prop'); +var_dump($test->prop = ['prop1', 'prop2']); +var_dump($test->prop); + +?> +--EXPECT-- +string(4) "prop" +array(2) { + [0]=> + string(5) "prop1" + [1]=> + string(5) "prop2" +} +string(12) "prop1, prop2" diff --git a/Zend/tests/property_hooks/field.phpt b/Zend/tests/property_hooks/field.phpt new file mode 100644 index 0000000000000..7fb3b11af67a8 --- /dev/null +++ b/Zend/tests/property_hooks/field.phpt @@ -0,0 +1,19 @@ +--TEST-- +Special $field variable refers to property backing store +--FILE-- + $field; + set => $field = $value; + } +} + +$test = new Test; +$test->prop = 42; +var_dump($test->prop); + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/field_assign.phpt b/Zend/tests/property_hooks/field_assign.phpt new file mode 100644 index 0000000000000..16e79dad57c83 --- /dev/null +++ b/Zend/tests/property_hooks/field_assign.phpt @@ -0,0 +1,35 @@ +--TEST-- +$field in different assignments +--FILE-- +prop = null; + +?> +--EXPECT-- +int(42) +int(43) +int(41) +int(123) +int(124) +int(123) diff --git a/Zend/tests/property_hooks/field_guard.phpt b/Zend/tests/property_hooks/field_guard.phpt new file mode 100644 index 0000000000000..e628b26a4f537 --- /dev/null +++ b/Zend/tests/property_hooks/field_guard.phpt @@ -0,0 +1,27 @@ +--TEST-- +$field refers to backing store from either hook +--FILE-- +prop; +$test->prop = 42; + +?> +--EXPECT-- +get +set +string(4) "prop" diff --git a/Zend/tests/property_hooks/final_private_prop.phpt b/Zend/tests/property_hooks/final_private_prop.phpt new file mode 100644 index 0000000000000..2687d57531f11 --- /dev/null +++ b/Zend/tests/property_hooks/final_private_prop.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property cannot be both final and private +--FILE-- + +--EXPECTF-- +Fatal error: Property cannot be both final and private in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop.phpt b/Zend/tests/property_hooks/final_prop.phpt new file mode 100644 index 0000000000000..c371cb075b507 --- /dev/null +++ b/Zend/tests/property_hooks/final_prop.phpt @@ -0,0 +1,16 @@ +--TEST-- +Property itself may be marked final (hook) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop_2.phpt b/Zend/tests/property_hooks/final_prop_2.phpt new file mode 100644 index 0000000000000..f605dfdc8a593 --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_2.phpt @@ -0,0 +1,16 @@ +--TEST-- +Property itself may be marked final (normal) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop_final_hook.phpt b/Zend/tests/property_hooks/final_prop_final_hook.phpt new file mode 100644 index 0000000000000..301c3d2be3894 --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_final_hook.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot make hook explicitly final in final property +--FILE-- + +--EXPECTF-- +Fatal error: Hook on final property cannot be explicitly final in %s on line %d diff --git a/Zend/tests/property_hooks/find_property_usage.phpt b/Zend/tests/property_hooks/find_property_usage.phpt new file mode 100644 index 0000000000000..4c4a240afde40 --- /dev/null +++ b/Zend/tests/property_hooks/find_property_usage.phpt @@ -0,0 +1,28 @@ +--TEST-- +Usage of property inside hook adds backing store +--FILE-- + $this->prop1; } + public $prop2 { get => fn () => $this->prop2; } + public $prop3 { get => function () { return $this->prop3; }; } + public $prop4 { get => $this->prop1; } + public $prop5 { get {} } + public $prop6 { get { var_dump($this->prop6); } } + public $prop7 { get => new class { function test() { $this->prop7; } }; } +} + +foreach ((new ReflectionClass(Test::class))->getProperties() as $prop) { + var_dump($prop->isVirtual()); +} + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(true) diff --git a/Zend/tests/property_hooks/foreach.phpt b/Zend/tests/property_hooks/foreach.phpt new file mode 100644 index 0000000000000..f6aa1c32f87c9 --- /dev/null +++ b/Zend/tests/property_hooks/foreach.phpt @@ -0,0 +1,121 @@ +--TEST-- +foreach over hooked properties +--FILE-- +_virtualByRef; + } + set { + echo __METHOD__, "\n"; + $this->_virtualByRef = $value; + } + } +} + +class ByVal extends ByRef { + private $_virtualByVal = 'virtualByVal'; + public $virtualByVal { + get { + echo __METHOD__, "\n"; + return $this->_virtualByVal; + } + set { + echo __METHOD__, "\n"; + $this->_virtualByVal = $value; + } + } + public $backed = 'backed' { + get { + echo __METHOD__, "\n"; + return $field; + } + set { + echo __METHOD__, "\n"; + $field = $value; + } + } + public string $backedUninitialized { + get { + echo __METHOD__, "\n"; + $field ??= 'backedUninitialized'; + return $field; + } + set { + echo __METHOD__, "\n"; + $field = $value; + } + } +} + +function testByRef($object) { + foreach ($object as $prop => &$value) { + echo "$prop => $value\n"; + $value = strtoupper($value); + } + var_dump($object); +} + +function testByVal($object) { + foreach ($object as $prop => $value) { + echo "$prop => $value\n"; + $object->{$prop} = strtoupper($value); + } + var_dump($object); +} + +testByVal(new ByVal); +testByVal(new ByRef); +testByRef(new ByRef); + +?> +--EXPECT-- +ByVal::$virtualByVal::get +virtualByVal => virtualByVal +ByVal::$virtualByVal::set +ByVal::$backed::get +backed => backed +ByVal::$backed::set +ByVal::$backedUninitialized::get +backedUninitialized => backedUninitialized +ByVal::$backedUninitialized::set +plain => plain +ByRef::$virtualByRef::get +virtualByRef => virtualByRef +ByRef::$virtualByRef::set +object(ByVal)#1 (5) { + ["plain"]=> + string(5) "PLAIN" + ["_virtualByRef":"ByRef":private]=> + string(12) "VIRTUALBYREF" + ["_virtualByVal":"ByVal":private]=> + string(12) "VIRTUALBYVAL" + ["backed"]=> + string(6) "BACKED" + ["backedUninitialized"]=> + string(19) "BACKEDUNINITIALIZED" +} +plain => plain +ByRef::$virtualByRef::get +virtualByRef => virtualByRef +ByRef::$virtualByRef::set +object(ByRef)#1 (2) { + ["plain"]=> + string(5) "PLAIN" + ["_virtualByRef":"ByRef":private]=> + string(12) "VIRTUALBYREF" +} +plain => plain +ByRef::$virtualByRef::get +virtualByRef => virtualByRef +object(ByRef)#1 (2) { + ["plain"]=> + string(5) "PLAIN" + ["_virtualByRef":"ByRef":private]=> + &string(12) "VIRTUALBYREF" +} diff --git a/Zend/tests/property_hooks/foreach_val_to_ref.phpt b/Zend/tests/property_hooks/foreach_val_to_ref.phpt new file mode 100644 index 0000000000000..6fa09945a0e46 --- /dev/null +++ b/Zend/tests/property_hooks/foreach_val_to_ref.phpt @@ -0,0 +1,28 @@ +--TEST-- +foreach by-ref on object with by-val hooked property +--FILE-- + $field; + set => $field = $value; + } +} + +function test($object) { + foreach ($object as $prop => &$value) { + $value = strtoupper($value); + } + var_dump($object); +} + +try { + test(new ByVal); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot create reference to property ByVal::$byVal diff --git a/Zend/tests/property_hooks/get.phpt b/Zend/tests/property_hooks/get.phpt new file mode 100644 index 0000000000000..0cb0a6b7c76be --- /dev/null +++ b/Zend/tests/property_hooks/get.phpt @@ -0,0 +1,24 @@ +--TEST-- +Basic get only property hook +--FILE-- +prop); + +try { + $test->prop = 0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +int(42) +Property Test::$prop is read-only diff --git a/Zend/tests/property_hooks/get_by_ref.phpt b/Zend/tests/property_hooks/get_by_ref.phpt new file mode 100644 index 0000000000000..4982c1a0aa9f9 --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref.phpt @@ -0,0 +1,34 @@ +--TEST-- +Get property hook by ref and indirect modification +--FILE-- +byVal; } + set { $this->byVal = $value; } + } +} + +$test = new Test; + +try { + $test->byVal = []; + $test->byVal[] = 42; +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->byVal); + +try { + $test->byVal =& $ref; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot acquire reference to hooked property Test::$byVal +array(0) { +} +Cannot assign by reference to overloaded object diff --git a/Zend/tests/property_hooks/get_by_ref_auto.phpt b/Zend/tests/property_hooks/get_by_ref_auto.phpt new file mode 100644 index 0000000000000..e4df31ee2a962 --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref_auto.phpt @@ -0,0 +1,30 @@ +--TEST-- +Get by reference with hooked property +--FILE-- +byVal[] = 42; +} catch (\Error $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; +} +var_dump($test->byVal); + +try { + $test->byVal =& $ref; +} catch (Error $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Error: Cannot acquire reference to hooked property Test::$byVal +array(0) { +} +Error: Cannot assign by reference to overloaded object diff --git a/Zend/tests/property_hooks/get_by_ref_backed.phpt b/Zend/tests/property_hooks/get_by_ref_backed.phpt new file mode 100644 index 0000000000000..49bfbb65baf2d --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref_backed.phpt @@ -0,0 +1,20 @@ +--TEST-- +Virtual get hook allows returning by reference +--FILE-- + $this->_prop; + set => $this->_prop = $value; + } +} + +?> +--EXPECTF-- +Fatal error: Only virtual get hooks may return by reference in %s on line %d diff --git a/Zend/tests/property_hooks/get_by_ref_virtual.phpt b/Zend/tests/property_hooks/get_by_ref_virtual.phpt new file mode 100644 index 0000000000000..17cb1517f27dd --- /dev/null +++ b/Zend/tests/property_hooks/get_by_ref_virtual.phpt @@ -0,0 +1,42 @@ +--TEST-- +Virtual get hook allows returning by reference +--FILE-- + $this->_prop; + set => $this->_prop = $value; + } +} + +function inc(&$ref) { + $ref++; +} + +$test = new Test(); +$test->prop = 42; + +$prop = &$test->prop; +$prop++; +var_dump($test); +var_dump($test->prop); +unset($prop); + +inc($test->prop); +var_dump($test); +var_dump($test->prop); + +?> +--EXPECT-- +object(Test)#1 (1) { + ["_prop":"Test":private]=> + &int(43) +} +int(43) +object(Test)#1 (1) { + ["_prop":"Test":private]=> + int(44) +} +int(44) diff --git a/Zend/tests/property_hooks/get_type_check.phpt b/Zend/tests/property_hooks/get_type_check.phpt new file mode 100644 index 0000000000000..2e0e9b9fcf6ea --- /dev/null +++ b/Zend/tests/property_hooks/get_type_check.phpt @@ -0,0 +1,26 @@ +--TEST-- +Get property hook must respect property type +--FILE-- +prop1); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->prop2); + +?> +--EXPECT-- +Test::$prop1::get(): Return value must be of type int, string returned +int(42) diff --git a/Zend/tests/property_hooks/hooked_with_magic_method.phpt b/Zend/tests/property_hooks/hooked_with_magic_method.phpt new file mode 100644 index 0000000000000..8e5a1ca87b23c --- /dev/null +++ b/Zend/tests/property_hooks/hooked_with_magic_method.phpt @@ -0,0 +1,28 @@ +--TEST-- +Access hooked property from magic method +--FILE-- +{$name}; + } + + public function __set($name, $value) { + $this->{$name} = $value; + } +} + +$test = new Test; +$test->prop; +$test->prop = 42; + +?> +--EXPECT-- +Test::$prop::get +Test::$prop::set diff --git a/Zend/tests/property_hooks/indirect_modification.phpt b/Zend/tests/property_hooks/indirect_modification.phpt new file mode 100644 index 0000000000000..5f5243a244ac1 --- /dev/null +++ b/Zend/tests/property_hooks/indirect_modification.phpt @@ -0,0 +1,51 @@ +--TEST-- +Different kinds of indirect modification with by-val and by-ref getters +--FILE-- +byVal = $value; + } + } +} + +$test = new Test; + +$test->byVal = 0; +$test->byVal++; +++$test->byVal; +$test->byVal += 1; +var_dump($test->byVal); +$test->byVal = []; +try { + $test->byVal[] = 1; +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->byVal); +try { + $ref =& $test->byVal; +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +$ref = 42; +var_dump($test->byVal); + +?> +--EXPECTF-- +Test::$byVal::set +Test::$byVal::set +Test::$byVal::set +Test::$byVal::set +int(3) +Test::$byVal::set +Cannot acquire reference to hooked property Test::$byVal +array(0) { +} +Cannot acquire reference to hooked property Test::$byVal +array(0) { +} diff --git a/Zend/tests/property_hooks/inheritance.phpt b/Zend/tests/property_hooks/inheritance.phpt new file mode 100644 index 0000000000000..ef6f7bac9c480 --- /dev/null +++ b/Zend/tests/property_hooks/inheritance.phpt @@ -0,0 +1,32 @@ +--TEST-- +Basic property hook inheritance +--FILE-- +prop); +$a->prop = 1; + +$b = new B; +var_dump($b->prop); +$b->prop = 1; + +?> +--EXPECT-- +string(1) "A" +A::$prop::set +string(1) "B" +A::$prop::set diff --git a/Zend/tests/property_hooks/interface.phpt b/Zend/tests/property_hooks/interface.phpt new file mode 100644 index 0000000000000..8cd0aa12ed4d0 --- /dev/null +++ b/Zend/tests/property_hooks/interface.phpt @@ -0,0 +1,15 @@ +--TEST-- +Property hooks in interfaces +--FILE-- + +--EXPECTF-- +Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (I::$prop::get, I::$prop::set) in %s on line %d diff --git a/Zend/tests/property_hooks/interface_explicit_abstract.phpt b/Zend/tests/property_hooks/interface_explicit_abstract.phpt new file mode 100644 index 0000000000000..fa7a7337260a5 --- /dev/null +++ b/Zend/tests/property_hooks/interface_explicit_abstract.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot have explicitly abstract property in interface +--FILE-- + +--EXPECTF-- +Fatal error: Property in interface cannot be explicitly abstract. All interface members are implicitly abstract in %s on line %d diff --git a/Zend/tests/property_hooks/interface_final_hook.phpt b/Zend/tests/property_hooks/interface_final_hook.phpt new file mode 100644 index 0000000000000..0cf2693c1c5cf --- /dev/null +++ b/Zend/tests/property_hooks/interface_final_hook.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cannot declare final hook in interface +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both abstract and final in %s on line %d diff --git a/Zend/tests/property_hooks/interface_final_prop.phpt b/Zend/tests/property_hooks/interface_final_prop.phpt new file mode 100644 index 0000000000000..ef664da7ab71a --- /dev/null +++ b/Zend/tests/property_hooks/interface_final_prop.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cannot declare final property in interface +--FILE-- + +--EXPECTF-- +Fatal error: Property in interface cannot be final in %s on line %d diff --git a/Zend/tests/property_hooks/interface_get_only.phpt b/Zend/tests/property_hooks/interface_get_only.phpt new file mode 100644 index 0000000000000..c6bc45fc87528 --- /dev/null +++ b/Zend/tests/property_hooks/interface_get_only.phpt @@ -0,0 +1,15 @@ +--TEST-- +Interface may contain only set with no implementation +--FILE-- + +--EXPECTF-- diff --git a/Zend/tests/property_hooks/interface_invalid_explicitly_abstract.phpt b/Zend/tests/property_hooks/interface_invalid_explicitly_abstract.phpt new file mode 100644 index 0000000000000..5453931ce7cbd --- /dev/null +++ b/Zend/tests/property_hooks/interface_invalid_explicitly_abstract.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hooks in interfaces cannot be explicitly abstract +--FILE-- + +--EXPECTF-- +Fatal error: Property hook in interface cannot be explicitly abstract. All interface members are implicitly abstract in %s on line %d diff --git a/Zend/tests/property_hooks/interface_not_implemented.phpt b/Zend/tests/property_hooks/interface_not_implemented.phpt new file mode 100644 index 0000000000000..3a9803039b97d --- /dev/null +++ b/Zend/tests/property_hooks/interface_not_implemented.phpt @@ -0,0 +1,15 @@ +--TEST-- +Property hook in interfaces not implemented +--FILE-- + +--EXPECTF-- +Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (I::$prop::get, I::$prop::set) in %s on line %d diff --git a/Zend/tests/property_hooks/interface_not_public.phpt b/Zend/tests/property_hooks/interface_not_public.phpt new file mode 100644 index 0000000000000..c652766f3314d --- /dev/null +++ b/Zend/tests/property_hooks/interface_not_public.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use non-public property hook in interface (whole property) +--FILE-- + +--EXPECTF-- +Fatal error: Property in interface cannot be protected or private in %s on line %d diff --git a/Zend/tests/property_hooks/interface_set_only.phpt b/Zend/tests/property_hooks/interface_set_only.phpt new file mode 100644 index 0000000000000..62423842b34f2 --- /dev/null +++ b/Zend/tests/property_hooks/interface_set_only.phpt @@ -0,0 +1,15 @@ +--TEST-- +Interface may contain only get with no implementation +--FILE-- + +--EXPECTF-- diff --git a/Zend/tests/property_hooks/invalid_abstract.phpt b/Zend/tests/property_hooks/invalid_abstract.phpt new file mode 100644 index 0000000000000..85286c9aa7713 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract.phpt @@ -0,0 +1,41 @@ +--TEST-- +Implementing abstract property hooks +--FILE-- +prop1; +$b->prop1 = 1; +$b->prop2; +$b->prop2 = 1; +$b->prop3; +$b->prop3 = 1; + +?> +--EXPECT-- +B::$prop1::get +A::$prop1::set +A::$prop2::get +B::$prop2::set diff --git a/Zend/tests/property_hooks/invalid_abstract_body.phpt b/Zend/tests/property_hooks/invalid_abstract_body.phpt new file mode 100644 index 0000000000000..e544d87b574d9 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_body.phpt @@ -0,0 +1,14 @@ +--TEST-- +Abstract property hook cannot have body +--FILE-- + +--EXPECTF-- +Fatal error: Abstract property hook cannot have body in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_abstract_final.phpt b/Zend/tests/property_hooks/invalid_abstract_final.phpt new file mode 100644 index 0000000000000..1e8fd59b9da26 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_final.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hook cannot be both abstract and final +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both abstract and final in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_abstract_indirect.phpt b/Zend/tests/property_hooks/invalid_abstract_indirect.phpt new file mode 100644 index 0000000000000..bac369895c6ed --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_indirect.phpt @@ -0,0 +1,16 @@ +--TEST-- +Class with abstract property hook not declared abstract (inherited 1) +--FILE-- + +--EXPECTF-- +Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_abstract_indirect_2.phpt b/Zend/tests/property_hooks/invalid_abstract_indirect_2.phpt new file mode 100644 index 0000000000000..e97d9fd7ac642 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_indirect_2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Class with abstract property hook not declared abstract (inherited 2) +--FILE-- + +--EXPECTF-- +Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_abstract_private.phpt b/Zend/tests/property_hooks/invalid_abstract_private.phpt new file mode 100644 index 0000000000000..b5cc7dae98e47 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_abstract_private.phpt @@ -0,0 +1,14 @@ +--TEST-- +Property hook cannot be both abstract and private +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both abstract and private in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_empty_hooks.phpt b/Zend/tests/property_hooks/invalid_empty_hooks.phpt new file mode 100644 index 0000000000000..c549536b4d61e --- /dev/null +++ b/Zend/tests/property_hooks/invalid_empty_hooks.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hook list cannot be empty +--FILE-- + +--EXPECTF-- +Fatal error: Property hook list cannot be empty in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_final_private.phpt b/Zend/tests/property_hooks/invalid_final_private.phpt new file mode 100644 index 0000000000000..f71a4fe7d53e4 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_final_private.phpt @@ -0,0 +1,12 @@ +--TEST-- +Property hook cannot be both final and private +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both final and private in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_hook_visibility.phpt b/Zend/tests/property_hooks/invalid_hook_visibility.phpt new file mode 100644 index 0000000000000..a5b1e721ff8d4 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_hook_visibility.phpt @@ -0,0 +1,14 @@ +--TEST-- +Property hooks cannot have explicity visibility +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the public modifier on a property hook in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_static.phpt b/Zend/tests/property_hooks/invalid_static.phpt new file mode 100644 index 0000000000000..a694269cd90fe --- /dev/null +++ b/Zend/tests/property_hooks/invalid_static.phpt @@ -0,0 +1,14 @@ +--TEST-- +Property hook cannot be static +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the static modifier on a property hook in %s on line %d diff --git a/Zend/tests/property_hooks/invalid_static_prop.phpt b/Zend/tests/property_hooks/invalid_static_prop.phpt new file mode 100644 index 0000000000000..c7e4b10055390 --- /dev/null +++ b/Zend/tests/property_hooks/invalid_static_prop.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use hooks for static property (for now) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot declare hooks for static property in %s on line %d diff --git a/Zend/tests/property_hooks/isset.phpt b/Zend/tests/property_hooks/isset.phpt new file mode 100644 index 0000000000000..1669d3616247d --- /dev/null +++ b/Zend/tests/property_hooks/isset.phpt @@ -0,0 +1,37 @@ +--TEST-- +isset() and empty() call get property hook +--FILE-- +prop1; } + set { $this->prop1 = $value; } + } +} + +$test = new Test; + +$test->prop1 = true; +var_dump(isset($test->prop1)); +var_dump(!empty($test->prop1)); +echo "\n", +$test->prop1 = false; +var_dump(isset($test->prop1)); +var_dump(!empty($test->prop1)); +echo "\n", +$test->prop1 = null; +var_dump(isset($test->prop1)); +var_dump(!empty($test->prop1)); +echo "\n"; + +?> +--EXPECT-- +bool(true) +bool(true) + +bool(true) +bool(false) + +bool(false) +bool(false) diff --git a/Zend/tests/property_hooks/magic_consts.phpt b/Zend/tests/property_hooks/magic_consts.phpt new file mode 100644 index 0000000000000..4865927c27109 --- /dev/null +++ b/Zend/tests/property_hooks/magic_consts.phpt @@ -0,0 +1,24 @@ +--TEST-- +Magic constants in property hooks +--FILE-- +prop; + +?> +--EXPECT-- +string(10) "$prop::get" +string(16) "Test::$prop::get" +string(4) "Test" diff --git a/Zend/tests/property_hooks/magic_interaction.phpt b/Zend/tests/property_hooks/magic_interaction.phpt new file mode 100644 index 0000000000000..7ea9e60f89691 --- /dev/null +++ b/Zend/tests/property_hooks/magic_interaction.phpt @@ -0,0 +1,62 @@ +--TEST-- +Interaction of inaccessible property hooks with magic methods +--FILE-- +$name; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } + public function __set($name, $value) { + echo __METHOD__, "($name, $value)\n"; + try { + $this->$name = $value; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } + public function __isset($name) { + echo __METHOD__, "($name)\n"; + try { + var_dump(isset($this->$name)); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } + public function __unset($name) { + echo "Never reached\n"; + } +} + +$b = new B; +$b->prop; +var_dump(isset($b->prop)); +$b->prop = 1; +try { + unset($b->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +A::$prop::get +A::$prop::get +bool(true) +A::$prop::set +Cannot unset hooked property B::$prop diff --git a/Zend/tests/property_hooks/magic_method_from_hooked.phpt b/Zend/tests/property_hooks/magic_method_from_hooked.phpt new file mode 100644 index 0000000000000..4f7fcd3fc5dfc --- /dev/null +++ b/Zend/tests/property_hooks/magic_method_from_hooked.phpt @@ -0,0 +1,30 @@ +--TEST-- +Accessing property from hook does not call magic method +--FILE-- + $field; + set => $field = $value; + } + + public function __get($name) { + echo __METHOD__, "\n"; + return 42; + } + + public function __set($name, $value) { + echo __METHOD__, "\n"; + } +} + +$test = new Test; +var_dump($test->prop); +$test->prop = 42; +var_dump($test->prop); + +?> +--EXPECT-- +NULL +int(42) diff --git a/Zend/tests/property_hooks/no_default_value_untyped_001.phpt b/Zend/tests/property_hooks/no_default_value_untyped_001.phpt new file mode 100644 index 0000000000000..1d5f66747b111 --- /dev/null +++ b/Zend/tests/property_hooks/no_default_value_untyped_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +Hooked properties with no default value are initialized to null +--FILE-- + $field; + set => $field = $value; + } +} + +$test = new Test; +var_dump($test->prop); + +?> +--EXPECT-- +NULL diff --git a/Zend/tests/property_hooks/no_default_value_untyped_002.phpt b/Zend/tests/property_hooks/no_default_value_untyped_002.phpt new file mode 100644 index 0000000000000..184a47a67059f --- /dev/null +++ b/Zend/tests/property_hooks/no_default_value_untyped_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +Hooked properties with no default value are initialized to null +--FILE-- +prop); + +?> +--EXPECT-- +NULL diff --git a/Zend/tests/property_hooks/no_get_parameters.phpt b/Zend/tests/property_hooks/no_get_parameters.phpt new file mode 100644 index 0000000000000..6755ec3c95cf7 --- /dev/null +++ b/Zend/tests/property_hooks/no_get_parameters.phpt @@ -0,0 +1,19 @@ +--TEST-- +No get property hook parameters +--FILE-- +prop = 42; + +?> +--EXPECTF-- +Fatal error: get hook of property Test::$prop must not have a parameter list in %s on line %d diff --git a/Zend/tests/property_hooks/object_in_hook.phpt b/Zend/tests/property_hooks/object_in_hook.phpt new file mode 100644 index 0000000000000..79ab5719e40ba --- /dev/null +++ b/Zend/tests/property_hooks/object_in_hook.phpt @@ -0,0 +1,41 @@ +--TEST-- +Assign on object inside property hook is ok +--FILE-- + $value\n"; + } + public function offsetUnset(mixed $offset): void { + echo "Unsetting $offset\n"; + } +} + +class C { + public $a { + get { return $this->a; } + set { $this->a = $value; } + } +} + +$c = new C; +$c->a = new A(); + +$c->a->b = 'b'; +var_dump($c->a->b); + +var_dump($c->a['foo']); +$c->a['foo'] = 'foo'; +unset($c->a['foo']); + +?> +--EXPECT-- +string(1) "b" +string(3) "foo" +Setting foo => foo +Unsetting foo diff --git a/Zend/tests/property_hooks/override_add_get.phpt b/Zend/tests/property_hooks/override_add_get.phpt new file mode 100644 index 0000000000000..28dbd047c0718 --- /dev/null +++ b/Zend/tests/property_hooks/override_add_get.phpt @@ -0,0 +1,37 @@ +--TEST-- +Overridden hooked property can add get to set only property +--FILE-- +prop = 1; +try { + var_dump($a->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$b = new B; +$b->prop = 1; +var_dump($b->prop); + +?> +--EXPECT-- +A::A::$prop::set +Property A::$prop is write-only +B::B::$prop::set +B::B::$prop::get +int(42) diff --git a/Zend/tests/property_hooks/override_add_get_invariant.phpt b/Zend/tests/property_hooks/override_add_get_invariant.phpt new file mode 100644 index 0000000000000..b5f60f32af19d --- /dev/null +++ b/Zend/tests/property_hooks/override_add_get_invariant.phpt @@ -0,0 +1,21 @@ +--TEST-- +Overridden hooked property that adds get to set only property becomes invariant +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::$prop must be int (as in class A) in %s on line %d diff --git a/Zend/tests/property_hooks/override_add_set.phpt b/Zend/tests/property_hooks/override_add_set.phpt new file mode 100644 index 0000000000000..6b490d4d87f94 --- /dev/null +++ b/Zend/tests/property_hooks/override_add_set.phpt @@ -0,0 +1,38 @@ +--TEST-- +Overridden hooked property can add set to get only property +--FILE-- +prop = 1; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($a->prop); + +$b = new B; +$b->prop = 1; +var_dump($b->prop); + +?> +--EXPECT-- +Property A::$prop is read-only +A::A::$prop::get +int(42) +B::B::$prop::set +B::B::$prop::get +int(42) diff --git a/Zend/tests/property_hooks/override_add_set_invariant.phpt b/Zend/tests/property_hooks/override_add_set_invariant.phpt new file mode 100644 index 0000000000000..c9576c2e1f1b3 --- /dev/null +++ b/Zend/tests/property_hooks/override_add_set_invariant.phpt @@ -0,0 +1,21 @@ +--TEST-- +Overridden hooked property that adds set to get only property becomes invariant +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::$prop must be string|int (as in class A) in %s on line %d diff --git a/Zend/tests/property_hooks/override_by_plain_prop.phpt b/Zend/tests/property_hooks/override_by_plain_prop.phpt new file mode 100644 index 0000000000000..a764d036b8796 --- /dev/null +++ b/Zend/tests/property_hooks/override_by_plain_prop.phpt @@ -0,0 +1,19 @@ +--TEST-- +Hooks must not be overridden by a plain property +--FILE-- + +--EXPECTF-- +Fatal error: Non-virtual property B::$prop must not redeclare virtual property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/override_implicit_with_explicit.phpt b/Zend/tests/property_hooks/override_implicit_with_explicit.phpt new file mode 100644 index 0000000000000..068f1d5341204 --- /dev/null +++ b/Zend/tests/property_hooks/override_implicit_with_explicit.phpt @@ -0,0 +1,30 @@ +--TEST-- +Property can be overridden by hooked property +--FILE-- +prop = 1; +var_dump($a->prop); + +$b = new B; +$b->prop = 2; +var_dump($b->prop); + +?> +--EXPECT-- +int(1) +B::B::$prop::set +B::B::$prop::get +int(3) diff --git a/Zend/tests/property_hooks/override_plain_set.phpt b/Zend/tests/property_hooks/override_plain_set.phpt new file mode 100644 index 0000000000000..b92d09b810b9c --- /dev/null +++ b/Zend/tests/property_hooks/override_plain_set.phpt @@ -0,0 +1,23 @@ +--TEST-- +Overridden set of plain property +--FILE-- +prop = 1; +var_dump($b->prop); + +?> +--EXPECT-- +B::B::$prop::set +NULL diff --git a/Zend/tests/property_hooks/parameter_attributes.phpt b/Zend/tests/property_hooks/parameter_attributes.phpt new file mode 100644 index 0000000000000..209966841b28f --- /dev/null +++ b/Zend/tests/property_hooks/parameter_attributes.phpt @@ -0,0 +1,26 @@ +--TEST-- +Hook parameters accept parameter-targeted attributes +--FILE-- +prop = 'secret'; +} catch (Exception $e) { + echo $e; +} + +?> +--EXPECTF-- +Exception: Exception from $prop in %s:%d +Stack trace: +#0 %s(%d): C->$prop::set(Object(SensitiveParameterValue)) +#1 {main} diff --git a/Zend/tests/property_hooks/parent_get.phpt b/Zend/tests/property_hooks/parent_get.phpt new file mode 100644 index 0000000000000..b42bd10f24007 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get.phpt @@ -0,0 +1,31 @@ +--TEST-- +Allow calling parent get in property hooks +--FILE-- +prop); + +$b = new B; +var_dump($b->prop); + +?> +--EXPECT-- +int(41) +int(42) diff --git a/Zend/tests/property_hooks/parent_get_ci.phpt b/Zend/tests/property_hooks/parent_get_ci.phpt new file mode 100644 index 0000000000000..30178a2f32def --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_ci.phpt @@ -0,0 +1,31 @@ +--TEST-- +parent::$prop::hook() syntax is case-insensitive +--FILE-- +prop); + +$b = new B; +var_dump($b->prop); + +?> +--EXPECT-- +int(41) +int(42) diff --git a/Zend/tests/property_hooks/parent_get_in_class_with_no_parent.phpt b/Zend/tests/property_hooks/parent_get_in_class_with_no_parent.phpt new file mode 100644 index 0000000000000..767e585f91999 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_in_class_with_no_parent.phpt @@ -0,0 +1,21 @@ +--TEST-- +Using parent::$prop::get() in class with no parent +--FILE-- + parent::$prop::get(); + } +} + +$foo = new Foo(); +try { + var_dump($foo->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot use "parent" when current class scope has no parent diff --git a/Zend/tests/property_hooks/parent_get_not_in_class.phpt b/Zend/tests/property_hooks/parent_get_not_in_class.phpt new file mode 100644 index 0000000000000..56a079b8b33ed --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_not_in_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +Using parent::$prop::get() outside of class context +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "parent" when no class scope is active in %s on line %d diff --git a/Zend/tests/property_hooks/parent_get_plain.phpt b/Zend/tests/property_hooks/parent_get_plain.phpt new file mode 100644 index 0000000000000..f461b39a8a372 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_plain.phpt @@ -0,0 +1,21 @@ +--TEST-- +Using parent::$prop::get() on plain property +--FILE-- + parent::$prop::get(); + } +} + +$c = new C(); +var_dump($c->prop); + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/parent_get_plain_typed_uninitialized.phpt b/Zend/tests/property_hooks/parent_get_plain_typed_uninitialized.phpt new file mode 100644 index 0000000000000..0edd8ff074efd --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_plain_typed_uninitialized.phpt @@ -0,0 +1,25 @@ +--TEST-- +Using parent::$prop::get() on plain uninitialized typed property +--FILE-- + parent::$prop::get(); + } +} + +$c = new C(); +try { + var_dump($c->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Typed property C::$prop must not be accessed before initialization diff --git a/Zend/tests/property_hooks/parent_get_plain_untyped_uninitialized.phpt b/Zend/tests/property_hooks/parent_get_plain_untyped_uninitialized.phpt new file mode 100644 index 0000000000000..c1f8a9a5571e1 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_plain_untyped_uninitialized.phpt @@ -0,0 +1,21 @@ +--TEST-- +Using parent::$prop::get() on plain untyped uninitialized property +--FILE-- + parent::$prop::get(); + } +} + +$c = new C(); +var_dump($c->prop); + +?> +--EXPECT-- +NULL diff --git a/Zend/tests/property_hooks/parent_get_plain_zpp.phpt b/Zend/tests/property_hooks/parent_get_plain_zpp.phpt new file mode 100644 index 0000000000000..5f43e9ee3a999 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_plain_zpp.phpt @@ -0,0 +1,27 @@ +--TEST-- +parent::$prop::get() ZPP +--FILE-- +prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +get() expects exactly 0 arguments, 1 given diff --git a/Zend/tests/property_hooks/parent_get_undefined_property.phpt b/Zend/tests/property_hooks/parent_get_undefined_property.phpt new file mode 100644 index 0000000000000..388448963e3e8 --- /dev/null +++ b/Zend/tests/property_hooks/parent_get_undefined_property.phpt @@ -0,0 +1,25 @@ +--TEST-- +Using parent::$prop::get() with undefined property +--FILE-- +prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Undefined property P::$prop diff --git a/Zend/tests/property_hooks/parent_in_different_hook.phpt b/Zend/tests/property_hooks/parent_in_different_hook.phpt new file mode 100644 index 0000000000000..5a9d872abe84f --- /dev/null +++ b/Zend/tests/property_hooks/parent_in_different_hook.phpt @@ -0,0 +1,16 @@ +--TEST-- +Using parent::$prop::get() in a different hook +--FILE-- + +--EXPECTF-- +Fatal error: Must not use parent::$foo::get() in a different property hook (set) in %s on line %d diff --git a/Zend/tests/property_hooks/parent_in_different_property.phpt b/Zend/tests/property_hooks/parent_in_different_property.phpt new file mode 100644 index 0000000000000..82a6dbcee3cd5 --- /dev/null +++ b/Zend/tests/property_hooks/parent_in_different_property.phpt @@ -0,0 +1,16 @@ +--TEST-- +Using parent::$prop::get() in a different property +--FILE-- + +--EXPECTF-- +Fatal error: Must not use parent::$bar::get() in a different property ($foo) in %s on line %d diff --git a/Zend/tests/property_hooks/parent_outside_property.phpt b/Zend/tests/property_hooks/parent_outside_property.phpt new file mode 100644 index 0000000000000..ce1d04245a1bf --- /dev/null +++ b/Zend/tests/property_hooks/parent_outside_property.phpt @@ -0,0 +1,14 @@ +--TEST-- +Using parent::$prop::get() outside a property hook +--FILE-- + +--EXPECTF-- +Fatal error: Must not use parent::$prop::get() outside a property hook in %s on line %d diff --git a/Zend/tests/property_hooks/parent_set.phpt b/Zend/tests/property_hooks/parent_set.phpt new file mode 100644 index 0000000000000..f6eeac89e6834 --- /dev/null +++ b/Zend/tests/property_hooks/parent_set.phpt @@ -0,0 +1,31 @@ +--TEST-- +Allow calling parent set in property hooks +--FILE-- +prop = 41; + +$b = new B; +$b->prop = 41; + +?> +--EXPECT-- +int(41) +int(42) diff --git a/Zend/tests/property_hooks/parent_set_plain.phpt b/Zend/tests/property_hooks/parent_set_plain.phpt new file mode 100644 index 0000000000000..935c07a41af8a --- /dev/null +++ b/Zend/tests/property_hooks/parent_set_plain.phpt @@ -0,0 +1,21 @@ +--TEST-- +Using parent::$prop::set() on plain property +--FILE-- + var_dump(parent::$prop::set($value)); + } +} + +$c = new C(); +$c->prop = 42; + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/parent_set_plain_zpp.phpt b/Zend/tests/property_hooks/parent_set_plain_zpp.phpt new file mode 100644 index 0000000000000..e3b13c3a3b66a --- /dev/null +++ b/Zend/tests/property_hooks/parent_set_plain_zpp.phpt @@ -0,0 +1,27 @@ +--TEST-- +parent::$prop::set() ZPP +--FILE-- +prop = 42; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +set() expects exactly 1 argument, 2 given diff --git a/Zend/tests/property_hooks/parent_wrong_property_info.phpt b/Zend/tests/property_hooks/parent_wrong_property_info.phpt new file mode 100644 index 0000000000000..c18aad7f11f9f --- /dev/null +++ b/Zend/tests/property_hooks/parent_wrong_property_info.phpt @@ -0,0 +1,25 @@ +--TEST-- +Allow calling parent set in property hooks +--FILE-- + parent::$prop::get(); + } +} + +$b = new B; +try { + var_dump($b->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot access private property A::$prop diff --git a/Zend/tests/property_hooks/plain_to_hook.phpt b/Zend/tests/property_hooks/plain_to_hook.phpt new file mode 100644 index 0000000000000..46fef3d6bdf9c --- /dev/null +++ b/Zend/tests/property_hooks/plain_to_hook.phpt @@ -0,0 +1,33 @@ +--TEST-- +Override plain property with hooked property +--FILE-- +prop = 42; +echo $a->prop, "\n"; + +$b = new B(); +$b->prop = 42; +echo $b->prop, "\n"; + +?> +--EXPECT-- +42 +B::$prop::set(42) +B::$prop::get() diff --git a/Zend/tests/property_hooks/private_override.phpt b/Zend/tests/property_hooks/private_override.phpt new file mode 100644 index 0000000000000..1bd94403dfe85 --- /dev/null +++ b/Zend/tests/property_hooks/private_override.phpt @@ -0,0 +1,56 @@ +--TEST-- +Overriding private hooks +--FILE-- +prop1; + $this->prop1 = 1; + $this->prop2; + $this->prop2 = 1; + } +} + +class B extends A { + public $prop1 { + get { echo __METHOD__, "\n"; } + set { echo __METHOD__, "\n"; } + } + public $prop2 { + get { echo __METHOD__, "\n"; } + set { echo __METHOD__, "\n"; } + } +} + +$a = new A; +$a->testPrivate(); +echo "\n"; + +$b = new B; +$b->testPrivate(); +echo "\n"; + +$b->prop1; +$b->prop1 = 1; +$b->prop2; +$b->prop2 = 1; + +?> +--EXPECT-- +A::$prop2::get +A::$prop2::set + +A::$prop2::get +A::$prop2::set + +B::$prop1::get +B::$prop1::set +B::$prop2::get +B::$prop2::set diff --git a/Zend/tests/property_hooks/private_prop_final_hook.phpt b/Zend/tests/property_hooks/private_prop_final_hook.phpt new file mode 100644 index 0000000000000..09653b612c3e7 --- /dev/null +++ b/Zend/tests/property_hooks/private_prop_final_hook.phpt @@ -0,0 +1,12 @@ +--TEST-- +Private property with final hook +--FILE-- + +--EXPECTF-- +Fatal error: Property hook cannot be both final and private in %s on line %d diff --git a/Zend/tests/property_hooks/property_const.phpt b/Zend/tests/property_hooks/property_const.phpt new file mode 100644 index 0000000000000..cdb884264e4a5 --- /dev/null +++ b/Zend/tests/property_hooks/property_const.phpt @@ -0,0 +1,28 @@ +--TEST-- +__PROPERTY__ magic constant +--FILE-- + __PROPERTY__; + set => var_dump(__PROPERTY__); + } + + public function method() { + return __PROPERTY__; + } +} + +$test = new Test; +var_dump($test->prop); +$test->prop = 'foo'; +var_dump($test->method()); +var_dump(__PROPERTY__); + +?> +--EXPECT-- +string(4) "prop" +string(4) "prop" +string(0) "" +string(0) "" diff --git a/Zend/tests/property_hooks/property_promotion.phpt b/Zend/tests/property_hooks/property_promotion.phpt new file mode 100644 index 0000000000000..e9fbf116db400 --- /dev/null +++ b/Zend/tests/property_hooks/property_promotion.phpt @@ -0,0 +1,21 @@ +--TEST-- +Generated hooks in property promotion +--FILE-- +prop; + +?> +--EXPECT-- +set(42) +get diff --git a/Zend/tests/property_hooks/protected_to_public.phpt b/Zend/tests/property_hooks/protected_to_public.phpt new file mode 100644 index 0000000000000..afa1a80128d49 --- /dev/null +++ b/Zend/tests/property_hooks/protected_to_public.phpt @@ -0,0 +1,23 @@ +--TEST-- +Inherited hooks change visibility with property +--FILE-- + 42; + } +} + +class B extends A { + public $prop { + set {} + } +} + +$b = new B(); +var_dump($b->prop); + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/property_hooks/readonly.phpt b/Zend/tests/property_hooks/readonly.phpt new file mode 100644 index 0000000000000..be68bc800576e --- /dev/null +++ b/Zend/tests/property_hooks/readonly.phpt @@ -0,0 +1,12 @@ +--TEST-- +Hooked properties cannot be readonly +--FILE-- + +--EXPECTF-- +Fatal error: Hooked properties cannot be readonly in %s on line %d diff --git a/Zend/tests/property_hooks/recursion.phpt b/Zend/tests/property_hooks/recursion.phpt new file mode 100644 index 0000000000000..0a070f9ba96ca --- /dev/null +++ b/Zend/tests/property_hooks/recursion.phpt @@ -0,0 +1,36 @@ +--TEST-- +Recursion behavior of property hooks +--FILE-- +prop * 2; } + set { $this->prop = $value * 2; } + } + + // Edge-case where recursion happens via isset(). + public int $prop2 { + get { return isset($this->prop2); } + set { } + } +} + +$test = new Test; +$test->prop = 10; +var_dump($test->prop); +var_dump(isset($test->prop)); +var_dump(isset($test->prop2)); +var_dump($test); + +?> +--EXPECT-- +int(40) +bool(true) +bool(true) +object(Test)#1 (1) { + ["prop"]=> + int(20) + ["prop2"]=> + uninitialized(int) +} diff --git a/Zend/tests/property_hooks/set.phpt b/Zend/tests/property_hooks/set.phpt new file mode 100644 index 0000000000000..04e36ae85411f --- /dev/null +++ b/Zend/tests/property_hooks/set.phpt @@ -0,0 +1,32 @@ +--TEST-- +Basic set only property hook +--FILE-- +_prop = $value; } + } +} + +$test = new Test; +$test->prop = 42; +var_dump($test->_prop); + +try { + var_dump($test->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(isset($test->prop)); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +int(42) +Property Test::$prop is write-only +Property Test::$prop is write-only diff --git a/Zend/tests/property_hooks/set_by_ref.phpt b/Zend/tests/property_hooks/set_by_ref.phpt new file mode 100644 index 0000000000000..e8d6fc4b395dc --- /dev/null +++ b/Zend/tests/property_hooks/set_by_ref.phpt @@ -0,0 +1,14 @@ +--TEST-- +set parameter must not be by-reference +--FILE-- + +--EXPECTF-- +Fatal error: Parameter $value of set hook Test::$prop must not be pass-by-reference in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_001.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_001.phpt new file mode 100644 index 0000000000000..d6090645abfae --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_001.phpt @@ -0,0 +1,14 @@ +--TEST-- +set $value parameter variance +--FILE-- + +--EXPECTF-- +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_002.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_002.phpt new file mode 100644 index 0000000000000..7e2d45aa3c464 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_002.phpt @@ -0,0 +1,14 @@ +--TEST-- +set $value parameter variance +--FILE-- + +--EXPECTF-- +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_003.phpt b/Zend/tests/property_hooks/set_value_parameter_type_variance_003.phpt new file mode 100644 index 0000000000000..fde736277d127 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_003.phpt @@ -0,0 +1,17 @@ +--TEST-- +set $value parameter variance +--FILE-- + +--EXPECTF-- +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_value_parameter_type_variance_004.inc b/Zend/tests/property_hooks/set_value_parameter_type_variance_004.inc new file mode 100644 index 0000000000000..714792d3dc085 --- /dev/null +++ b/Zend/tests/property_hooks/set_value_parameter_type_variance_004.inc @@ -0,0 +1,7 @@ + +--EXPECTF-- +Autoloading X +Autoloading Y + +Fatal error: Type of parameter $prop of hook Test::$prop::set must be compatible with property type in %s on line %d diff --git a/Zend/tests/property_hooks/set_variadic.phpt b/Zend/tests/property_hooks/set_variadic.phpt new file mode 100644 index 0000000000000..bb47173a402f7 --- /dev/null +++ b/Zend/tests/property_hooks/set_variadic.phpt @@ -0,0 +1,14 @@ +--TEST-- +set parameter must not be variadic +--FILE-- + +--EXPECTF-- +Fatal error: Parameter $value of set hook Test::$prop must not be variadic in %s on line %d diff --git a/Zend/tests/property_hooks/syntax.phpt b/Zend/tests/property_hooks/syntax.phpt new file mode 100644 index 0000000000000..32280546db6e9 --- /dev/null +++ b/Zend/tests/property_hooks/syntax.phpt @@ -0,0 +1,14 @@ +--TEST-- +Basic property hook syntax +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/property_hooks/traits.phpt b/Zend/tests/property_hooks/traits.phpt new file mode 100644 index 0000000000000..782825faaa67f --- /dev/null +++ b/Zend/tests/property_hooks/traits.phpt @@ -0,0 +1,24 @@ +--TEST-- +Hooked properties in traits +--FILE-- +prop; +$test->prop = 1; + +?> +--EXPECT-- +T::$prop::get +T::$prop::set diff --git a/Zend/tests/property_hooks/traits_abstract.phpt b/Zend/tests/property_hooks/traits_abstract.phpt new file mode 100644 index 0000000000000..e13638f392472 --- /dev/null +++ b/Zend/tests/property_hooks/traits_abstract.phpt @@ -0,0 +1,16 @@ +--TEST-- +Abstract property hooks from trait +--FILE-- + +--EXPECTF-- +Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (T::$prop::get, T::$prop::set) in %s on line %d diff --git a/Zend/tests/property_hooks/traits_conflict.phpt b/Zend/tests/property_hooks/traits_conflict.phpt new file mode 100644 index 0000000000000..71f591703f8aa --- /dev/null +++ b/Zend/tests/property_hooks/traits_conflict.phpt @@ -0,0 +1,24 @@ +--TEST-- +Trait property hook conflict +--FILE-- + +--EXPECTF-- +Fatal error: C and T define the same hooked property ($prop) in the composition of C. Conflict resolution between hooked properties is currently not supported. Class was composed in %s on line %d diff --git a/Zend/tests/property_hooks/type_compatibility.phpt b/Zend/tests/property_hooks/type_compatibility.phpt new file mode 100644 index 0000000000000..f2f64f683620b --- /dev/null +++ b/Zend/tests/property_hooks/type_compatibility.phpt @@ -0,0 +1,16 @@ +--TEST-- +Relaxed type compatibility for read-only and write-only properties +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/property_hooks/type_compatibility_invalid.phpt b/Zend/tests/property_hooks/type_compatibility_invalid.phpt new file mode 100644 index 0000000000000..8898936768b4c --- /dev/null +++ b/Zend/tests/property_hooks/type_compatibility_invalid.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid type compatibility for read-only property +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::$a must be subtype of int (as in class A) in %s on line %d diff --git a/Zend/tests/property_hooks/type_compatibility_invalid_2.phpt b/Zend/tests/property_hooks/type_compatibility_invalid_2.phpt new file mode 100644 index 0000000000000..903dd30cd9e10 --- /dev/null +++ b/Zend/tests/property_hooks/type_compatibility_invalid_2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid type compatibility for write-only property +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::$a must be supertype of int|float (as in class A) in %s on line %d diff --git a/Zend/tests/property_hooks/unknown_hook.phpt b/Zend/tests/property_hooks/unknown_hook.phpt new file mode 100644 index 0000000000000..188d7f0560505 --- /dev/null +++ b/Zend/tests/property_hooks/unknown_hook.phpt @@ -0,0 +1,14 @@ +--TEST-- +Unknown property hook +--FILE-- + +--EXPECTF-- +Fatal error: Unknown hook "foobar" for property Test::$prop, expected "get" or "set" in %s on line %d diff --git a/Zend/tests/property_hooks/unknown_hook_private.phpt b/Zend/tests/property_hooks/unknown_hook_private.phpt new file mode 100644 index 0000000000000..a2c25f6360454 --- /dev/null +++ b/Zend/tests/property_hooks/unknown_hook_private.phpt @@ -0,0 +1,14 @@ +--TEST-- +Unknown property hook (private property) +--FILE-- + +--EXPECTF-- +Fatal error: Unknown hook "foobar" for property Test::$prop, expected "get" or "set" in %s on line %d diff --git a/Zend/tests/property_hooks/unserialize.phpt b/Zend/tests/property_hooks/unserialize.phpt new file mode 100644 index 0000000000000..2461817a08814 --- /dev/null +++ b/Zend/tests/property_hooks/unserialize.phpt @@ -0,0 +1,71 @@ +--TEST-- +unserialize() with hooks +--FILE-- +prop3; +$test_u->prop3 = 42; + +$s = 'O:4:"Test":1:{s:5:"prop3";i:42;}'; +var_dump(unserialize($s)); +echo "\n"; + +// Override implicit hook with explicit. +class Test2 extends Test { + public $prop1 { + get { echo __METHOD__, "\n"; } + } +} + +$test2 = new Test2(1, 2); +var_dump($s = serialize($test2)); +var_dump($test2_u = unserialize($s)); +$test2_u->prop1; +$test2_u->prop1 = 42; + +$s = 'O:5:"Test2":1:{s:5:"prop1";i:42;}'; +var_dump(unserialize($s)); + +?> +--EXPECTF-- +string(31) "O:4:"Test":1:{s:5:"prop1";i:1;}" +object(Test)#2 (1) { + ["prop1"]=> + int(1) +} +Test::$prop3::get +Test::$prop3::set + +Warning: unserialize(): Cannot unserialize value for hooked property Test::$prop3 in %s on line %d + +Warning: unserialize(): Error at offset 26 of 32 bytes in %s on line %d +bool(false) + +string(32) "O:5:"Test2":1:{s:5:"prop1";i:1;}" +object(Test2)#4 (1) { + ["prop1"]=> + int(1) +} +Test2::$prop1::get +object(Test2)#5 (1) { + ["prop1"]=> + int(42) +} diff --git a/Zend/tests/property_hooks/unset.phpt b/Zend/tests/property_hooks/unset.phpt new file mode 100644 index 0000000000000..49edd56831a0b --- /dev/null +++ b/Zend/tests/property_hooks/unset.phpt @@ -0,0 +1,29 @@ +--TEST-- +Hooked properties cannot be unset +--FILE-- +prop; } + set { $this->prop = $value; } + } + + public function __unset($name) { + echo "Never reached\n"; + } +} + +$test = new Test; +$test->prop = 42; +try { + unset($test->prop); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->prop); + +?> +--EXPECT-- +Cannot unset hooked property Test::$prop +int(42) diff --git a/Zend/tests/property_hooks/update_constants_virtual_prop.phpt b/Zend/tests/property_hooks/update_constants_virtual_prop.phpt new file mode 100644 index 0000000000000..fcead21f4fd27 --- /dev/null +++ b/Zend/tests/property_hooks/update_constants_virtual_prop.phpt @@ -0,0 +1,16 @@ +--TEST-- +Make sure constant updating works in the presence of virtual properties +--FILE-- +prop2); + +?> +--EXPECT-- +int(42) diff --git a/Zend/tests/type_declarations/typed_properties_002.phpt b/Zend/tests/type_declarations/typed_properties_002.phpt index b0d1e1c0f2795..b10aaf95bf807 100644 --- a/Zend/tests/type_declarations/typed_properties_002.phpt +++ b/Zend/tests/type_declarations/typed_properties_002.phpt @@ -9,7 +9,7 @@ $thing = new class() { var_dump($thing->int); ?> --EXPECTF-- -Fatal error: Uncaught Error: Typed property class@anonymous::$int must not be accessed before initialization in %s:6 +Fatal error: Uncaught Error: Typed property class@anonymous::$int must not be accessed before initialization in %s:%d Stack trace: #0 {main} thrown in %s on line 6 diff --git a/Zend/tests/type_declarations/typed_properties_026.phpt b/Zend/tests/type_declarations/typed_properties_026.phpt index 7d01e927a0289..6d2174e99c778 100644 --- a/Zend/tests/type_declarations/typed_properties_026.phpt +++ b/Zend/tests/type_declarations/typed_properties_026.phpt @@ -17,7 +17,7 @@ class Baz{ var_dump((new Baz)->get()); ?> --EXPECTF-- -Fatal error: Uncaught Error: Typed property Baz::$baz must not be accessed before initialization in %s:10 +Fatal error: Uncaught Error: Typed property Baz::$baz must not be accessed before initialization in %s:%d Stack trace: #0 %s(14): Baz->get() #1 {main} diff --git a/Zend/zend.h b/Zend/zend.h index fd21cbfeb93cf..d7fc13a7761e0 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -204,6 +204,8 @@ struct _zend_class_entry { uint32_t num_interfaces; uint32_t num_traits; + uint32_t num_prop_hooks_variance_checks; + uint32_t num_hooked_props; /* class_entry or string(s) depending on ZEND_ACC_LINKED */ union { diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e9058f3e43db9..b6160fb942e56 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4259,6 +4259,18 @@ 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 ((access_type & ZEND_ACC_VIRTUAL)) { + /* Virtual properties have no backing storage, the offset should never be used. The virtual + * flag may still be removed during inheritance. So allow adding it, but throw if the flag + * hasn't been removed after inheritance. */ + if ((!property || Z_TYPE_P(property) == IS_UNDEF)) { + property_info->offset = (uint32_t)-1; + goto skip_default_property; + } + } + if (!(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) { @@ -4319,13 +4331,14 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z ZVAL_COPY_VALUE(property_default_ptr, property); Z_PROP_FLAG_P(property_default_ptr) = Z_ISUNDEF_P(property) ? IS_PROP_UNINIT : 0; } +skip_default_property: 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)) { + if (property && Z_REFCOUNTED_P(property)) { zend_error_noreturn(E_CORE_ERROR, "Internal zvals cannot be refcounted"); } } @@ -4343,6 +4356,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->hooks = NULL; property_info->ce = ce; property_info->type = type; diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 954c9958c534d..9c4d45765637d 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -239,34 +239,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; } @@ -1499,6 +1505,14 @@ static ZEND_COLD void zend_ast_export_stmt(smart_str *str, zend_ast *ast, int in case ZEND_AST_NAMESPACE: case ZEND_AST_DECLARE: break; + case ZEND_AST_PROP_GROUP: { + zend_ast *first_prop = zend_ast_get_list(ast->child[1])->child[0]; + zend_ast *hook_list = first_prop->child[3]; + if (hook_list == NULL) { + smart_str_appendc(str, ';'); + } + break; + } default: smart_str_appendc(str, ';'); break; @@ -1953,6 +1967,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio case T_TRAIT_C: APPEND_STR("__TRAIT__"); case T_METHOD_C: APPEND_STR("__METHOD__"); case T_FUNC_C: APPEND_STR("__FUNCTION__"); + case T_PROPERTY_C: APPEND_STR("__PROPERTY__"); case T_NS_C: APPEND_STR("__NAMESPACE__"); case T_CLASS_C: APPEND_STR("__CLASS__"); EMPTY_SWITCH_DEFAULT_CASE(); @@ -2105,6 +2120,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_ex(str, ast->child[1], 0, indent); smart_str_appendc(str, ')'); break; + case ZEND_AST_PARENT_PROPERTY_HOOK_CALL: + smart_str_append(str, Z_STR_P(zend_ast_get_zval(ast->child[0]))); + smart_str_appendc(str, '('); + zend_ast_export_ex(str, ast->child[1], 0, indent); + smart_str_appendc(str, ')'); + break; case ZEND_AST_CALLABLE_CONVERT: smart_str_appends(str, "..."); break; @@ -2318,7 +2339,59 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio break; case ZEND_AST_PROP_ELEM: smart_str_appendc(str, '$'); - ZEND_FALLTHROUGH; + zend_ast_export_name(str, ast->child[0], 0, indent); + + zend_ast *default_value = ast->child[1]; + if (default_value) { + smart_str_appends(str, " = "); + zend_ast_export_ex(str, default_value, 0, indent + 1); + } + + if (ast->child[3]) { + zend_ast_list *hook_list = zend_ast_get_list(ast->child[3]); + + smart_str_appends(str, " {"); + smart_str_appendc(str, '\n'); + indent++; + zend_ast_export_indent(str, indent); + + for (uint32_t i = 0; i < hook_list->children; i++) { + zend_ast_decl *hook = (zend_ast_decl *)hook_list->child[i]; + zend_ast_export_visibility(str, hook->flags); + if (hook->flags & ZEND_ACC_ABSTRACT) { + smart_str_appends(str, "abstract "); + } + if (hook->flags & ZEND_ACC_FINAL) { + smart_str_appends(str, "final "); + } + switch (i) { + case ZEND_PROPERTY_HOOK_GET: + smart_str_appends(str, "get"); + break; + case ZEND_PROPERTY_HOOK_SET: + smart_str_appends(str, "set"); + break; + } + zend_ast *statement_list = hook->child[2]; + if (statement_list != NULL) { + smart_str_appends(str, " {\n"); + zend_ast_export_stmt(str, statement_list, indent + 1); + zend_ast_export_indent(str, indent); + smart_str_appendc(str, '}'); + } else { + smart_str_appendc(str, ';'); + } + if (i < (hook_list->children - 1)) { + smart_str_appendc(str, '\n'); + zend_ast_export_indent(str, indent); + } + } + smart_str_appendc(str, '\n'); + indent--; + zend_ast_export_indent(str, indent); + smart_str_appendc(str, '}'); + } + break; case ZEND_AST_CONST_ELEM: zend_ast_export_name(str, ast->child[0], 0, indent); APPEND_DEFAULT_VALUE(1); @@ -2576,6 +2649,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr) case ZEND_AST_CLOSURE: case ZEND_AST_METHOD: case ZEND_AST_ARROW_FUNC: + case ZEND_AST_PROPERTY_HOOK: ((zend_ast_decl *) ast)->child[4] = attr; break; case ZEND_AST_CLASS: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 0bbb3a820c291..0dc0958237343 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -43,6 +43,7 @@ enum _zend_ast_kind { ZEND_AST_METHOD, ZEND_AST_CLASS, ZEND_AST_ARROW_FUNC, + ZEND_AST_PROPERTY_HOOK, /* list nodes */ ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT, @@ -149,6 +150,7 @@ enum _zend_ast_kind { ZEND_AST_MATCH, ZEND_AST_MATCH_ARM, ZEND_AST_NAMED_ARG, + ZEND_AST_PARENT_PROPERTY_HOOK_CALL, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, @@ -159,7 +161,6 @@ enum _zend_ast_kind { ZEND_AST_TRY, ZEND_AST_CATCH, ZEND_AST_PROP_GROUP, - ZEND_AST_PROP_ELEM, ZEND_AST_CONST_ELEM, ZEND_AST_CLASS_CONST_GROUP, @@ -170,9 +171,12 @@ enum _zend_ast_kind { ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, ZEND_AST_FOREACH, ZEND_AST_ENUM_CASE, + ZEND_AST_PROP_ELEM, /* 5 child nodes */ - ZEND_AST_PARAM = 5 << ZEND_AST_NUM_CHILDREN_SHIFT, + + /* 6 child nodes */ + ZEND_AST_PARAM = 6 << ZEND_AST_NUM_CHILDREN_SHIFT, }; typedef uint16_t zend_ast_kind; @@ -227,12 +231,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); @@ -240,7 +244,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); @@ -267,11 +275,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_builtin_functions.c b/Zend/zend_builtin_functions.c index b8cd96c480282..959aa469c7780 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -749,7 +749,9 @@ ZEND_FUNCTION(get_object_vars) Z_PARAM_OBJ(zobj) ZEND_PARSE_PARAMETERS_END(); - properties = zobj->handlers->get_properties(zobj); + zval obj_zv; + ZVAL_OBJ(&obj_zv, zobj); + properties = zend_get_properties_for(&obj_zv, ZEND_PROP_PURPOSE_GET_OJBECT_VARS); if (properties == NULL) { RETURN_EMPTY_ARRAY(); } @@ -765,6 +767,8 @@ ZEND_FUNCTION(get_object_vars) ZEND_HASH_FOREACH_KEY_VAL(properties, num_key, key, value) { bool is_dynamic = 1; + zval tmp; + ZVAL_UNDEF(&tmp); if (Z_TYPE_P(value) == IS_INDIRECT) { value = Z_INDIRECT_P(value); if (UNEXPECTED(Z_ISUNDEF_P(value))) { @@ -781,6 +785,19 @@ ZEND_FUNCTION(get_object_vars) if (Z_ISREF_P(value) && Z_REFCOUNT_P(value) == 1) { value = Z_REFVAL_P(value); } + if (Z_TYPE_P(value) == IS_PTR) { + /* value is IS_PTR for properties with hooks. */ + zend_property_info *prop_info = Z_PTR_P(value); + zend_read_property_ex(prop_info->ce, zobj, prop_info->name, /* silent */ true, &tmp); + if (EG(exception)) { + if (zobj->properties != properties) { + zend_release_properties(properties); + } + zval_ptr_dtor(return_value); + RETURN_THROWS(); + } + value = &tmp; + } Z_TRY_ADDREF_P(value); if (UNEXPECTED(!key)) { @@ -799,8 +816,12 @@ ZEND_FUNCTION(get_object_vars) } else { zend_symtable_add_new(Z_ARRVAL_P(return_value), key, value); } + zval_ptr_dtor(&tmp); } ZEND_HASH_FOREACH_END(); } + if (zobj->properties != properties) { + zend_release_properties(properties); + } } /* }}} */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index eeb940060bba4..d26b52fb0ee63 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -35,6 +35,7 @@ #include "zend_enum.h" #include "zend_observer.h" #include "zend_call_stack.h" +#include "zend_property_hooks.h" #define SET_NODE(target, src) do { \ target ## _type = (src)->op_type; \ @@ -403,6 +404,8 @@ void init_compiler(void) /* {{{ */ { CG(arena) = zend_arena_create(64 * 1024); CG(active_op_array) = NULL; + CG(active_property_info) = NULL; + CG(active_property_hook_kind) = (zend_property_hook_kind)-1; memset(&CG(context), 0, sizeof(CG(context))); zend_init_compiler_data_structures(); zend_init_rsrc_list(); @@ -814,23 +817,37 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token { switch (token) { case T_PUBLIC: - return ZEND_ACC_PUBLIC; + if (target != ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + return ZEND_ACC_PUBLIC; + } + break; case T_PROTECTED: - return ZEND_ACC_PROTECTED; + if (target != ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + return ZEND_ACC_PROTECTED; + } + break; case T_PRIVATE: - return ZEND_ACC_PRIVATE; + if (target != ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + return ZEND_ACC_PRIVATE; + } + break; case T_READONLY: if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) { return ZEND_ACC_READONLY; } break; case T_ABSTRACT: - if (target == ZEND_MODIFIER_TARGET_METHOD) { + if (target == ZEND_MODIFIER_TARGET_METHOD + || target == ZEND_MODIFIER_TARGET_PROPERTY + || target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { return ZEND_ACC_ABSTRACT; } break; case T_FINAL: - if (target == ZEND_MODIFIER_TARGET_METHOD || target == ZEND_MODIFIER_TARGET_CONSTANT) { + if (target == ZEND_MODIFIER_TARGET_METHOD + || target == ZEND_MODIFIER_TARGET_CONSTANT + || target == ZEND_MODIFIER_TARGET_PROPERTY + || target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { return ZEND_ACC_FINAL; } break; @@ -843,19 +860,21 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token char *member; if (target == ZEND_MODIFIER_TARGET_PROPERTY) { - member = "property"; + member = "a property"; } else if (target == ZEND_MODIFIER_TARGET_METHOD) { - member = "method"; + member = "a method"; } else if (target == ZEND_MODIFIER_TARGET_CONSTANT) { - member = "class constant"; + member = "a class constant"; } else if (target == ZEND_MODIFIER_TARGET_CPP) { - member = "promoted property"; + member = "a promoted property"; + } else if (target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + member = "a property hook"; } else { ZEND_UNREACHABLE(); } zend_throw_exception_ex(zend_ce_compile_error, 0, - "Cannot use the %s modifier on a %s", zend_modifier_token_to_string(token), member); + "Cannot use the %s modifier on %s", zend_modifier_token_to_string(token), member); return 0; } @@ -1971,6 +1990,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->num_interfaces = 0; ce->interfaces = NULL; ce->num_traits = 0; + ce->num_prop_hooks_variance_checks = 0; + ce->num_hooked_props = 0; ce->trait_names = NULL; ce->trait_aliases = NULL; ce->trait_precedences = NULL; @@ -4545,6 +4566,59 @@ static zend_result zend_try_compile_special_func(znode *result, zend_string *lcn } /* }}} */ +static const char *zend_get_cstring_from_property_hook_kind(zend_property_hook_kind kind) { + switch (kind) { + case ZEND_PROPERTY_HOOK_GET: + return "get"; + case ZEND_PROPERTY_HOOK_SET: + return "set"; + EMPTY_SWITCH_DEFAULT_CASE() + } +} + +static void zend_compile_parent_property_hook_call(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ +{ + zend_class_entry *ce = CG(active_class_entry); + if (!ce) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use \"parent\" when no class scope is active"); + } + + zend_ast *name_ast = ast->child[0]; + zend_ast *args_ast = ast->child[1]; + + zend_string *name = zend_ast_get_str(name_ast); + char *property_name_start = ZSTR_VAL(name) + strlen("parent::$"); + char *hook_name_start = strchr(property_name_start, ':') + 2; + zend_string *property_name = zend_string_init(property_name_start, hook_name_start - property_name_start - 2, /* persistent */ false); + zend_string *hook_name = zend_string_init(hook_name_start, (ZSTR_VAL(name) + ZSTR_LEN(name)) - hook_name_start, /* persistent */ false); + zend_property_hook_kind hook_kind = zend_get_property_hook_kind_from_name(hook_name); + ZEND_ASSERT(hook_kind != (uint32_t)-1); + + zend_property_info *prop_info = CG(active_property_info); + if (!prop_info) { + zend_error_noreturn(E_COMPILE_ERROR, "Must not use parent::$%s::%s() outside a property hook", + ZSTR_VAL(property_name), ZSTR_VAL(hook_name)); + } + if (!zend_string_equals(property_name, prop_info->name)) { + zend_error_noreturn(E_COMPILE_ERROR, "Must not use parent::$%s::%s() in a different property ($%s)", + ZSTR_VAL(property_name), ZSTR_VAL(hook_name), ZSTR_VAL(prop_info->name)); + } + if (hook_kind != CG(active_property_hook_kind)) { + zend_error_noreturn(E_COMPILE_ERROR, "Must not use parent::$%s::%s() in a different property hook (%s)", + ZSTR_VAL(property_name), ZSTR_VAL(hook_name), zend_get_cstring_from_property_hook_kind(CG(active_property_hook_kind))); + } + zend_string_release_ex(hook_name, /* persistent */ false); + + zend_op *opline = get_next_op(); + opline->opcode = ZEND_INIT_PARENT_PROPERTY_HOOK_CALL; + opline->op1_type = IS_CONST; + opline->op1.constant = zend_add_class_name_literal(property_name); + opline->op2.num = hook_kind; + + zend_function *fbc = NULL; + zend_compile_call_common(result, args_ast, fbc, zend_ast_get_lineno(name_ast)); +} + static void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ { zend_ast *name_ast = ast->child[0]; @@ -6853,6 +6927,92 @@ static void zend_compile_attributes( } /* }}} */ +static void zend_compile_property_hooks( + zend_property_info *prop_info, zend_string *prop_name, + zend_ast *prop_type_ast, zend_ast_list *hooks); + +typedef struct { + zend_string *property_name; + bool uses_property; +} find_property_usage_context; + +static void zend_property_hook_find_property_usage(zend_ast **ast_ptr, void *_context) /* {{{ */ +{ + zend_ast *ast = *ast_ptr; + find_property_usage_context *context = (find_property_usage_context *) _context; + + if (ast == NULL) { + return; + } else if (ast->kind == ZEND_AST_VAR + && ast->child[0]->kind == ZEND_AST_ZVAL) { + zval *var_name = zend_ast_get_zval(ast->child[0]); + if (Z_TYPE_P(var_name) == IS_STRING + && zend_string_equals_literal(Z_STR_P(var_name), "field")) { + context->uses_property = true; + zend_ast_destroy(ast); + *ast_ptr = zend_ast_create(ZEND_AST_PROP, + zend_ast_create(ZEND_AST_VAR, zend_ast_create_zval_from_str(ZSTR_INIT_LITERAL("this", false))), + zend_ast_create_zval_from_str(zend_string_copy(context->property_name))); + /* We just replaced the AST, no need to look for references in this branch. */ + return; + } + } else if (ast->kind == ZEND_AST_PROP || ast->kind == ZEND_AST_NULLSAFE_PROP) { + zend_ast *object_ast = ast->child[0]; + zend_ast *property_ast = ast->child[1]; + + if (object_ast->kind == ZEND_AST_VAR + && object_ast->child[0]->kind == ZEND_AST_ZVAL + && property_ast->kind == ZEND_AST_ZVAL) { + zval *object = zend_ast_get_zval(object_ast->child[0]); + zval *property = zend_ast_get_zval(property_ast); + if (Z_TYPE_P(object) == IS_STRING + && Z_TYPE_P(property) == IS_STRING + && zend_string_equals_literal(Z_STR_P(object), "this") + && zend_string_equals(Z_STR_P(property), context->property_name)) { + context->uses_property = true; + /* We just replaced the AST, no need to look for references in this branch. */ + return; + } + } + } + + /* Don't search across function/class boundaries. */ + if (!zend_ast_is_special(ast)) { + zend_ast_apply(ast, zend_property_hook_find_property_usage, context); + } +} + +static bool zend_property_hook_uses_property(zend_string *property_name, zend_ast *hook_ast) +{ + find_property_usage_context context = { property_name, false }; + zend_property_hook_find_property_usage(&hook_ast, &context); + return context.uses_property; +} + +static bool zend_property_is_virtual(zend_string *property_name, zend_ast *hooks_ast) { + if (!hooks_ast) { + return false; + } + + bool is_virtual = true; + + zend_ast_list *hooks = zend_ast_get_list(hooks_ast); + for (uint32_t i = 0; i < hooks->children; i++) { + zend_ast_decl *hook = (zend_ast_decl *) hooks->child[i]; + if (!hook) { + continue; + } + + zend_ast *body = hook->child[2]; + /* Abstract properties aren't virtual. */ + if (!body || zend_property_hook_uses_property(property_name, body)) { + is_virtual = false; + } + } + + return is_virtual; +} + static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fallback_return_type) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); @@ -6904,6 +7064,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zend_ast **default_ast_ptr = ¶m_ast->child[2]; zend_ast *attributes_ast = param_ast->child[3]; zend_ast *doc_comment_ast = param_ast->child[4]; + zend_ast *hooks_ast = param_ast->child[5]; zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast)); bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0; @@ -7076,7 +7237,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 /* Don't give the property an explicit default value. For typed properties this means * uninitialized, for untyped properties it means an implicit null default value. */ zval default_value; - if (ZEND_TYPE_IS_SET(type)) { + if (ZEND_TYPE_IS_SET(type) || hooks_ast) { ZVAL_UNDEF(&default_value); } else { if (property_flags & ZEND_ACC_READONLY) { @@ -7090,7 +7251,13 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zend_string *doc_comment = doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL; zend_property_info *prop = zend_declare_typed_property( - scope, name, &default_value, property_flags | ZEND_ACC_PROMOTED, doc_comment, type); + scope, name, &default_value, + property_flags | (zend_property_is_virtual(name, hooks_ast) ? ZEND_ACC_VIRTUAL : 0) | ZEND_ACC_PROMOTED, + doc_comment, type); + if (hooks_ast) { + zend_ast_list *hooks = zend_ast_get_list(hooks_ast); + zend_compile_property_hooks(prop, name, type_ast, hooks); + } if (attributes_ast) { zend_compile_attributes( &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, ZEND_ATTRIBUTE_TARGET_PARAMETER); @@ -7466,7 +7633,7 @@ static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, } /* }}} */ -static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ +static zend_op_array *zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *params_ast = decl->child[0]; @@ -7474,7 +7641,8 @@ static 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 *lcname; + zend_string *lcname = NULL; + bool is_hook = decl->kind == ZEND_AST_PROPERTY_HOOK; zend_class_entry *orig_class_entry = CG(active_class_entry); zend_op_array *orig_op_array = CG(active_op_array); @@ -7501,7 +7669,11 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) op_array->fn_flags |= ZEND_ACC_CLOSURE; } - if (is_method) { + if (is_hook) { + zend_class_entry *ce = CG(active_class_entry); + op_array->scope = ce; + op_array->function_name = zend_string_copy(decl->name); + } else if (is_method) { bool has_body = stmt_ast != NULL; lcname = zend_begin_method_decl(op_array, decl->name, has_body); } else { @@ -7601,37 +7773,264 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) zend_observer_function_declared_notify(op_array, lcname); } - zend_string_release_ex(lcname, 0); + if (lcname != NULL) { + zend_string_release_ex(lcname, 0); + } CG(active_op_array) = orig_op_array; CG(active_class_entry) = orig_class_entry; + + return op_array; } /* }}} */ +zend_property_hook_kind zend_get_property_hook_kind_from_name(zend_string *name) { + if (zend_string_equals_literal_ci(name, "get")) { + return ZEND_PROPERTY_HOOK_GET; + } else if (zend_string_equals_literal_ci(name, "set")) { + return ZEND_PROPERTY_HOOK_SET; + } else { + return (zend_property_hook_kind)-1; + } +} + +static void zend_compile_property_hooks( + zend_property_info *prop_info, zend_string *prop_name, + zend_ast *prop_type_ast, zend_ast_list *hooks) +{ + zend_class_entry *ce = CG(active_class_entry); + + if (hooks->children == 0) { + zend_error_noreturn(E_COMPILE_ERROR, "Property hook list cannot be empty"); + } + + for (uint32_t i = 0; i < hooks->children; i++) { + zend_ast_decl *hook = (zend_ast_decl *) hooks->child[i]; + zend_string *name = hook->name; + zend_ast **stmt_ast_ptr = &hook->child[2]; + zend_ast **return_ast_ptr = &hook->child[3]; + zend_ast *orig_stmt_ast = *stmt_ast_ptr; + CG(zend_lineno) = hook->start_lineno; + bool reset_return_ast = false; + zend_ast **value_type_ast_ptr = NULL; + + /* Non-private hooks are always public. This avoids having to copy the hook when inheriting + * hooks from protected properties to public ones. */ + uint32_t hook_visibility = (prop_info->flags & ZEND_ACC_PPP_MASK) != ZEND_ACC_PRIVATE ? ZEND_ACC_PUBLIC : ZEND_ACC_PRIVATE; + hook->flags |= hook_visibility; + + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + if (prop_info->flags & (ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Property in interface cannot be protected or private"); + } + if (hook->flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Property hook in interface cannot be explicitly abstract. " + "All interface members are implicitly abstract"); + } + hook->flags |= ZEND_ACC_ABSTRACT; + } else if (prop_info->flags & ZEND_ACC_ABSTRACT) { + if (hook->flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Property hook on abstract property cannot be explicitly abstract"); + } + hook->flags |= ZEND_ACC_ABSTRACT; + } + + if (prop_info->flags & ZEND_ACC_STATIC) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare hooks for static property"); + } + if ((hook->flags & ZEND_ACC_FINAL) && (hook->flags & ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, "Property hook cannot be both final and private"); + } + if ((prop_info->flags & ZEND_ACC_FINAL) && (hook->flags & ZEND_ACC_FINAL)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Hook on final property cannot be explicitly final"); + } + if (hook->flags & ZEND_ACC_ABSTRACT) { + if (orig_stmt_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Abstract property hook cannot have body"); + } + if (hook->flags & ZEND_ACC_PRIVATE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Property hook cannot be both abstract and private"); + } + if (hook->flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, "Property hook cannot be both abstract and final"); + } + + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } else { + if (!orig_stmt_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Non-abstract property hook must have a body"); + } + } + + if (*return_ast_ptr) { + zend_error_noreturn(E_COMPILE_ERROR, + "Property hook \"%s\" may not have a return type " + "(the return type is determined by the property type)", + ZSTR_VAL(name)); + } + + zend_property_hook_kind hook_kind = zend_get_property_hook_kind_from_name(name); + if (hook_kind == (zend_property_hook_kind)-1) { + zend_error_noreturn(E_COMPILE_ERROR, + "Unknown hook \"%s\" for property %s::$%s, expected \"get\" or \"set\"", + ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + + if (hook_kind == ZEND_PROPERTY_HOOK_GET) { + if (hook->child[0]) { + zend_error_noreturn(E_COMPILE_ERROR, "get hook of property %s::$%s must not have a parameter list", + ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + + hook->child[0] = zend_ast_create_list(0, ZEND_AST_PARAM_LIST); + + reset_return_ast = true; + *return_ast_ptr = prop_type_ast; + } else if (hook_kind == ZEND_PROPERTY_HOOK_SET) { + if (hook->child[0]) { + zend_ast_list *param_list = zend_ast_get_list(hook->child[0]); + if (param_list->children != 1) { + zend_error_noreturn(E_COMPILE_ERROR, "%s hook of property %s::$%s must accept exactly one parameters", + ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + zend_ast *value_param_ast = param_list->child[0]; + if (value_param_ast->attr & ZEND_PARAM_REF) { + zend_error_noreturn(E_COMPILE_ERROR, "Parameter $%s of %s hook %s::$%s must not be pass-by-reference", + ZSTR_VAL(zend_ast_get_str(value_param_ast->child[1])), ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + if (value_param_ast->attr & ZEND_PARAM_VARIADIC) { + zend_error_noreturn(E_COMPILE_ERROR, "Parameter $%s of %s hook %s::$%s must not be variadic", + ZSTR_VAL(zend_ast_get_str(value_param_ast->child[1])), ZSTR_VAL(name), ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + zend_ast *value_parameter = param_list->child[0]; + if (!value_parameter->child[0]) { + value_type_ast_ptr = &value_parameter->child[0]; + value_parameter->child[0] = prop_type_ast; + } + } 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, + /* hooks */ NULL); + value_type_ast_ptr = ¶m->child[0]; + hook->child[0] = zend_ast_create_list(1, ZEND_AST_PARAM_LIST, param); + } + } else { + ZEND_UNREACHABLE(); + } + + hook->name = zend_strpprintf(0, "$%s::%s", ZSTR_VAL(prop_name), ZSTR_VAL(name)); + + CG(active_property_hook_kind) = hook_kind; + zend_function *func = (zend_function *) zend_compile_func_decl( + NULL, (zend_ast *) hook, /* toplevel */ false); + CG(active_property_hook_kind) = (zend_property_hook_kind)-1; + + ce->ce_flags |= ZEND_ACC_USE_GUARDS; + + if (!prop_info->hooks) { + prop_info->hooks = zend_arena_alloc(&CG(arena), ZEND_PROPERTY_HOOK_STRUCT_SIZE); + memset(prop_info->hooks, 0, ZEND_PROPERTY_HOOK_STRUCT_SIZE); + } + + if (prop_info->hooks[hook_kind]) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot redeclare property hook \"%s\"", ZSTR_VAL(name)); + } + prop_info->hooks[hook_kind] = func; + + if (hook_kind == ZEND_PROPERTY_HOOK_SET) { + switch (zend_verify_property_hook_variance(prop_info, func)) { + case INHERITANCE_SUCCESS: + break; + case INHERITANCE_UNRESOLVED: + ce->num_prop_hooks_variance_checks++; + break; + case INHERITANCE_ERROR: + zend_hooked_property_variance_error(prop_info); + case INHERITANCE_WARNING: + ZEND_UNREACHABLE(); + } + } + + zend_string_release(name); + /* Un-share type ASTs. Alternatively we could duplicate them. */ + if (reset_return_ast) { + *return_ast_ptr = NULL; + } + if (value_type_ast_ptr) { + *value_type_ast_ptr = NULL; + } + } + + ce->num_hooked_props++; + + if (!ce->get_iterator) { + ce->get_iterator = zend_hooked_object_get_iterator; + } +} + static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */ { 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, "Enum %s cannot include properties", ZSTR_VAL(ce->name)); } + if ((flags & ZEND_ACC_FINAL) && (flags & ZEND_ACC_PRIVATE)) { + zend_error_noreturn(E_COMPILE_ERROR, "Property cannot be both final and private"); + } + + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + if (flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, "Property in interface cannot be final"); + } + if (flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Property in interface cannot be explicitly abstract. " + "All interface members are implicitly abstract"); + } + flags |= ZEND_ACC_ABSTRACT; + } + for (i = 0; i < children; ++i) { zend_property_info *info; zend_ast *prop_ast = list->child[i]; zend_ast *name_ast = prop_ast->child[0]; zend_ast **value_ast_ptr = &prop_ast->child[1]; zend_ast *doc_comment_ast = prop_ast->child[2]; + zend_ast *hooks_ast = prop_ast->child[3]; zend_string *name = zval_make_interned_string(zend_ast_get_zval(name_ast)); zend_string *doc_comment = NULL; zval value_zv; zend_type type = ZEND_TYPE_INIT_NONE(0); + flags |= zend_property_is_virtual(name, hooks_ast) ? ZEND_ACC_VIRTUAL : 0; + + if (!hooks_ast) { + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Interfaces may only include hooked properties"); + } + if (flags & ZEND_ACC_ABSTRACT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Only hooked properties may be declared abstract"); + } + } + + if (hooks_ast && flags & ZEND_ACC_READONLY) { + zend_error_noreturn(E_COMPILE_ERROR, + "Hooked properties cannot be readonly"); + } if (type_ast) { type = zend_compile_typename(type_ast, /* force_allow_null */ 0); @@ -7675,7 +8074,7 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(str)); } } - } else if (!ZEND_TYPE_IS_SET(type)) { + } else if (!ZEND_TYPE_IS_SET(type) && !hooks_ast) { ZVAL_NULL(&value_zv); } else { ZVAL_UNDEF(&value_zv); @@ -7704,6 +8103,16 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type); + if (hooks_ast) { + CG(active_property_info) = info; + zend_compile_property_hooks(info, name, type_ast, zend_ast_get_list(hooks_ast)); + CG(active_property_info) = NULL; + + if (!ce->parent_name) { + zend_verify_hooked_property(ce, info, name); + } + } + if (attr_ast) { zend_compile_attributes(&info->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, 0); } @@ -8075,7 +8484,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) } /* We currently don't early-bind classes that implement interfaces or use traits */ - if (!ce->num_interfaces && !ce->num_traits + if (!ce->num_interfaces && !ce->num_traits && !ce->num_prop_hooks_variance_checks && !(CG(compiler_options) & ZEND_COMPILE_WITHOUT_EXECUTION)) { if (toplevel) { if (extends_ast) { @@ -8144,7 +8553,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) if (toplevel && (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) /* We currently don't early-bind classes that implement interfaces or use traits */ - && !ce->num_interfaces && !ce->num_traits + && !ce->num_interfaces && !ce->num_traits && !ce->num_prop_hooks_variance_checks ) { if (!extends_ast) { /* Use empty string for classes without parents to avoid new handler, and special @@ -8533,6 +8942,14 @@ static bool zend_try_ct_eval_magic_const(zval *zv, zend_ast *ast) /* {{{ */ ZVAL_EMPTY_STRING(zv); } break; + case T_PROPERTY_C:; + zend_property_info *prop_info = CG(active_property_info); + if (prop_info) { + ZVAL_STR_COPY(zv, prop_info->name); + } else { + ZVAL_EMPTY_STRING(zv); + } + break; case T_METHOD_C: /* Detect whether we are directly inside a class (e.g. a class constant) and treat * this as not being inside a function. */ @@ -10395,6 +10812,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: case ZEND_AST_STATIC_CALL: + case ZEND_AST_PARENT_PROPERTY_HOOK_CALL: zend_compile_var(result, ast, BP_VAR_R, 0); return; case ZEND_AST_ASSIGN: @@ -10544,6 +10962,9 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty case ZEND_AST_CALL: zend_compile_call(result, ast, type); return NULL; + case ZEND_AST_PARENT_PROPERTY_HOOK_CALL: + zend_compile_parent_property_hook_call(result, ast, type); + return NULL; case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: zend_compile_method_call(result, ast, type); @@ -10597,7 +11018,7 @@ static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t } /* }}} */ -static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ +static void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ { zend_ast *ast = *ast_ptr; zval result; @@ -10606,25 +11027,10 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ return; } - /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ - if (ast->kind == ZEND_AST_DIM - && (ast->attr & ZEND_DIM_IS) - && ast->child[0]->kind == ZEND_AST_DIM) { - ast->child[0]->attr |= ZEND_DIM_IS; - } - - /* We don't want to evaluate the class name of ZEND_AST_CLASS_NAME nodes. We need to be able to - * differenciate between literal class names and expressions that evaluate to strings. Strings - * are not actually allowed in ::class expressions. - * - * ZEND_AST_COALESCE and ZEND_AST_CONDITIONAL will manually evaluate only the children for the - * taken paths. */ - if (ast->kind != ZEND_AST_CLASS_NAME && ast->kind != ZEND_AST_COALESCE && ast->kind != ZEND_AST_CONDITIONAL) { - zend_ast_apply(ast, zend_eval_const_expr_inner, ctx); - } - switch (ast->kind) { case ZEND_AST_BINARY_OP: + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); if (ast->child[0]->kind != ZEND_AST_ZVAL || ast->child[1]->kind != ZEND_AST_ZVAL) { return; } @@ -10637,6 +11043,8 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ break; case ZEND_AST_GREATER: case ZEND_AST_GREATER_EQUAL: + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); if (ast->child[0]->kind != ZEND_AST_ZVAL || ast->child[1]->kind != ZEND_AST_ZVAL) { return; } @@ -10648,6 +11056,8 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ case ZEND_AST_OR: { bool child0_is_true, child1_is_true; + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); if (ast->child[0]->kind != ZEND_AST_ZVAL) { return; } @@ -10671,6 +11081,7 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ break; } case ZEND_AST_UNARY_OP: + zend_eval_const_expr(&ast->child[0]); if (ast->child[0]->kind != ZEND_AST_ZVAL) { return; } @@ -10681,6 +11092,7 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ break; case ZEND_AST_UNARY_PLUS: case ZEND_AST_UNARY_MINUS: + zend_eval_const_expr(&ast->child[0]); if (ast->child[0]->kind != ZEND_AST_ZVAL) { return; } @@ -10751,6 +11163,13 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ zend_error(E_COMPILE_ERROR, "Array and string offset access syntax with curly braces is no longer supported"); } + /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ + if ((ast->attr & ZEND_DIM_IS) && ast->child[0]->kind == ZEND_AST_DIM) { + ast->child[0]->attr |= ZEND_DIM_IS; + } + + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); if (ast->child[0]->kind != ZEND_AST_ZVAL || ast->child[1]->kind != ZEND_AST_ZVAL) { return; } @@ -10828,6 +11247,9 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ zend_ast *name_ast; zend_string *resolved_name; + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); + if (UNEXPECTED(ast->child[1]->kind != ZEND_AST_ZVAL || Z_TYPE_P(zend_ast_get_zval(ast->child[1])) != IS_STRING)) { return; @@ -10857,6 +11279,33 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ } break; } + // TODO: We should probably use zend_ast_apply to recursively walk nodes without + // special handling. It is required that all nodes that are part of a const expr + // are visited. Probably we should be distinguishing evaluation of const expr and + // normal exprs here. + case ZEND_AST_ARG_LIST: + { + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + zend_eval_const_expr(&list->child[i]); + } + return; + } + case ZEND_AST_NEW: + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); + return; + case ZEND_AST_NAMED_ARG: + zend_eval_const_expr(&ast->child[1]); + return; + case ZEND_AST_CONST_ENUM_INIT: + zend_eval_const_expr(&ast->child[2]); + return; + case ZEND_AST_PROP: + case ZEND_AST_NULLSAFE_PROP: + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); + return; default: return; } @@ -10865,9 +11314,3 @@ static void zend_eval_const_expr_inner(zend_ast **ast_ptr, void *ctx) /* {{{ */ *ast_ptr = zend_ast_create_zval(&result); } /* }}} */ - - -static void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ -{ - zend_eval_const_expr_inner(ast_ptr, NULL); -} diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index d474d13e86d42..847f56e71c83c 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -207,14 +207,11 @@ typedef struct _zend_oparray_context { /* Static method or property | | | */ #define ZEND_ACC_STATIC (1 << 4) /* | X | X | */ /* | | | */ -/* Promoted property / parameter | | | */ -#define ZEND_ACC_PROMOTED (1 << 5) /* | | X | X */ -/* | | | */ /* Final class or method | | | */ -#define ZEND_ACC_FINAL (1 << 5) /* X | X | | */ +#define ZEND_ACC_FINAL (1 << 5) /* X | X | X | */ /* | | | */ /* Abstract method | | | */ -#define ZEND_ACC_ABSTRACT (1 << 6) /* X | X | | */ +#define ZEND_ACC_ABSTRACT (1 << 6) /* X | X | X | */ #define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS (1 << 6) /* X | | | */ /* | | | */ /* Readonly property | | | */ @@ -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: 10...) | | | */ +/* =========== | | | */ +/* | | | */ +/* Promoted property / parameter | | | */ +#define ZEND_ACC_PROMOTED (1 << 8) /* | | X | */ +/* | | | */ +/* Virtual property without backing storage | | | */ +#define ZEND_ACC_VIRTUAL (1 << 9) /* | | X | */ +/* | | | */ /* Class Flags (unused: 30,31) | | | */ /* =========== | | | */ /* | | | */ @@ -382,6 +388,16 @@ typedef struct _zend_oparray_context { char *zend_visibility_string(uint32_t fn_flags); +typedef enum { + ZEND_PROPERTY_HOOK_GET = 0, + ZEND_PROPERTY_HOOK_SET = 1, +} zend_property_hook_kind; + +#define ZEND_PROPERTY_HOOK_COUNT 2 +#define ZEND_PROPERTY_HOOK_STRUCT_SIZE (sizeof(zend_function*) * ZEND_PROPERTY_HOOK_COUNT) + +zend_property_hook_kind zend_get_property_hook_kind_from_name(zend_string *name); + typedef struct _zend_property_info { uint32_t offset; /* property offset for object properties or property index for static properties */ @@ -391,6 +407,7 @@ typedef struct _zend_property_info { HashTable *attributes; zend_class_entry *ce; zend_type type; + zend_function **hooks; } zend_property_info; #define OBJ_PROP(obj, offset) \ @@ -816,6 +833,7 @@ typedef enum { ZEND_MODIFIER_TARGET_METHOD, ZEND_MODIFIER_TARGET_CONSTANT, ZEND_MODIFIER_TARGET_CPP, + ZEND_MODIFIER_TARGET_PROPERTY_HOOK, } zend_modifier_target; /* Used during AST construction */ diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 21628f74956bb..770beb7320ddb 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -597,6 +597,7 @@ ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zv ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name) { zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), name); + ZEND_ASSERT(c && "Must be a valid enum case"); ZEND_ASSERT(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE); if (Z_TYPE(c->value) == IS_CONSTANT_AST) { diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index caaedce98a850..af480eac0bb2a 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3216,7 +3216,10 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c } return; } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { GC_DELREF(zobj->properties); @@ -3261,7 +3264,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; } @@ -4343,6 +4346,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o opline->opcode == ZEND_INIT_USER_CALL || opline->opcode == ZEND_INIT_METHOD_CALL || opline->opcode == ZEND_INIT_STATIC_METHOD_CALL || + opline->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL || opline->opcode == ZEND_NEW)) { ZEND_ASSERT(op_num); opline--; @@ -4370,6 +4374,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o case ZEND_INIT_USER_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_NEW: if (level == 0) { ZEND_CALL_NUM_ARGS(call) = 0; @@ -4425,6 +4430,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o case ZEND_INIT_USER_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_NEW: if (level == 0) { do_exit = 1; diff --git a/Zend/zend_gdb.c b/Zend/zend_gdb.c index 02afb6bc6f7bc..82e8182ba822a 100644 --- a/Zend/zend_gdb.c +++ b/Zend/zend_gdb.c @@ -113,7 +113,7 @@ ZEND_API bool zend_gdb_present(void) #if defined(__linux__) /* netbsd while having this procfs part, does not hold the tracer pid */ int fd = open("/proc/self/status", O_RDONLY); - if (fd > 0) { + if (fd >= 0) { char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf) - 1); char *s; diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 8900a5f416f53..af3c807a34ef5 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -89,6 +89,8 @@ struct _zend_compiler_globals { int zend_lineno; zend_op_array *active_op_array; + zend_property_info *active_property_info; + zend_property_hook_kind active_property_hook_kind; HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ diff --git a/Zend/zend_highlight.c b/Zend/zend_highlight.c index 501eed5757133..7bd83c2bcfd8c 100644 --- a/Zend/zend_highlight.c +++ b/Zend/zend_highlight.c @@ -109,6 +109,7 @@ ZEND_API void zend_highlight(zend_syntax_highlighter_ini *syntax_highlighter_ini case T_TRAIT_C: case T_METHOD_C: case T_FUNC_C: + case T_PROPERTY_C: case T_NS_C: case T_CLASS_C: next_color = syntax_highlighter_ini->highlight_default; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 17bbae8335445..0e7f459cec5ad 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -37,12 +37,13 @@ ZEND_API zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, z /* Unresolved means that class declarations that are currently not available are needed to * determine whether the inheritance is valid or not. At runtime UNRESOLVED should be treated * as an ERROR. */ +typedef zend_inheritance_status inheritance_status; + typedef enum { - INHERITANCE_UNRESOLVED = -1, - INHERITANCE_ERROR = 0, - INHERITANCE_WARNING = 1, - INHERITANCE_SUCCESS = 2, -} inheritance_status; + PROP_INVARIANT, + PROP_COVARIANT, + PROP_CONTRAVARIANT, +} prop_variance; static void add_dependency_obligation(zend_class_entry *ce, zend_class_entry *dependency_ce); static void add_compatibility_obligation( @@ -50,10 +51,12 @@ static void add_compatibility_obligation( const zend_function *parent_fn, zend_class_entry *parent_scope); static void add_property_compatibility_obligation( zend_class_entry *ce, const zend_property_info *child_prop, - const zend_property_info *parent_prop); + const zend_property_info *parent_prop, prop_variance variance); static void add_class_constant_compatibility_obligation( zend_class_entry *ce, const zend_class_constant *child_const, const zend_class_constant *parent_const, const zend_string *const_name); +static void add_property_hook_obligation( + zend_class_entry *ce, const zend_property_info *hooked_prop, const zend_function *hook_func); static void ZEND_COLD emit_incompatible_method_error( const zend_function *child, zend_class_entry *child_scope, @@ -620,7 +623,7 @@ static inheritance_status zend_is_intersection_subtype_of_type( return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } -static inheritance_status zend_perform_covariant_type_check( +ZEND_API inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, zend_type fe_type, zend_class_entry *proto_scope, zend_type proto_type) { @@ -1221,7 +1224,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; @@ -1232,10 +1236,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; } @@ -1247,16 +1253,65 @@ inheritance_status property_types_compatible( } static void emit_incompatible_property_error( - const zend_property_info *child, const zend_property_info *parent) { + const zend_property_info *child, const zend_property_info *parent, prop_variance variance) { zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce); zend_error_noreturn(E_COMPILE_ERROR, - "Type of %s::$%s must be %s (as in class %s)", + "Type of %s::$%s must be %s%s (as in class %s)", ZSTR_VAL(child->ce->name), zend_get_unmangled_property_name(child->name), + variance == PROP_INVARIANT ? "" : + variance == PROP_COVARIANT ? "subtype of " : "supertype of ", ZSTR_VAL(type_str), ZSTR_VAL(parent->ce->name)); } +static void inherit_property_hook( + zend_class_entry *ce, zend_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; + } + + if (parent_flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot override final property hook %s::%s()", + ZSTR_VAL(parent->common.scope->name), + ZSTR_VAL(parent->common.function_name)); + } + + /* 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->hooks) { + if (!prop_info->hooks[ZEND_PROPERTY_HOOK_SET]) { + return PROP_COVARIANT; + } + if (!prop_info->hooks[ZEND_PROPERTY_HOOK_GET]) { + return PROP_CONTRAVARIANT; + } + } + return PROP_INVARIANT; +} + static void do_inherit_property(zend_property_info *parent_info, zend_string *key, zend_class_entry *ce) /* {{{ */ { zval *child = zend_hash_find_known_hash(&ce->properties_info, key); @@ -1267,6 +1322,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", @@ -1284,24 +1343,47 @@ 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) && !(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_hooks = parent_info->hooks; + zend_function **child_hooks = child_info->hooks; + if (parent_hooks) { + if (child_hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + inherit_property_hook(ce, &parent_hooks[i], &child_hooks[i]); + } + } else { + if (parent_info->flags & ZEND_ACC_VIRTUAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Non-virtual property %s::$%s must not redeclare virtual property %s::$%s", + ZSTR_VAL(ce->name), ZSTR_VAL(key), + ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key)); + } + ce->num_hooked_props++; + } + } + + prop_variance variance = prop_get_variance(child_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, @@ -1312,6 +1394,16 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } } } else { + zend_function **hooks = parent_info->hooks; + if (hooks) { + ce->num_hooked_props++; + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (hooks[i] && (hooks[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); } } @@ -1471,12 +1563,69 @@ void zend_build_properties_info_table(zend_class_entry *ce) } ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) { - if (prop->ce == ce && (prop->flags & ZEND_ACC_STATIC) == 0) { + if (prop->ce == ce && (prop->flags & ZEND_ACC_STATIC) == 0 + && !(prop->flags & ZEND_ACC_VIRTUAL)) { table[OBJ_PROP_TO_NUM(prop->offset)] = prop; } } ZEND_HASH_FOREACH_END(); } +ZEND_API void zend_verify_hooked_property(zend_class_entry *ce, zend_property_info *prop_info, zend_string *prop_name) +{ + if (!prop_info->hooks) { + return; + } + /* We specified a default value (otherwise offset would be -1), but the virtual flag wasn't + * removed during inheritance. */ + if ((prop_info->flags & ZEND_ACC_VIRTUAL) && prop_info->offset != (uint32_t)-1) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot specify default value for hooked property %s::$%s", ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } + /* If the property turns backed during inheritance and no type and default value are set, we want + * the default value to be null. */ + if (!(prop_info->flags & ZEND_ACC_VIRTUAL) + && !ZEND_TYPE_IS_SET(prop_info->type) + && Z_TYPE(ce->default_properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]) == IS_UNDEF) { + ZVAL_NULL(&ce->default_properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]); + } + if (prop_info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + zend_function *func = prop_info->hooks[i]; + if (func) { + if ((func->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) + && (!(prop_info->flags & ZEND_ACC_VIRTUAL) + || (zend_property_hook_kind)i != ZEND_PROPERTY_HOOK_GET)) { + zend_error_noreturn(E_COMPILE_ERROR, "Only virtual get hooks may return by reference"); + } + } + } + } +} + +ZEND_API ZEND_COLD ZEND_NORETURN void zend_hooked_property_variance_error(const zend_property_info *prop_info) +{ + zend_string *value_param_name = prop_info->hooks[ZEND_PROPERTY_HOOK_SET]->op_array.arg_info[0].name; + zend_error_noreturn(E_COMPILE_ERROR, "Type of parameter $%s of hook %s::$%s::set must be compatible with property type", + ZSTR_VAL(value_param_name), ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name)); +} + +ZEND_API inheritance_status zend_verify_property_hook_variance(const zend_property_info *prop_info, const zend_function *func) +{ + ZEND_ASSERT(prop_info->hooks && prop_info->hooks[ZEND_PROPERTY_HOOK_SET] == func); + + zend_arg_info *value_arg_info = &func->op_array.arg_info[0]; + if (!ZEND_TYPE_IS_SET(value_arg_info->type)) { + return INHERITANCE_SUCCESS; + } + + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + return INHERITANCE_ERROR; + } + + zend_class_entry *ce = prop_info->ce; + return zend_perform_covariant_type_check(ce, prop_info->type, ce, value_arg_info->type); +} + ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, bool checked) /* {{{ */ { zend_property_info *property_info; @@ -1614,7 +1763,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par if (property_info->ce == ce) { if (property_info->flags & ZEND_ACC_STATIC) { property_info->offset += parent_ce->default_static_members_count; - } else { + } else if (property_info->offset != (uint32_t)-1) { property_info->offset += parent_ce->default_properties_count * sizeof(zval); } } @@ -1630,6 +1779,12 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par } ZEND_HASH_FOREACH_END(); } + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, key, property_info) { + if (property_info->ce == ce && property_info->hooks) { + zend_verify_hooked_property(ce, property_info, key); + } + } ZEND_HASH_FOREACH_END(); + if (zend_hash_num_elements(&parent_ce->constants_table)) { zend_class_constant *c; @@ -1767,6 +1922,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_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { do_inherit_iface_constant(key, c, ce, iface); @@ -1776,6 +1932,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); @@ -2422,8 +2586,17 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent bool is_compatible = false; uint32_t flags_mask = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY; + if (colliding_prop->hooks || property_info->hooks) { + zend_error_noreturn(E_COMPILE_ERROR, + "%s and %s define the same hooked property ($%s) in the composition of %s. Conflict resolution between hooked properties is currently not supported. Class was composed", + ZSTR_VAL(find_first_property_definition(ce, traits, i, prop_name, colliding_prop->ce)->name), + ZSTR_VAL(property_info->ce->name), + ZSTR_VAL(prop_name), + ZSTR_VAL(ce->name)); + } + if ((colliding_prop->flags & flags_mask) == (flags & flags_mask) && - property_types_compatible(property_info, colliding_prop) == INHERITANCE_SUCCESS + property_types_compatible(property_info, colliding_prop, PROP_INVARIANT) == INHERITANCE_SUCCESS ) { /* the flags are identical, thus, the properties may be compatible */ zval *op1, *op2; @@ -2464,14 +2637,17 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } /* property not found, so lets add it */ - if (flags & ZEND_ACC_STATIC) { - prop_value = &traits[i]->default_static_members_table[property_info->offset]; - ZEND_ASSERT(Z_TYPE_P(prop_value) != IS_INDIRECT); + 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; @@ -2485,6 +2661,20 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent GC_ADDREF(new_prop->attributes); } } + if (property_info->hooks) { + zend_function **hooks = new_prop->hooks = + zend_arena_alloc(&CG(arena), ZEND_PROPERTY_HOOK_STRUCT_SIZE); + memcpy(hooks, property_info->hooks, ZEND_PROPERTY_HOOK_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (hooks[i]) { + hooks[i] = zend_duplicate_function(hooks[i], ce); + if (hooks[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(); } } @@ -2557,6 +2747,20 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ } } ZEND_HASH_FOREACH_END(); + if (!is_explicit_abstract) { + zend_property_info *prop_info; + ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) { + if (prop_info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + zend_function *fn = prop_info->hooks[i]; + if (fn && (fn->common.fn_flags & ZEND_ACC_ABSTRACT)) { + zend_verify_abstract_class_function(fn, &ai); + } + } + } + } ZEND_HASH_FOREACH_END(); + } + if (ai.cnt) { zend_error_noreturn(E_ERROR, !is_explicit_abstract && can_be_abstract ? "%s %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")" @@ -2580,7 +2784,8 @@ typedef struct { OBLIGATION_DEPENDENCY, OBLIGATION_COMPATIBILITY, OBLIGATION_PROPERTY_COMPATIBILITY, - OBLIGATION_CLASS_CONSTANT_COMPATIBILITY + OBLIGATION_CLASS_CONSTANT_COMPATIBILITY, + OBLIGATION_PROPERTY_HOOK, } type; union { zend_class_entry *dependency_ce; @@ -2595,12 +2800,17 @@ typedef struct { struct { const zend_property_info *parent_prop; const zend_property_info *child_prop; + prop_variance variance; }; struct { const zend_string *const_name; const zend_class_constant *parent_const; const zend_class_constant *child_const; }; + struct { + const zend_property_info *hooked_prop; + const zend_function *hook_func; + }; }; } variance_obligation; @@ -2667,12 +2877,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); } @@ -2688,6 +2899,16 @@ static void add_class_constant_compatibility_obligation( zend_hash_next_index_insert_ptr(obligations, obligation); } +static void add_property_hook_obligation( + zend_class_entry *ce, const zend_property_info *hooked_prop, const zend_function *hook_func) { + HashTable *obligations = get_or_init_obligations_for_class(ce); + variance_obligation *obligation = emalloc(sizeof(variance_obligation)); + obligation->type = OBLIGATION_PROPERTY_HOOK; + obligation->hooked_prop = hooked_prop; + obligation->hook_func = hook_func; + zend_hash_next_index_insert_ptr(obligations, obligation); +} + static void resolve_delayed_variance_obligations(zend_class_entry *ce); static void check_variance_obligation(variance_obligation *obligation) { @@ -2713,17 +2934,22 @@ static void check_variance_obligation(variance_obligation *obligation) { /* Either the compatibility check was successful or only threw a warning. */ } else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) { inheritance_status status = - property_types_compatible(obligation->parent_prop, obligation->child_prop); + property_types_compatible(obligation->parent_prop, obligation->child_prop, obligation->variance); if (status != INHERITANCE_SUCCESS) { - emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); + emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop, obligation->variance); } - } else { - ZEND_ASSERT(obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY); + } else if (obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY) { inheritance_status status = class_constant_types_compatible(obligation->parent_const, obligation->child_const); if (status != INHERITANCE_SUCCESS) { emit_incompatible_class_constant_error(obligation->child_const, obligation->parent_const, obligation->const_name); } + } else { + ZEND_ASSERT(obligation->type == OBLIGATION_PROPERTY_HOOK); + inheritance_status status = zend_verify_property_hook_variance(obligation->hooked_prop, obligation->hook_func); + if (status != INHERITANCE_SUCCESS) { + zend_hooked_property_variance_error(obligation->hooked_prop); + } } } @@ -2791,6 +3017,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; @@ -2828,19 +3082,8 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) p = ce->function_table.arData; end = p + ce->function_table.nNumUsed; for (; p != end; p++) { - zend_op_array *op_array, *new_op_array; - - op_array = Z_PTR(p->val); - ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); - ZEND_ASSERT(op_array->scope == pce); - ZEND_ASSERT(op_array->prototype == NULL); - new_op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); - Z_PTR(p->val) = new_op_array; - memcpy(new_op_array, op_array, sizeof(zend_op_array)); - new_op_array->fn_flags &= ~ZEND_ACC_IMMUTABLE; - new_op_array->scope = ce; - ZEND_MAP_PTR_INIT(new_op_array->run_time_cache, NULL); - ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, NULL); + zend_op_array *op_array = Z_PTR(p->val); + zend_op_array *new_op_array = Z_PTR(p->val) = zend_lazy_method_load(op_array, ce, pce); zend_update_inherited_handler(constructor); zend_update_inherited_handler(destructor); @@ -2896,6 +3139,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->hooks) { + new_prop_info->hooks = zend_arena_alloc(&CG(arena), ZEND_PROPERTY_HOOK_STRUCT_SIZE); + memcpy(new_prop_info->hooks, prop_info->hooks, ZEND_PROPERTY_HOOK_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (new_prop_info->hooks[i]) { + new_prop_info->hooks[i] = (zend_function *) zend_lazy_method_load( + (zend_op_array *) new_prop_info->hooks[i], ce, pce); + } + } + } } } @@ -3100,6 +3353,25 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string if (ce->ce_flags & ZEND_ACC_ENUM) { zend_verify_enum(ce); } + if (ce->num_prop_hooks_variance_checks) { + zend_property_info *prop_info; + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, key, prop_info) { + if (prop_info->ce == ce && prop_info->hooks && prop_info->hooks[ZEND_PROPERTY_HOOK_SET]) { + switch (zend_verify_property_hook_variance(prop_info, prop_info->hooks[ZEND_PROPERTY_HOOK_SET])) { + case INHERITANCE_SUCCESS: + break; + case INHERITANCE_ERROR: + zend_hooked_property_variance_error(prop_info); + break; + case INHERITANCE_UNRESOLVED: + add_property_hook_obligation(ce, prop_info, prop_info->hooks[ZEND_PROPERTY_HOOK_SET]); + break; + case INHERITANCE_WARNING: + ZEND_UNREACHABLE(); + } + } + } ZEND_HASH_FOREACH_END(); + } /* Normally Stringable is added during compilation. However, if it is imported from a trait, * we need to explicitly add the interface here. */ @@ -3217,7 +3489,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e if (zv) { zend_property_info *child_info = Z_PTR_P(zv); if (ZEND_TYPE_IS_SET(child_info->type)) { - inheritance_status status = property_types_compatible(parent_info, child_info); + inheritance_status status = property_types_compatible(parent_info, child_info, prop_get_variance(parent_info)); ZEND_ASSERT(status != INHERITANCE_WARNING); if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { return status; diff --git a/Zend/zend_inheritance.h b/Zend/zend_inheritance.h index 1ecd6017f1506..09d4872d60d05 100644 --- a/Zend/zend_inheritance.h +++ b/Zend/zend_inheritance.h @@ -21,6 +21,7 @@ #define ZEND_INHERITANCE_H #include "zend.h" +#include "zend_compile.h" BEGIN_EXTERN_C() @@ -40,6 +41,17 @@ ZEND_API zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_ ZEND_API extern zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces); ZEND_API extern zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, zend_class_entry *proto, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, HashTable *dependencies); +typedef enum { + INHERITANCE_UNRESOLVED = -1, + INHERITANCE_ERROR = 0, + INHERITANCE_WARNING = 1, + INHERITANCE_SUCCESS = 2, +} zend_inheritance_status; + +ZEND_API zend_inheritance_status zend_verify_property_hook_variance(const zend_property_info *prop_info, const zend_function *func); +ZEND_API ZEND_COLD ZEND_NORETURN void zend_hooked_property_variance_error(const zend_property_info *prop_info); +ZEND_API void zend_verify_hooked_property(zend_class_entry *ce, zend_property_info *prop_info, zend_string *prop_name); + END_EXTERN_C() #endif diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 298eaf95ad055..3f784730c9599 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -98,6 +98,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_CONSTANT_ENCAPSED_STRING "quoted string" %token T_STRING_VARNAME "variable name" %token T_NUM_STRING "number" +%token T_PARENT_PROPERTY_HOOK_NAME "parent property hook name" %token T_INCLUDE "'include'" %token T_INCLUDE_ONCE "'include_once'" @@ -177,6 +178,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_TRAIT_C "'__TRAIT__'" %token T_METHOD_C "'__METHOD__'" %token T_FUNC_C "'__FUNCTION__'" +%token T_PROPERTY_C "'__PROPERTY__'" %token T_NS_C "'__NAMESPACE__'" %token END 0 "end of file" @@ -279,8 +281,10 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list %type enum_declaration_statement enum_backing_type enum_case enum_case_expr %type function_name non_empty_member_modifiers +%type property_hook property_hook_list hooked_property optional_property_hook_list property_hook_body +%type optional_parameter_list -%type returns_ref function fn is_reference is_variadic property_modifiers +%type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers %type class_modifiers class_modifier anonymous_class_modifiers anonymous_class_modifiers_optional use_type backup_fn_flags @@ -303,6 +307,7 @@ reserved_non_modifiers: | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM + | T_PROPERTY_C ; semi_reserved: @@ -801,15 +806,20 @@ optional_cpp_modifiers: if (!$$) { YYERROR; } } ; +optional_property_hook_list: + %empty { $$ = NULL; } + | '{' property_hook_list '}' { $$ = $2; } +; + parameter: optional_cpp_modifiers optional_type_without_static - is_reference is_variadic T_VARIABLE backup_doc_comment + is_reference is_variadic T_VARIABLE backup_doc_comment optional_property_hook_list { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, NULL, - NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); } + NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL, $7); } | optional_cpp_modifiers optional_type_without_static - is_reference is_variadic T_VARIABLE backup_doc_comment '=' expr + is_reference is_variadic T_VARIABLE backup_doc_comment '=' expr optional_property_hook_list { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, $8, - NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); } + NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL, $9); } ; optional_type_without_static: @@ -939,6 +949,9 @@ attributed_class_statement: property_modifiers optional_type_without_static property_list ';' { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, $3, NULL); $$->attr = $1; } + | property_modifiers optional_type_without_static hooked_property + { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, zend_ast_create_list(1, ZEND_AST_PROP_DECL, $3), NULL); + $$->attr = $1; } | class_const_modifiers T_CONST class_const_list ';' { $$ = zend_ast_create(ZEND_AST_CLASS_CONST_GROUP, $3, NULL, NULL); $$->attr = $1; } @@ -1072,9 +1085,58 @@ property_list: property: T_VARIABLE backup_doc_comment - { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, NULL, ($2 ? zend_ast_create_zval_from_str($2) : NULL)); } + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, NULL, ($2 ? zend_ast_create_zval_from_str($2) : NULL), NULL); } | T_VARIABLE '=' expr backup_doc_comment - { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL)); } + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); } +; + +hooked_property: + T_VARIABLE backup_doc_comment '{' property_hook_list '}' + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, NULL, ($2 ? zend_ast_create_zval_from_str($2) : NULL), $4); } + | T_VARIABLE '=' expr backup_doc_comment '{' property_hook_list '}' + { $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), $6); } + | T_VARIABLE T_DOUBLE_ARROW expr backup_doc_comment ';' { + zend_ast *return_ast = zend_ast_create_list(1, ZEND_AST_STMT_LIST, zend_ast_create(ZEND_AST_RETURN, $3)); + zend_ast *get_ast = zend_ast_create_decl(ZEND_AST_PROPERTY_HOOK, 0, 0, NULL, ZSTR_INIT_LITERAL("get", 0), NULL, NULL, return_ast, NULL, NULL); + $$ = zend_ast_create(ZEND_AST_PROP_ELEM, $1, NULL, ($4 ? zend_ast_create_zval_from_str($4) : NULL), zend_ast_create_list(1, ZEND_AST_STMT_LIST, get_ast)); + } +; + +property_hook_list: + %empty { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); } + | property_hook_list property_hook { $$ = zend_ast_list_add($1, $2); } + | property_hook_list attributes property_hook { + $$ = zend_ast_list_add($1, zend_ast_with_attributes($3, $2)); + } +; + +property_hook_modifiers: + %empty { $$ = 0; } + | non_empty_member_modifiers + { $$ = zend_modifier_list_to_flags(ZEND_MODIFIER_TARGET_PROPERTY_HOOK, $1); + if (!$$) { YYERROR; } } +; + +property_hook: + property_hook_modifiers returns_ref T_STRING + backup_doc_comment { $$ = CG(zend_lineno); } + optional_parameter_list property_hook_body + { $$ = zend_ast_create_decl( + ZEND_AST_PROPERTY_HOOK, $1 | $2, $5, $4, zend_ast_get_str($3), + $6, NULL, $7, NULL, NULL); } +; + +property_hook_body: + ';' { $$ = NULL; } + | '{' inner_statement_list '}' { $$ = $2; } + | T_DOUBLE_ARROW expr ';' + { $$ = zend_ast_create_list(1, ZEND_AST_STMT_LIST, + zend_ast_create(ZEND_AST_RETURN, $2)); } +; + +optional_parameter_list: + %empty { $$ = NULL; } + | '(' parameter_list ')' { $$ = $2; } ; class_const_list: @@ -1328,6 +1390,8 @@ function_call: $$ = zend_ast_create(ZEND_AST_CALL, $1, $3); $$->lineno = $2; } + | T_PARENT_PROPERTY_HOOK_NAME argument_list + { $$ = zend_ast_create(ZEND_AST_PARENT_PROPERTY_HOOK_CALL, $1, $2); } ; class_name: @@ -1389,6 +1453,7 @@ constant: | T_TRAIT_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_TRAIT_C); } | T_METHOD_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_METHOD_C); } | T_FUNC_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_FUNC_C); } + | T_PROPERTY_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_PROPERTY_C); } | T_NS_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_NS_C); } | T_CLASS_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_CLASS_C); } ; @@ -1436,8 +1501,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 @@ -1474,8 +1537,6 @@ new_variable: { $$ = zend_ast_create(ZEND_AST_VAR, $1); } | new_variable '[' optional_expr ']' { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } - | new_variable '{' expr '}' - { $$ = zend_ast_create_ex(ZEND_AST_DIM, ZEND_DIM_ALTERNATIVE_SYNTAX, $1, $3); } | new_variable T_OBJECT_OPERATOR property_name { $$ = zend_ast_create(ZEND_AST_PROP, $1, $3); } | new_variable T_NULLSAFE_OBJECT_OPERATOR property_name diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 054ed7bdc1ef6..e2bc25b81e6fb 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -2194,6 +2194,10 @@ string: RETURN_TOKEN_WITH_IDENT(T_FUNC_C); } +"__PROPERTY__" { + RETURN_TOKEN_WITH_IDENT(T_PROPERTY_C); +} + "__METHOD__" { RETURN_TOKEN_WITH_IDENT(T_METHOD_C); } @@ -2368,6 +2372,10 @@ inline_char_handler: RETURN_TOKEN_WITH_STR(T_NAME_RELATIVE, sizeof("namespace\\") - 1); } +"parent::$"{LABEL}"::"("get"|"set") { + RETURN_TOKEN_WITH_STR(T_PARENT_PROPERTY_HOOK_NAME, 0); +} + {LABEL}("\\"{LABEL})+ { RETURN_TOKEN_WITH_STR(T_NAME_QUALIFIED, 0); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index b18d0f46f80a6..53ac5831e4d69 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -30,16 +30,19 @@ #include "zend_closures.h" #include "zend_compile.h" #include "zend_hash.h" +#include "zend_property_hooks.h" #define DEBUG_OBJECT_HANDLERS 0 #define ZEND_WRONG_PROPERTY_OFFSET 0 +#define ZEND_HOOKED_PROPERTY_OFFSET 1 /* guard flags */ #define IN_GET (1<<0) #define IN_SET (1<<1) #define IN_UNSET (1<<2) #define IN_ISSET (1<<3) +#define IN_HOOK (1<<4) /* __X accessors explanation: @@ -395,6 +398,15 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c return ZEND_DYNAMIC_PROPERTY_OFFSET; } + if (property_info->hooks) { + *info_ptr = property_info; + if (cache_slot) { + CACHE_POLYMORPHIC_PTR_EX(cache_slot, ce, (void*)ZEND_HOOKED_PROPERTY_OFFSET); + CACHE_PTR_EX(cache_slot + 2, property_info); + } + return ZEND_HOOKED_PROPERTY_OFFSET; + } + offset = property_info->offset; if (EXPECTED(!ZEND_TYPE_IS_SET(property_info->type))) { property_info = NULL; @@ -589,11 +601,18 @@ ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *membe } /* }}} */ +ZEND_COLD static void zend_typed_property_uninitialized_access(const zend_property_info *prop_info, zend_string *name) +{ + zend_throw_error(NULL, "Typed property %s::$%s must not be accessed before initialization", + ZSTR_VAL(prop_info->ce->name), + ZSTR_VAL(name)); +} + ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int type, void **cache_slot, zval *rv) /* {{{ */ { zval *retval; uintptr_t property_offset; - const zend_property_info *prop_info = NULL; + zend_property_info *prop_info = NULL; uint32_t *guard = NULL; zend_string *tmp_name = NULL; @@ -602,8 +621,9 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int #endif /* make zend_get_property_info silent if we have getter - we may want to use it */ - property_offset = zend_get_property_offset(zobj->ce, name, (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot, &prop_info); + property_offset = zend_get_property_offset(zobj->ce, name, (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot, (const zend_property_info **) &prop_info); +try_again: if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { retval = OBJ_PROP(zobj, property_offset); if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) { @@ -666,6 +686,74 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto exit; } } + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + if (!get) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Property %s::$%s is write-only", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(uninitialized_zval); + } else { + /* Cache the fact that this hook has trivial read. This only applies to + * BP_VAR_R and BP_VAR_IS fetches. */ + ZEND_SET_PROPERTY_HOOK_SIMPLE_READ(cache_slot); + + retval = OBJ_PROP(zobj, prop_info->offset); + if (UNEXPECTED(Z_TYPE_P(retval) == IS_UNDEF)) { + /* As hooked properties can't be unset, the only way to end up with an undef + * value is via an uninitialized property. */ + ZEND_ASSERT(Z_PROP_FLAG_P(retval) == IS_PROP_UNINIT); + goto uninit_error; + } + + if (UNEXPECTED(type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)) { + if (UNEXPECTED(Z_TYPE_P(retval) != IS_OBJECT)) { + zend_throw_error(NULL, "Cannot acquire reference to hooked property %s::$%s", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + goto exit; + } + ZVAL_COPY(rv, retval); + retval = rv; + } + goto exit; + } + } + + guard = zend_get_property_guard(zobj, name); + if (UNEXPECTED((*guard) & IN_HOOK)) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Must not read from virtual property %s::$%s", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return &EG(uninitialized_zval); + } + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + + GC_ADDREF(zobj); + *guard |= IN_HOOK; + zend_call_known_instance_method_with_0_params(get, zobj, rv); + *guard &= ~IN_HOOK; + + 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_throw_error(NULL, "Cannot acquire reference to hooked property %s::$%s", + 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; } else if (UNEXPECTED(EG(exception))) { retval = &EG(uninitialized_zval); goto exit; @@ -734,7 +822,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto exit; } else if (UNEXPECTED(IS_WRONG_PROPERTY_OFFSET(property_offset))) { /* Trigger the correct error */ - zend_get_property_offset(zobj->ce, name, 0, NULL, &prop_info); + zend_wrong_offset(zobj->ce, name); ZEND_ASSERT(EG(exception)); retval = &EG(uninitialized_zval); goto exit; @@ -744,9 +832,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", - ZSTR_VAL(prop_info->ce->name), - ZSTR_VAL(name)); + zend_typed_property_uninitialized_access(prop_info, name); } else { zend_error(E_WARNING, "Undefined property: %s::$%s", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); } @@ -801,11 +887,12 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva { zval *variable_ptr, tmp; uintptr_t property_offset; - const zend_property_info *prop_info = NULL; + zend_property_info *prop_info = NULL; ZEND_ASSERT(!Z_ISREF_P(value)); - property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info); + property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, (const zend_property_info **) &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) { @@ -882,6 +969,47 @@ found:; goto found; } } + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + zend_function *set = prop_info->hooks[ZEND_PROPERTY_HOOK_SET]; + + if (!set && prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Property %s::$%s is read-only", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + variable_ptr = &EG(error_zval); + goto exit; + } + + if (!set) { + ZEND_ASSERT(!(prop_info->flags & ZEND_ACC_VIRTUAL)); + ZEND_SET_PROPERTY_HOOK_SIMPLE_WRITE(cache_slot); + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + + uint32_t *guard = zend_get_property_guard(zobj, name); + if (UNEXPECTED((*guard) & IN_HOOK)) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Must not write to virtual property %s::$%s", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + variable_ptr = &EG(error_zval); + goto exit; + } + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + GC_ADDREF(zobj); + (*guard) |= IN_HOOK; + zend_call_known_instance_method_with_1_params(set, zobj, NULL, value); + (*guard) &= ~IN_HOOK; + OBJ_RELEASE(zobj); + + variable_ptr = value; + goto exit; } else if (UNEXPECTED(EG(exception))) { variable_ptr = &EG(error_zval); goto exit; @@ -1096,10 +1224,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam UNEXPECTED(prop_info && (Z_PROP_FLAG_P(retval) & IS_PROP_UNINIT))) { if (UNEXPECTED(type == BP_VAR_RW || type == BP_VAR_R)) { if (UNEXPECTED(prop_info)) { - zend_throw_error(NULL, - "Typed property %s::$%s must not be accessed before initialization", - ZSTR_VAL(prop_info->ce->name), - ZSTR_VAL(name)); + zend_typed_property_uninitialized_access(prop_info, name); retval = &EG(error_zval); } else { ZVAL_NULL(retval); @@ -1152,7 +1277,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_HOOKED_PROPERTY_OFFSET(property_offset) && zobj->ce->__get == NULL) { retval = &EG(error_zval); } @@ -1215,6 +1340,10 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void if (EXPECTED(zend_hash_del(zobj->properties, name) != FAILURE)) { return; } + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + zend_throw_error(NULL, "Cannot unset hooked property %s::$%s", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return; } else if (UNEXPECTED(EG(exception))) { return; } @@ -1364,6 +1493,93 @@ ZEND_API zend_function *zend_get_call_trampoline_func(const zend_class_entry *ce } /* }}} */ +static ZEND_FUNCTION(zend_parent_hook_get_trampoline) +{ + zend_parent_hook_call_info *parent_hook_call_info = Z_PTR_P(ZEND_THIS); + zend_object *obj = parent_hook_call_info->object; + zend_string *prop_name = parent_hook_call_info->property; + + if (UNEXPECTED(ZEND_NUM_ARGS() != 0)) { + zend_wrong_parameters_none_error(); + goto clean; + } + + zval rv; + zval *retval = obj->handlers->read_property(obj, prop_name, BP_VAR_R, NULL, &rv); + if (retval == &rv) { + RETVAL_COPY_VALUE(retval); + } else { + RETVAL_COPY(retval); + } + +clean: + zend_free_trampoline(EX(func)); + EX(func) = NULL; + efree(parent_hook_call_info); +} + +static ZEND_FUNCTION(zend_parent_hook_set_trampoline) +{ + zend_parent_hook_call_info *parent_hook_call_info = Z_PTR_P(ZEND_THIS); + zend_object *obj = parent_hook_call_info->object; + zend_string *prop_name = parent_hook_call_info->property; + + zval *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END_EX(goto clean); + + zval *retval = obj->handlers->write_property(obj, prop_name, value, NULL); + RETVAL_COPY(retval); + +clean: + zend_free_trampoline(EX(func)); + EX(func) = NULL; + efree(parent_hook_call_info); +} + +static zend_result zend_property_hook_trampoline(zend_function **fptr_ptr, zend_string *name, uint32_t args, zif_handler handler) +{ + zend_function *func; + if (EXPECTED(EG(trampoline).common.function_name == NULL)) { + func = &EG(trampoline); + } else { + func = ecalloc(sizeof(zend_internal_function), 1); + } + func->type = ZEND_INTERNAL_FUNCTION; + func->common.arg_flags[0] = 0; + func->common.arg_flags[1] = 0; + func->common.arg_flags[2] = 0; + func->common.fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE; + func->common.function_name = name; + /* set to 0 to avoid arg_info[] allocation, because all values are passed by value anyway */ + func->common.num_args = args; + func->common.required_num_args = args; + func->common.scope = NULL; + func->common.prototype = NULL; + func->common.arg_info = NULL; + func->internal_function.handler = handler; + func->internal_function.module = NULL; + + func->internal_function.reserved[0] = NULL; + func->internal_function.reserved[1] = NULL; + + *fptr_ptr = func; + + return SUCCESS; +} + +ZEND_API zend_result zend_property_hook_get_trampoline(zend_function **fptr_ptr) +{ + return zend_property_hook_trampoline(fptr_ptr, ZSTR_KNOWN(ZEND_STR_GET), 0, ZEND_FN(zend_parent_hook_get_trampoline)); +} + +ZEND_API zend_result zend_property_hook_set_trampoline(zend_function **fptr_ptr) +{ + return zend_property_hook_trampoline(fptr_ptr, ZSTR_KNOWN(ZEND_STR_SET), 1, ZEND_FN(zend_parent_hook_set_trampoline)); +} + static zend_always_inline zend_function *zend_get_user_call_function(zend_class_entry *ce, zend_string *method_name) /* {{{ */ { return zend_get_call_trampoline_func(ce, method_name, 0); @@ -1805,11 +2021,12 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has int result; zval *value = NULL; uintptr_t property_offset; - const zend_property_info *prop_info = NULL; + zend_property_info *prop_info = NULL; zend_string *tmp_name = NULL; - property_offset = zend_get_property_offset(zobj->ce, name, 1, cache_slot, &prop_info); + property_offset = zend_get_property_offset(zobj->ce, name, 1, cache_slot, (const zend_property_info **) &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) { @@ -1858,6 +2075,53 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has goto exit; } } + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + if (!get) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Property %s::$%s is write-only", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return 0; + } else { + property_offset = prop_info->offset; + goto try_again; + } + } + + if (has_set_exists == ZEND_PROPERTY_EXISTS) { + return 1; + } + + uint32_t *guard = zend_get_property_guard(zobj, name); + if (UNEXPECTED(*guard & IN_HOOK)) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + zend_throw_error(NULL, "Must not read from virtual property %s::$%s", + ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name)); + return 0; + } + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + + zval rv; + GC_ADDREF(zobj); + *guard |= IN_HOOK; + zend_call_known_instance_method_with_0_params(get, zobj, &rv); + *guard &= ~IN_HOOK; + 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; } else if (UNEXPECTED(EG(exception))) { result = 0; goto exit; @@ -1974,10 +2238,15 @@ ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purp return ht; } ZEND_FALLTHROUGH; + case ZEND_PROP_PURPOSE_JSON: + case ZEND_PROP_PURPOSE_GET_OJBECT_VARS: + if (obj->ce->num_hooked_props) { + return zend_hooked_object_build_properties(obj); + } + ZEND_FALLTHROUGH; case ZEND_PROP_PURPOSE_ARRAY_CAST: case ZEND_PROP_PURPOSE_SERIALIZE: case ZEND_PROP_PURPOSE_VAR_EXPORT: - case ZEND_PROP_PURPOSE_JSON: ht = obj->handlers->get_properties(obj); if (ht) { GC_TRY_ADDREF(ht); diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 475ba6263825d..af67545c8c20b 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -23,16 +23,46 @@ #include struct _zend_property_info; +struct _zend_object; +struct _zend_string; + +typedef struct { + struct _zend_object *object; + struct _zend_string *property; +} zend_parent_hook_call_info; #define ZEND_WRONG_PROPERTY_INFO \ ((struct _zend_property_info*)((intptr_t)-1)) #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_HOOKED_PROPERTY_OFFSET(offset) \ + ((intptr_t)(offset) > 0 && (intptr_t)(offset) < 8) #define IS_DYNAMIC_PROPERTY_OFFSET(offset) ((intptr_t)(offset) < 0) +#define ZEND_PROPERTY_HOOK_SIMPLE_READ_BIT 2u +#define ZEND_PROPERTY_HOOK_SIMPLE_WRITE_BIT 4u +#define ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(offset) \ + (((offset) & ZEND_PROPERTY_HOOK_SIMPLE_READ_BIT) != 0) +#define ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(offset) \ + (((offset) & ZEND_PROPERTY_HOOK_SIMPLE_WRITE_BIT) != 0) +#define ZEND_SET_PROPERTY_HOOK_SIMPLE_READ(cache_slot) \ + do { \ + void **__cache_slot = (cache_slot); \ + if (__cache_slot) { \ + CACHE_PTR_EX(__cache_slot + 1, (void*)((uintptr_t)CACHED_PTR_EX(__cache_slot + 1) | ZEND_PROPERTY_HOOK_SIMPLE_READ_BIT)); \ + } \ + } while (0) +#define ZEND_SET_PROPERTY_HOOK_SIMPLE_WRITE(cache_slot) \ + do { \ + void **__cache_slot = (cache_slot); \ + if (__cache_slot) { \ + CACHE_PTR_EX(__cache_slot + 1, (void*)((uintptr_t)CACHED_PTR_EX(__cache_slot + 1) | ZEND_PROPERTY_HOOK_SIMPLE_WRITE_BIT)); \ + } \ + } while (0) + #define 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))) @@ -98,6 +128,8 @@ typedef enum _zend_prop_purpose { ZEND_PROP_PURPOSE_VAR_EXPORT, /* Used for json_encode(). */ ZEND_PROP_PURPOSE_JSON, + /* Used for get_object_vars(). */ + ZEND_PROP_PURPOSE_GET_OJBECT_VARS, /* Dummy member to ensure that "default" is specified. */ _ZEND_PROP_PURPOSE_NON_EXHAUSTIVE_ENUM } zend_prop_purpose; @@ -249,6 +281,9 @@ ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purp * consumers of the get_properties_for API. */ ZEND_API HashTable *zend_get_properties_for(zval *obj, zend_prop_purpose purpose); +ZEND_API zend_result zend_property_hook_get_trampoline(zend_function **fptr_ptr); +ZEND_API zend_result zend_property_hook_set_trampoline(zend_function **fptr_ptr); + #define zend_release_properties(ht) do { \ if ((ht) && !(GC_FLAGS(ht) & GC_IMMUTABLE) && !GC_DELREF(ht)) { \ zend_array_destroy(ht); \ diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index dc968bc395303..1dc848ce8b78f 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -399,6 +399,13 @@ ZEND_API void destroy_zend_class(zval *zv) zend_hash_release(prop_info->attributes); } zend_type_release(prop_info->type, /* persistent */ 0); + if (prop_info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop_info->hooks[i]) { + destroy_op_array(&prop_info->hooks[i]->op_array); + } + } + } } } ZEND_HASH_FOREACH_END(); zend_hash_destroy(&ce->properties_info); @@ -782,6 +789,7 @@ static void emit_live_range( case ZEND_INIT_USER_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: case ZEND_NEW: level++; break; diff --git a/Zend/zend_property_hooks.c b/Zend/zend_property_hooks.c new file mode 100644 index 0000000000000..6c4670dfab962 --- /dev/null +++ b/Zend/zend_property_hooks.c @@ -0,0 +1,218 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Ilija Tovilo | + +----------------------------------------------------------------------+ +*/ + +#include "zend.h" +#include "zend_property_hooks.h" + +typedef struct { + zend_object_iterator it; + bool by_ref; + zval properties; + zval current_data; +} zend_hooked_object_iterator; + +static int zend_hooked_object_it_valid(zend_object_iterator *iter); + +ZEND_API zend_array *zend_hooked_object_build_properties(zend_object *zobj) +{ + zend_class_entry *ce = zobj->ce; + zend_array *properties = zend_new_array(ce->default_properties_count); + zend_hash_real_init_mixed(properties); + + zend_property_info *prop_info; + int virtual_property_count = 0; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop_info) { + if (prop_info->flags & ZEND_ACC_STATIC) { + continue; + } + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + virtual_property_count++; + } + if (prop_info->hooks) { + _zend_hash_append_ptr(properties, prop_info->name, prop_info); + } else { + if (UNEXPECTED(Z_TYPE_P(OBJ_PROP(zobj, prop_info->offset)) == IS_UNDEF)) { + HT_FLAGS(properties) |= HASH_FLAG_HAS_EMPTY_IND; + } + _zend_hash_append_ind(properties, prop_info->name, OBJ_PROP(zobj, prop_info->offset)); + } + } ZEND_HASH_FOREACH_END(); + + if (zobj->properties) { + int stored_property_count = zend_array_count(&zobj->ce->properties_info) - virtual_property_count; + zend_string *prop_name; + zval *prop_value; + ZEND_HASH_FOREACH_STR_KEY_VAL_FROM(zobj->properties, prop_name, prop_value, stored_property_count) { + Z_TRY_ADDREF_P(_zend_hash_append(properties, prop_name, prop_value)); + } ZEND_HASH_FOREACH_END(); + } + + return properties; +} + +static void zend_hooked_object_it_get_current_key(zend_object_iterator *iter, zval *key); + +static zend_result zend_hooked_object_it_fetch_current_data(zend_object_iterator *iter) +{ + if (zend_hooked_object_it_valid(iter) != SUCCESS) { + return FAILURE; + } + + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zval_ptr_dtor(&hooked_iter->current_data); + if (EG(exception)) { + return FAILURE; + } + ZVAL_UNDEF(&hooked_iter->current_data); + zend_object *zobj = Z_OBJ_P(&iter->data); + zend_array *properties = Z_ARR(hooked_iter->properties); + zval *property = zend_hash_get_current_data(properties); + bool is_dynamic; + switch (Z_TYPE_P(property)) { + case IS_INDIRECT: + property = Z_INDIRECT_P(property); + ZEND_FALLTHROUGH; + case IS_PTR: + is_dynamic = false; + break; + default: + is_dynamic = true; + } + zval name; + zend_hooked_object_it_get_current_key(iter, &name); + bool accessible = zend_check_property_access(zobj, Z_STR(name), is_dynamic) == SUCCESS; + zval_ptr_dtor_nogc(&name); + if (!accessible) { + return FAILURE; + } + if (Z_TYPE_P(property) == IS_PTR) { + zend_property_info *prop_info = Z_PTR_P(property); + zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; + if (hooked_iter->by_ref + && (get == NULL + || !(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE))) { + zend_throw_error(NULL, "Cannot create reference to property %s::$%s", + ZSTR_VAL(zobj->ce->name), zend_get_unmangled_property_name(prop_info->name)); + return FAILURE; + } + zend_read_property_ex(prop_info->ce, zobj, prop_info->name, /* silent */ true, &hooked_iter->current_data); + } else { + if (hooked_iter->by_ref + && Z_TYPE_P(property) != IS_REFERENCE + && Z_TYPE_P(property) != IS_UNDEF) { + ZVAL_MAKE_REF(property); + } + ZVAL_COPY(&hooked_iter->current_data, property); + } + if (!hooked_iter->by_ref) { + SEPARATE_ZVAL(&hooked_iter->current_data); + } + return SUCCESS; +} + +static void zend_hooked_object_it_dtor(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zval_ptr_dtor(&iter->data); + zval_ptr_dtor(&hooked_iter->properties); + zval_ptr_dtor(&hooked_iter->current_data); +} + +static int zend_hooked_object_it_valid(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zend_array *properties = Z_ARR(hooked_iter->properties); + return zend_hash_has_more_elements(properties); +} + +static zval *zend_hooked_object_it_get_current_data(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + return &hooked_iter->current_data; +} + +static void zend_hooked_object_it_get_current_key(zend_object_iterator *iter, zval *key) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zend_array *properties = Z_ARR(hooked_iter->properties); + zend_hash_get_current_key_zval(properties, key); +} + +static void zend_hooked_object_it_move_forward(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zend_array *properties = Z_ARR(hooked_iter->properties); + zend_result result; + do { + zend_hash_move_forward(properties); + result = zend_hooked_object_it_fetch_current_data(iter); + } while (result == FAILURE + && !EG(exception) + && zend_hooked_object_it_valid(iter) == SUCCESS + && Z_TYPE(hooked_iter->current_data) == IS_UNDEF); +} + +static void zend_hooked_object_it_rewind(zend_object_iterator *iter) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zend_array *properties = Z_ARR(hooked_iter->properties); + zend_hash_internal_pointer_reset(properties); + while (zend_hooked_object_it_fetch_current_data(iter) == FAILURE + && !EG(exception) + && zend_hooked_object_it_valid(iter) == SUCCESS + && Z_TYPE(hooked_iter->current_data) == IS_UNDEF) { + zend_hash_move_forward(properties); + } +} + +static HashTable *zend_hooked_object_it_get_gc(zend_object_iterator *iter, zval **table, int *n) +{ + zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + zend_get_gc_buffer_add_zval(gc_buffer, &iter->data); + zend_get_gc_buffer_add_zval(gc_buffer, &hooked_iter->properties); + zend_get_gc_buffer_add_zval(gc_buffer, &hooked_iter->current_data); + zend_get_gc_buffer_use(gc_buffer, table, n); + return NULL; +} + +static const zend_object_iterator_funcs zend_hooked_object_it_funcs = { + zend_hooked_object_it_dtor, + zend_hooked_object_it_valid, + zend_hooked_object_it_get_current_data, + zend_hooked_object_it_get_current_key, + zend_hooked_object_it_move_forward, + zend_hooked_object_it_rewind, + NULL, + zend_hooked_object_it_get_gc, +}; + +ZEND_API zend_object_iterator *zend_hooked_object_get_iterator(zend_class_entry *ce, zval *object, int by_ref) +{ + zend_hooked_object_iterator *iterator = emalloc(sizeof(zend_hooked_object_iterator)); + zend_iterator_init(&iterator->it); + + ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object)); + iterator->it.funcs = &zend_hooked_object_it_funcs; + iterator->by_ref = by_ref; + zend_array *properties = zend_hooked_object_build_properties(Z_OBJ_P(object)); + ZVAL_ARR(&iterator->properties, properties); + ZVAL_UNDEF(&iterator->current_data); + + return &iterator->it; +} diff --git a/Zend/zend_property_hooks.h b/Zend/zend_property_hooks.h new file mode 100644 index 0000000000000..ae7447a180074 --- /dev/null +++ b/Zend/zend_property_hooks.h @@ -0,0 +1,32 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Ilija Tovilo | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_PROPERTY_HOOKS_H +#define ZEND_PROPERTY_HOOKS_H + +#include "zend.h" +#include "zend_API.h" + +BEGIN_EXTERN_C() + +ZEND_API zend_object_iterator *zend_hooked_object_get_iterator(zend_class_entry *ce, zval *object, int by_ref); +ZEND_API zend_array *zend_hooked_object_build_properties(zend_object *zobj); + +END_EXTERN_C() + +#endif /* ZEND_PROPERTY_HOOKS_H */ diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 1513a19c36070..b063e7858b06d 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -631,6 +631,8 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_COUNT, "count") \ _(ZEND_STR_SENSITIVEPARAMETER, "SensitiveParameter") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ + _(ZEND_STR_GET, "get") \ + _(ZEND_STR_SET, "set") \ typedef enum _zend_known_string_id { diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 7e86b29c6b4f7..ff87bae9f9420 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2068,6 +2068,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) { @@ -2078,7 +2079,15 @@ ZEND_VM_C_LABEL(fetch_obj_r_fast_copy): ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + ZEND_VM_C_GOTO(fetch_obj_r_simple); + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -2243,7 +2252,10 @@ ZEND_VM_C_LABEL(fetch_obj_is_fast_copy): ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); 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); @@ -2422,7 +2434,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)) { @@ -2471,6 +2483,19 @@ ZEND_VM_C_LABEL(fast_assign_obj): } ZEND_VM_C_GOTO(exit_assign_obj); } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + ZEND_VM_C_GOTO(free_and_exit_assign_obj); + } else { + ZEND_VM_C_GOTO(fast_assign_obj); + } + } } } name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); @@ -7141,6 +7166,13 @@ ZEND_VM_HANDLER(126, ZEND_FE_FETCH_RW, VAR, ANY, JMP_ADDR) UNDEF_RESULT(); HANDLE_EXCEPTION(); } + if (UNEXPECTED(prop_info->hooks != NULL)) { + zend_throw_error(NULL, + "Cannot acquire reference to hooked property %s::$%s", + ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(p->key)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } if (ZEND_TYPE_IS_SET(prop_info->type)) { ZVAL_NEW_REF(value, value); ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(value), prop_info); @@ -9507,6 +9539,69 @@ ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED) ZEND_VM_NEXT_OPCODE(); } +ZEND_VM_HANDLER(204, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL, CONST, UNUSED|NUM, NUM) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_class_entry *ce = EX(func)->common.scope; + ZEND_ASSERT(ce); + + zend_class_entry *parent_ce = ce->parent; + if (!parent_ce) { + zend_throw_error(NULL, "Cannot use \"parent\" when current class scope has no parent"); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + zend_string *property_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + zend_property_hook_kind hook_kind = opline->op2.num; + + zend_property_info *prop_info = zend_hash_find_ptr(&parent_ce->properties_info, property_name); + if (!prop_info) { + zend_throw_error(NULL, "Undefined property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + if (prop_info->flags & ZEND_ACC_PRIVATE) { + zend_throw_error(NULL, "Cannot access private property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + zend_function **hooks = prop_info->hooks; + zend_function *hook = hooks ? hooks[hook_kind] : NULL; + + zend_execute_data *call; + if (hook) { + call = zend_vm_stack_push_call_frame( + ZEND_CALL_FUNCTION | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_THIS, + hook, + opline->extended_value, + ZEND_THIS); + } else { + zend_function *fbc; + if (hook_kind == ZEND_PROPERTY_HOOK_GET) { + zend_property_hook_get_trampoline(&fbc); + } else if (hook_kind == ZEND_PROPERTY_HOOK_SET) { + zend_property_hook_set_trampoline(&fbc); + } else { + ZEND_UNREACHABLE(); + } + zend_parent_hook_call_info *hook_call_info = emalloc(sizeof(zend_parent_hook_call_info)); + hook_call_info->object = Z_PTR_P(ZEND_THIS); + hook_call_info->property = property_name; + call = zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS, + fbc, opline->extended_value, hook_call_info); + /* zend_vm_stack_push_call_frame stores this as IS_OBJECT, we need to convert it to IS_PTR. */ + ZVAL_PTR(&call->This, hook_call_info); + } + + call->prev_execute_data = EX(call); + EX(call) = call; + ZEND_VM_NEXT_OPCODE(); +} + ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_JMP, (OP_JMP_ADDR(op, op->op1) > op), ZEND_JMP_FORWARD, JMP_ADDR, ANY) { USE_OPLINE diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 14e3a5aca2a2a..5e9b8d9591083 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -6406,6 +6406,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) { @@ -6416,7 +6417,15 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -6543,7 +6552,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -8903,6 +8915,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) { @@ -8913,7 +8926,15 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -9040,7 +9061,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -11051,6 +11075,69 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSE ZEND_VM_NEXT_OPCODE(); } +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_class_entry *ce = EX(func)->common.scope; + ZEND_ASSERT(ce); + + zend_class_entry *parent_ce = ce->parent; + if (!parent_ce) { + zend_throw_error(NULL, "Cannot use \"parent\" when current class scope has no parent"); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + zend_string *property_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + zend_property_hook_kind hook_kind = opline->op2.num; + + zend_property_info *prop_info = zend_hash_find_ptr(&parent_ce->properties_info, property_name); + if (!prop_info) { + zend_throw_error(NULL, "Undefined property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + if (prop_info->flags & ZEND_ACC_PRIVATE) { + zend_throw_error(NULL, "Cannot access private property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + zend_function **hooks = prop_info->hooks; + zend_function *hook = hooks ? hooks[hook_kind] : NULL; + + zend_execute_data *call; + if (hook) { + call = zend_vm_stack_push_call_frame( + ZEND_CALL_FUNCTION | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_THIS, + hook, + opline->extended_value, + ZEND_THIS); + } else { + zend_function *fbc; + if (hook_kind == ZEND_PROPERTY_HOOK_GET) { + zend_property_hook_get_trampoline(&fbc); + } else if (hook_kind == ZEND_PROPERTY_HOOK_SET) { + zend_property_hook_set_trampoline(&fbc); + } else { + ZEND_UNREACHABLE(); + } + zend_parent_hook_call_info *hook_call_info = emalloc(sizeof(zend_parent_hook_call_info)); + hook_call_info->object = Z_PTR_P(ZEND_THIS); + hook_call_info->property = property_name; + call = zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS, + fbc, opline->extended_value, hook_call_info); + /* zend_vm_stack_push_call_frame stores this as IS_OBJECT, we need to convert it to IS_PTR. */ + ZVAL_PTR(&call->This, hook_call_info); + } + + call->prev_execute_data = EX(call); + EX(call) = call; + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DIV_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -11277,6 +11364,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) { @@ -11287,7 +11375,15 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_ ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -11414,7 +11510,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -15659,6 +15758,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) { @@ -15669,7 +15769,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CONST_ ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -15796,7 +15904,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CONST ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -17104,6 +17215,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) { @@ -17114,7 +17226,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_TMPVAR ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -17241,7 +17361,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_TMPVA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -18438,6 +18561,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) { @@ -18448,7 +18572,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_TMPVAR_CV_HAN ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -18575,7 +18707,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CV_HA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -22431,6 +22566,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_FETCH_RW_SPEC_VAR_HANDLER(Z UNDEF_RESULT(); HANDLE_EXCEPTION(); } + if (UNEXPECTED(prop_info->hooks != NULL)) { + zend_throw_error(NULL, + "Cannot acquire reference to hooked property %s::$%s", + ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(p->key)); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } if (ZEND_TYPE_IS_SET(prop_info->type)) { ZVAL_NEW_REF(value, value); ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(value), prop_info); @@ -23334,7 +23476,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)) { @@ -23383,6 +23525,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23468,7 +23623,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)) { @@ -23517,6 +23672,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23602,7 +23770,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)) { @@ -23651,6 +23819,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -23736,7 +23917,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)) { @@ -23785,6 +23966,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -26238,7 +26432,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)) { @@ -26287,6 +26481,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -26372,7 +26579,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)) { @@ -26421,6 +26628,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -26506,7 +26726,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)) { @@ -26555,6 +26775,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -26640,7 +26873,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)) { @@ -26689,6 +26922,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -30530,7 +30776,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)) { @@ -30579,6 +30825,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -30664,7 +30923,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)) { @@ -30713,6 +30972,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -30798,7 +31070,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)) { @@ -30847,6 +31119,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -30932,7 +31217,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)) { @@ -30981,6 +31266,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -32839,6 +33137,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) { @@ -32849,7 +33148,15 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -33019,7 +33326,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CONST ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -33163,7 +33473,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)) { @@ -33212,6 +33522,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33297,7 +33620,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)) { @@ -33346,6 +33669,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33431,7 +33767,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)) { @@ -33480,6 +33816,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -33565,7 +33914,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)) { @@ -33614,6 +33963,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -34876,6 +35238,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) { @@ -34886,7 +35249,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_TMPVAR ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -35051,7 +35422,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_TMPVA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -35195,7 +35569,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)) { @@ -35244,6 +35618,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -35329,7 +35716,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)) { @@ -35378,6 +35765,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -35463,7 +35863,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)) { @@ -35512,6 +35912,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -35597,7 +36010,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)) { @@ -35646,6 +36059,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -37366,6 +37792,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) { @@ -37376,7 +37803,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_UNUSED_CV_HAN ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -37541,7 +37976,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_UNUSED_CV_HA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -37685,7 +38123,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)) { @@ -37734,6 +38172,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -37819,7 +38270,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)) { @@ -37868,6 +38319,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -37953,7 +38417,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)) { @@ -38002,6 +38466,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -38087,7 +38564,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)) { @@ -38136,6 +38613,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -41633,6 +42123,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) { @@ -41643,7 +42134,15 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -41813,7 +42312,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CONST_HAN ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -41957,7 +42459,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)) { @@ -42006,6 +42508,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -42091,7 +42606,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)) { @@ -42140,6 +42655,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -42225,7 +42753,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)) { @@ -42274,6 +42802,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -42359,7 +42900,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)) { @@ -42408,6 +42949,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); @@ -45477,6 +46031,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) { @@ -45487,7 +46042,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_TMPVAR_HAN ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -45652,7 +46215,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_TMPVAR_HA ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -45796,7 +46362,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)) { @@ -45845,6 +46411,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -45930,7 +46509,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)) { @@ -45979,6 +46558,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -46064,7 +46656,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)) { @@ -46113,6 +46705,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -46198,7 +46803,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)) { @@ -46247,6 +46852,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); @@ -50842,6 +51460,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) { @@ -50852,7 +51471,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_R_SPEC_CV_CV_HANDLER ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_READ(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + prop_offset = prop_info->offset; + goto fetch_obj_r_simple; + } } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -51017,7 +51644,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_CV_CV_HANDLE ZEND_VM_NEXT_OPCODE(); } } + } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { + /* Fall through to read_property for hooks. */ } else if (EXPECTED(zobj->properties != NULL)) { + ZEND_ASSERT(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset)); name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); if (!IS_UNKNOWN_DYNAMIC_PROPERTY_OFFSET(prop_offset)) { uintptr_t idx = ZEND_DECODE_DYN_PROP_OFFSET(prop_offset); @@ -51161,7 +51791,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)) { @@ -51210,6 +51840,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -51295,7 +51938,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)) { @@ -51344,6 +51987,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -51429,7 +52085,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)) { @@ -51478,6 +52134,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -51563,7 +52232,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)) { @@ -51612,6 +52281,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } goto exit_assign_obj; } + } else { + /* Fall through to write_property for hooks. */ + ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset)); + if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) { + zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); + property_val = OBJ_PROP(zobj, prop_info->offset); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + goto free_and_exit_assign_obj; + } else { + goto fast_assign_obj; + } + } } } name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); @@ -55898,6 +56580,7 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_VERIFY_NEVER_TYPE_SPEC_UNUSED_UNUSED_LABEL, (void*)&&ZEND_CALLABLE_CONVERT_SPEC_UNUSED_UNUSED_LABEL, (void*)&&ZEND_BIND_INIT_STATIC_OR_JMP_SPEC_CV_LABEL, + (void*)&&ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_LABEL, (void*)&&ZEND_RECV_NOTYPE_SPEC_LABEL, (void*)&&ZEND_JMP_FORWARD_SPEC_LABEL, (void*)&&ZEND_NULL_LABEL, @@ -58001,6 +58684,10 @@ ZEND_API void execute_ex(zend_execute_data *ex) VM_TRACE(ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED) ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); HYBRID_BREAK(); + HYBRID_CASE(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED): + VM_TRACE(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) + ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + HYBRID_BREAK(); HYBRID_CASE(ZEND_DIV_SPEC_CONST_CV): VM_TRACE(ZEND_DIV_SPEC_CONST_CV) ZEND_DIV_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -64007,6 +64694,7 @@ void zend_vm_init(void) ZEND_VERIFY_NEVER_TYPE_SPEC_UNUSED_UNUSED_HANDLER, ZEND_CALLABLE_CONVERT_SPEC_UNUSED_UNUSED_HANDLER, ZEND_BIND_INIT_STATIC_OR_JMP_SPEC_CV_HANDLER, + ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER, ZEND_RECV_NOTYPE_SPEC_HANDLER, ZEND_JMP_FORWARD_SPEC_HANDLER, ZEND_NULL_HANDLER, @@ -64958,7 +65646,7 @@ void zend_vm_init(void) 1255, 1256 | SPEC_RULE_OP1, 1261 | SPEC_RULE_OP1, - 3471, + 3472, 1266 | SPEC_RULE_OP1, 1271 | SPEC_RULE_OP1, 1276 | SPEC_RULE_OP2, @@ -65117,58 +65805,58 @@ void zend_vm_init(void) 2565, 2566, 2567, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, - 3471, + 2568, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, }; #if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) zend_opcode_handler_funcs = labels; @@ -65341,7 +66029,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2570 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2571 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -65349,7 +66037,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2595 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2596 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -65357,7 +66045,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2620 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2621 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -65368,17 +66056,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2645 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2646 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2670 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2671 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2695 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2696 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_MUL: @@ -65389,17 +66077,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2720 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2721 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2745 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2746 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2770 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2771 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_IDENTICAL: @@ -65410,14 +66098,14 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2795 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2796 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2870 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2871 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3095 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3096 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_IDENTICAL: @@ -65428,14 +66116,14 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2945 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2946 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3020 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3021 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3100 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3101 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_EQUAL: @@ -65446,12 +66134,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2795 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2796 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2870 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2871 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_EQUAL: @@ -65462,12 +66150,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2945 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2946 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3020 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3021 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_SMALLER: @@ -65475,12 +66163,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3105 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3106 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3180 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3181 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_IS_SMALLER_OR_EQUAL: @@ -65488,74 +66176,74 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3255 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3256 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3330 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3331 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_QM_ASSIGN: if (op1_info == MAY_BE_LONG) { - spec = 3417 | SPEC_RULE_OP1; + spec = 3418 | SPEC_RULE_OP1; } else if (op1_info == MAY_BE_DOUBLE) { - spec = 3422 | SPEC_RULE_OP1; + spec = 3423 | SPEC_RULE_OP1; } else if ((op->op1_type == IS_CONST) ? !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)) : (!(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))))) { - spec = 3427 | SPEC_RULE_OP1; + spec = 3428 | SPEC_RULE_OP1; } break; case ZEND_PRE_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3405 | SPEC_RULE_RETVAL; + spec = 3406 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3407 | SPEC_RULE_RETVAL; + spec = 3408 | SPEC_RULE_RETVAL; } break; case ZEND_PRE_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3409 | SPEC_RULE_RETVAL; + spec = 3410 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3411 | SPEC_RULE_RETVAL; + spec = 3412 | SPEC_RULE_RETVAL; } break; case ZEND_POST_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3413; - } else if (op1_info == MAY_BE_LONG) { spec = 3414; + } else if (op1_info == MAY_BE_LONG) { + spec = 3415; } break; case ZEND_POST_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3415; - } else if (op1_info == MAY_BE_LONG) { spec = 3416; + } else if (op1_info == MAY_BE_LONG) { + spec = 3417; } break; case ZEND_JMP: if (OP_JMP_ADDR(op, op->op1) > op) { - spec = 2569; + spec = 2570; } break; case ZEND_RECV: if (op->op2.num == MAY_BE_ANY) { - spec = 2568; + spec = 2569; } break; case ZEND_SEND_VAL: if (op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3467; + spec = 3468; } break; case ZEND_SEND_VAR_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3462 | SPEC_RULE_OP1; + spec = 3463 | SPEC_RULE_OP1; } break; case ZEND_FE_FETCH_R: if (op->op2_type == IS_CV && (op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 3469 | SPEC_RULE_RETVAL; + spec = 3470 | SPEC_RULE_RETVAL; } break; case ZEND_FETCH_DIM_R: @@ -65563,17 +66251,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3432 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 3433 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_SEND_VAL_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3468; + spec = 3469; } break; case ZEND_SEND_VAR: if (op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3457 | SPEC_RULE_OP1; + spec = 3458 | SPEC_RULE_OP1; } break; case ZEND_BW_OR: diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index 97dfeac30cae8..24f9478389475 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -1363,498 +1363,499 @@ _(2565, ZEND_VERIFY_NEVER_TYPE_SPEC_UNUSED_UNUSED) \ _(2566, ZEND_CALLABLE_CONVERT_SPEC_UNUSED_UNUSED) \ _(2567, ZEND_BIND_INIT_STATIC_OR_JMP_SPEC_CV) \ - _(2568, ZEND_RECV_NOTYPE_SPEC) \ - _(2569, ZEND_JMP_FORWARD_SPEC) \ - _(2575, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2576, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2568, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) \ + _(2569, ZEND_RECV_NOTYPE_SPEC) \ + _(2570, ZEND_JMP_FORWARD_SPEC) \ + _(2576, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2577, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2579, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2580, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2581, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2578, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2580, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2581, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2582, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2584, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2590, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2591, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2583, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2585, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2591, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2592, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2594, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2600, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2601, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2593, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2595, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2601, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2602, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2604, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2605, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2606, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2603, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2605, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2606, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2607, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2609, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2615, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2616, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2608, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2610, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2616, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2617, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2619, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2625, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2626, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2618, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2620, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2626, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2627, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2629, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2630, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2631, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2628, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2630, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2631, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2632, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2634, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2640, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2641, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2633, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2635, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2641, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2642, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2644, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2646, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2643, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2645, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2647, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2649, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2650, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2651, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2648, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2650, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2651, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2652, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2654, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2655, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2656, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2653, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2655, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2656, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2657, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2659, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2665, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2666, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2658, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2660, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2666, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2667, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2669, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2671, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2668, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2670, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2672, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2674, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2675, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2676, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2673, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2675, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2676, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2677, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2679, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2680, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2681, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2678, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2680, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2681, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2682, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2684, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2690, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2691, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2683, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2685, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2691, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2692, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2694, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2696, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2693, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2695, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2697, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2699, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2700, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2701, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2698, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2700, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2701, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2702, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2704, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2705, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2706, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2703, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2705, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2706, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2707, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2709, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2715, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2716, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2708, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2710, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2716, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2717, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2719, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2725, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2726, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2718, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2720, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2726, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2727, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2729, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2730, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2731, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2728, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2730, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2731, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2732, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2734, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2740, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2741, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2733, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2735, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2741, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2742, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2744, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2750, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2751, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2743, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2745, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2751, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2752, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2754, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2755, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2756, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2753, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2755, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2756, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2757, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2759, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2765, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2766, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2758, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2760, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2766, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2767, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2769, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2775, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2776, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2768, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2770, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2776, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2777, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2779, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2780, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2781, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2778, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2780, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2781, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2782, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2784, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2790, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2791, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2783, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2785, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2791, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2792, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2794, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2810, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2811, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2812, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2813, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2814, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2815, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2816, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2817, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2818, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2822, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2823, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2824, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2825, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2826, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2827, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2828, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2831, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2832, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2833, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2837, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2838, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2839, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2855, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2856, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2857, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2858, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2859, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2860, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2861, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2862, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2863, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2867, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2868, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2869, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2885, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2886, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2887, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2888, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2889, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2890, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2891, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2892, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2893, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2897, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2898, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2899, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2900, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2901, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2902, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2903, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2906, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2907, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2908, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2912, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2913, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2914, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2930, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2931, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2932, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2933, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2934, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2935, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2936, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2937, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2938, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2942, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2943, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2944, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2960, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2961, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2962, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2963, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2964, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2965, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2966, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2967, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2968, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2972, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2973, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2974, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2975, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2976, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2977, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2978, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2981, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2982, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2983, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2987, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2988, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2989, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3005, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3006, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3007, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3008, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3009, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3010, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3011, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3012, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3013, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3017, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3018, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3019, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3035, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3036, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3037, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3038, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3039, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3040, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3041, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3042, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3043, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3047, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3048, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3049, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3050, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3051, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3052, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3053, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3056, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3057, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3058, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3062, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3063, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3064, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3080, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3081, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3082, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3083, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3084, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3085, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3086, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3087, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3088, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3092, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3093, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3094, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3095, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3099, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3100, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3104, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3108, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3109, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3110, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3111, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3112, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3113, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3117, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3118, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3119, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3120, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3121, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3122, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3123, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3124, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3125, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3126, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3127, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3128, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3132, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3133, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3134, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3135, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3140, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3141, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3142, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3147, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3148, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3149, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3165, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3166, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3167, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3168, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3169, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3170, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3171, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3172, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3173, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3177, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3178, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3179, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3183, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3184, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3185, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3186, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3187, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3188, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3192, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3193, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3194, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3195, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3196, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3197, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3198, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3200, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3201, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3202, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3203, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3207, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3208, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3209, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3222, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3223, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3224, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3240, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3241, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3242, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3243, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3244, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3245, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3246, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3247, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3248, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3252, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3253, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3254, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3258, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3259, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3260, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3261, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3262, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3263, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3267, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3268, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3269, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3270, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3271, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3272, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3273, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3275, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3276, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3277, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3278, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3282, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3283, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3284, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3297, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3298, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3299, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3315, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3316, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3317, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3318, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3319, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3320, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3321, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3322, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3323, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3327, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3328, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3329, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3333, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3334, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3335, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3336, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3337, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3338, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3342, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3343, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3344, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3345, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3346, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3347, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3348, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3350, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3351, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3352, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3353, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3357, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3358, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3359, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3372, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3373, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3374, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3390, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3391, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3392, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3393, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3394, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3395, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3396, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3397, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3398, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3402, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3403, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3404, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3405, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3406, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3407, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3408, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ - _(3409, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3410, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3411, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3412, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ - _(3413, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3414, ZEND_POST_INC_LONG_SPEC_CV) \ - _(3415, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3416, ZEND_POST_DEC_LONG_SPEC_CV) \ - _(3417, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ - _(3418, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(2793, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2795, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2811, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2812, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2813, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2814, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2815, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2816, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2817, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2818, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2819, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2823, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2824, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2825, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2826, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2827, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2828, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2831, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2832, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2833, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2834, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2838, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2839, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2840, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2856, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2857, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2858, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2859, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2860, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2861, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2862, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2863, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2864, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2868, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2869, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2870, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2886, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2887, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2888, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2889, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2890, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2891, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2892, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2893, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2894, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2898, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2899, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2900, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2901, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2902, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2903, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2906, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2907, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2908, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2909, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2913, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2914, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2915, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2931, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2932, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2933, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2934, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2935, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2936, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2937, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2938, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2939, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2943, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2944, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2945, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2961, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2962, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2963, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2964, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2965, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2966, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2967, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2968, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2969, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2973, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2974, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2975, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2976, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2977, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2978, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2981, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2982, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2983, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2984, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2988, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2989, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2990, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3006, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3007, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3008, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3009, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3010, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3011, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3012, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3013, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3014, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3018, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3019, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3020, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3036, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3037, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3038, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3039, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3040, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3041, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3042, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3043, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3044, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3048, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3049, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3050, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3051, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3052, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3053, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3056, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3057, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3058, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3059, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3063, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3064, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3065, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3081, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3082, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3083, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3084, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3085, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3086, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3087, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3088, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3089, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3093, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3094, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3095, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3096, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3100, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3101, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3105, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3109, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3110, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3111, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3112, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3113, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3114, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3118, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3119, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3120, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3121, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3122, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3123, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3124, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3125, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3126, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3127, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3128, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3129, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3133, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3134, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3135, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3140, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3141, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3142, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3144, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3148, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3149, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3166, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3167, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3168, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3169, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3170, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3171, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3172, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3173, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3174, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3178, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3179, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3180, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3184, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3185, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3186, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3187, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3188, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3189, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3193, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3194, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3195, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3196, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3197, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3198, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3200, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3201, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3202, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3203, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3204, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3208, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3209, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3223, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3224, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3241, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3242, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3243, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3244, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3245, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3246, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3247, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3248, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3249, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3253, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3254, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3255, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3259, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3260, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3261, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3262, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3263, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3264, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3268, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3269, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3270, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3271, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3272, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3273, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3275, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3276, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3277, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3278, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3279, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3283, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3284, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3298, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3299, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3316, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3317, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3318, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3319, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3320, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3321, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3322, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3323, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3324, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3328, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3329, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3330, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3334, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3335, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3336, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3337, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3338, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3339, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3343, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3344, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3345, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3346, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3347, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3348, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3350, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3351, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3352, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3353, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3354, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3358, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3359, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3373, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3374, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3391, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3392, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3393, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3394, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3395, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3396, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3397, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3398, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3399, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3403, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3404, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3405, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3406, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3407, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3408, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3409, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ + _(3410, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3411, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3412, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3413, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ + _(3414, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3415, ZEND_POST_INC_LONG_SPEC_CV) \ + _(3416, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3417, ZEND_POST_DEC_LONG_SPEC_CV) \ + _(3418, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ _(3419, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3421, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3422, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ - _(3423, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3420, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3422, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3423, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ _(3424, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3426, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3427, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ - _(3428, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3425, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3427, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3428, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ _(3429, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3431, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3433, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3430, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3432, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ _(3434, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3436, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3437, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3438, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3435, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3437, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3438, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ _(3439, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3441, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3442, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3443, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3440, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3442, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3443, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ _(3444, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3446, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3452, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ - _(3453, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3445, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3447, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3453, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ _(3454, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3456, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3459, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ - _(3461, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ - _(3464, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ - _(3466, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ - _(3467, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ - _(3468, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ - _(3469, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ - _(3470, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ - _(3470+1, ZEND_NULL) + _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3460, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ + _(3462, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ + _(3465, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ + _(3467, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ + _(3468, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ + _(3469, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ + _(3470, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ + _(3471, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ + _(3471+1, ZEND_NULL) diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index e94b6c7d5f0cd..6f84a8c7e996e 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -22,7 +22,7 @@ #include #include -static const char *zend_vm_opcodes_names[204] = { +static const char *zend_vm_opcodes_names[205] = { "ZEND_NOP", "ZEND_ADD", "ZEND_SUB", @@ -227,9 +227,10 @@ static const char *zend_vm_opcodes_names[204] = { "ZEND_VERIFY_NEVER_TYPE", "ZEND_CALLABLE_CONVERT", "ZEND_BIND_INIT_STATIC_OR_JMP", + "ZEND_INIT_PARENT_PROPERTY_HOOK_CALL", }; -static uint32_t zend_vm_opcodes_flags[204] = { +static uint32_t zend_vm_opcodes_flags[205] = { 0x00000000, 0x00000b0b, 0x00000b0b, @@ -434,6 +435,7 @@ static uint32_t zend_vm_opcodes_flags[204] = { 0x00000101, 0x00000101, 0x00002001, + 0x01001103, }; ZEND_API const char* ZEND_FASTCALL zend_get_opcode_name(uint8_t opcode) { diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index 5531accbf0c20..2816bfde1eb9d 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -83,210 +83,211 @@ ZEND_API uint8_t zend_get_opcode_id(const char *name, size_t length); END_EXTERN_C() -#define ZEND_NOP 0 -#define ZEND_ADD 1 -#define ZEND_SUB 2 -#define ZEND_MUL 3 -#define ZEND_DIV 4 -#define ZEND_MOD 5 -#define ZEND_SL 6 -#define ZEND_SR 7 -#define ZEND_CONCAT 8 -#define ZEND_BW_OR 9 -#define ZEND_BW_AND 10 -#define ZEND_BW_XOR 11 -#define ZEND_POW 12 -#define ZEND_BW_NOT 13 -#define ZEND_BOOL_NOT 14 -#define ZEND_BOOL_XOR 15 -#define ZEND_IS_IDENTICAL 16 -#define ZEND_IS_NOT_IDENTICAL 17 -#define ZEND_IS_EQUAL 18 -#define ZEND_IS_NOT_EQUAL 19 -#define ZEND_IS_SMALLER 20 -#define ZEND_IS_SMALLER_OR_EQUAL 21 -#define ZEND_ASSIGN 22 -#define ZEND_ASSIGN_DIM 23 -#define ZEND_ASSIGN_OBJ 24 -#define ZEND_ASSIGN_STATIC_PROP 25 -#define ZEND_ASSIGN_OP 26 -#define ZEND_ASSIGN_DIM_OP 27 -#define ZEND_ASSIGN_OBJ_OP 28 -#define ZEND_ASSIGN_STATIC_PROP_OP 29 -#define ZEND_ASSIGN_REF 30 -#define ZEND_QM_ASSIGN 31 -#define ZEND_ASSIGN_OBJ_REF 32 -#define ZEND_ASSIGN_STATIC_PROP_REF 33 -#define ZEND_PRE_INC 34 -#define ZEND_PRE_DEC 35 -#define ZEND_POST_INC 36 -#define ZEND_POST_DEC 37 -#define ZEND_PRE_INC_STATIC_PROP 38 -#define ZEND_PRE_DEC_STATIC_PROP 39 -#define ZEND_POST_INC_STATIC_PROP 40 -#define ZEND_POST_DEC_STATIC_PROP 41 -#define ZEND_JMP 42 -#define ZEND_JMPZ 43 -#define ZEND_JMPNZ 44 -#define ZEND_JMPZ_EX 46 -#define ZEND_JMPNZ_EX 47 -#define ZEND_CASE 48 -#define ZEND_CHECK_VAR 49 -#define ZEND_SEND_VAR_NO_REF_EX 50 -#define ZEND_CAST 51 -#define ZEND_BOOL 52 -#define ZEND_FAST_CONCAT 53 -#define ZEND_ROPE_INIT 54 -#define ZEND_ROPE_ADD 55 -#define ZEND_ROPE_END 56 -#define ZEND_BEGIN_SILENCE 57 -#define ZEND_END_SILENCE 58 -#define ZEND_INIT_FCALL_BY_NAME 59 -#define ZEND_DO_FCALL 60 -#define ZEND_INIT_FCALL 61 -#define ZEND_RETURN 62 -#define ZEND_RECV 63 -#define ZEND_RECV_INIT 64 -#define ZEND_SEND_VAL 65 -#define ZEND_SEND_VAR_EX 66 -#define ZEND_SEND_REF 67 -#define ZEND_NEW 68 -#define ZEND_INIT_NS_FCALL_BY_NAME 69 -#define ZEND_FREE 70 -#define ZEND_INIT_ARRAY 71 -#define ZEND_ADD_ARRAY_ELEMENT 72 -#define ZEND_INCLUDE_OR_EVAL 73 -#define ZEND_UNSET_VAR 74 -#define ZEND_UNSET_DIM 75 -#define ZEND_UNSET_OBJ 76 -#define ZEND_FE_RESET_R 77 -#define ZEND_FE_FETCH_R 78 -#define ZEND_EXIT 79 -#define ZEND_FETCH_R 80 -#define ZEND_FETCH_DIM_R 81 -#define ZEND_FETCH_OBJ_R 82 -#define ZEND_FETCH_W 83 -#define ZEND_FETCH_DIM_W 84 -#define ZEND_FETCH_OBJ_W 85 -#define ZEND_FETCH_RW 86 -#define ZEND_FETCH_DIM_RW 87 -#define ZEND_FETCH_OBJ_RW 88 -#define ZEND_FETCH_IS 89 -#define ZEND_FETCH_DIM_IS 90 -#define ZEND_FETCH_OBJ_IS 91 -#define ZEND_FETCH_FUNC_ARG 92 -#define ZEND_FETCH_DIM_FUNC_ARG 93 -#define ZEND_FETCH_OBJ_FUNC_ARG 94 -#define ZEND_FETCH_UNSET 95 -#define ZEND_FETCH_DIM_UNSET 96 -#define ZEND_FETCH_OBJ_UNSET 97 -#define ZEND_FETCH_LIST_R 98 -#define ZEND_FETCH_CONSTANT 99 -#define ZEND_CHECK_FUNC_ARG 100 -#define ZEND_EXT_STMT 101 -#define ZEND_EXT_FCALL_BEGIN 102 -#define ZEND_EXT_FCALL_END 103 -#define ZEND_EXT_NOP 104 -#define ZEND_TICKS 105 -#define ZEND_SEND_VAR_NO_REF 106 -#define ZEND_CATCH 107 -#define ZEND_THROW 108 -#define ZEND_FETCH_CLASS 109 -#define ZEND_CLONE 110 -#define ZEND_RETURN_BY_REF 111 -#define ZEND_INIT_METHOD_CALL 112 -#define ZEND_INIT_STATIC_METHOD_CALL 113 -#define ZEND_ISSET_ISEMPTY_VAR 114 -#define ZEND_ISSET_ISEMPTY_DIM_OBJ 115 -#define ZEND_SEND_VAL_EX 116 -#define ZEND_SEND_VAR 117 -#define ZEND_INIT_USER_CALL 118 -#define ZEND_SEND_ARRAY 119 -#define ZEND_SEND_USER 120 -#define ZEND_STRLEN 121 -#define ZEND_DEFINED 122 -#define ZEND_TYPE_CHECK 123 -#define ZEND_VERIFY_RETURN_TYPE 124 -#define ZEND_FE_RESET_RW 125 -#define ZEND_FE_FETCH_RW 126 -#define ZEND_FE_FREE 127 -#define ZEND_INIT_DYNAMIC_CALL 128 -#define ZEND_DO_ICALL 129 -#define ZEND_DO_UCALL 130 -#define ZEND_DO_FCALL_BY_NAME 131 -#define ZEND_PRE_INC_OBJ 132 -#define ZEND_PRE_DEC_OBJ 133 -#define ZEND_POST_INC_OBJ 134 -#define ZEND_POST_DEC_OBJ 135 -#define ZEND_ECHO 136 -#define ZEND_OP_DATA 137 -#define ZEND_INSTANCEOF 138 -#define ZEND_GENERATOR_CREATE 139 -#define ZEND_MAKE_REF 140 -#define ZEND_DECLARE_FUNCTION 141 -#define ZEND_DECLARE_LAMBDA_FUNCTION 142 -#define ZEND_DECLARE_CONST 143 -#define ZEND_DECLARE_CLASS 144 -#define ZEND_DECLARE_CLASS_DELAYED 145 -#define ZEND_DECLARE_ANON_CLASS 146 -#define ZEND_ADD_ARRAY_UNPACK 147 -#define ZEND_ISSET_ISEMPTY_PROP_OBJ 148 -#define ZEND_HANDLE_EXCEPTION 149 -#define ZEND_USER_OPCODE 150 -#define ZEND_ASSERT_CHECK 151 -#define ZEND_JMP_SET 152 -#define ZEND_UNSET_CV 153 -#define ZEND_ISSET_ISEMPTY_CV 154 -#define ZEND_FETCH_LIST_W 155 -#define ZEND_SEPARATE 156 -#define ZEND_FETCH_CLASS_NAME 157 -#define ZEND_CALL_TRAMPOLINE 158 -#define ZEND_DISCARD_EXCEPTION 159 -#define ZEND_YIELD 160 -#define ZEND_GENERATOR_RETURN 161 -#define ZEND_FAST_CALL 162 -#define ZEND_FAST_RET 163 -#define ZEND_RECV_VARIADIC 164 -#define ZEND_SEND_UNPACK 165 -#define ZEND_YIELD_FROM 166 -#define ZEND_COPY_TMP 167 -#define ZEND_BIND_GLOBAL 168 -#define ZEND_COALESCE 169 -#define ZEND_SPACESHIP 170 -#define ZEND_FUNC_NUM_ARGS 171 -#define ZEND_FUNC_GET_ARGS 172 -#define ZEND_FETCH_STATIC_PROP_R 173 -#define ZEND_FETCH_STATIC_PROP_W 174 -#define ZEND_FETCH_STATIC_PROP_RW 175 -#define ZEND_FETCH_STATIC_PROP_IS 176 -#define ZEND_FETCH_STATIC_PROP_FUNC_ARG 177 -#define ZEND_FETCH_STATIC_PROP_UNSET 178 -#define ZEND_UNSET_STATIC_PROP 179 -#define ZEND_ISSET_ISEMPTY_STATIC_PROP 180 -#define ZEND_FETCH_CLASS_CONSTANT 181 -#define ZEND_BIND_LEXICAL 182 -#define ZEND_BIND_STATIC 183 -#define ZEND_FETCH_THIS 184 -#define ZEND_SEND_FUNC_ARG 185 -#define ZEND_ISSET_ISEMPTY_THIS 186 -#define ZEND_SWITCH_LONG 187 -#define ZEND_SWITCH_STRING 188 -#define ZEND_IN_ARRAY 189 -#define ZEND_COUNT 190 -#define ZEND_GET_CLASS 191 -#define ZEND_GET_CALLED_CLASS 192 -#define ZEND_GET_TYPE 193 -#define ZEND_ARRAY_KEY_EXISTS 194 -#define ZEND_MATCH 195 -#define ZEND_CASE_STRICT 196 -#define ZEND_MATCH_ERROR 197 -#define ZEND_JMP_NULL 198 -#define ZEND_CHECK_UNDEF_ARGS 199 -#define ZEND_FETCH_GLOBALS 200 -#define ZEND_VERIFY_NEVER_TYPE 201 -#define ZEND_CALLABLE_CONVERT 202 -#define ZEND_BIND_INIT_STATIC_OR_JMP 203 +#define ZEND_NOP 0 +#define ZEND_ADD 1 +#define ZEND_SUB 2 +#define ZEND_MUL 3 +#define ZEND_DIV 4 +#define ZEND_MOD 5 +#define ZEND_SL 6 +#define ZEND_SR 7 +#define ZEND_CONCAT 8 +#define ZEND_BW_OR 9 +#define ZEND_BW_AND 10 +#define ZEND_BW_XOR 11 +#define ZEND_POW 12 +#define ZEND_BW_NOT 13 +#define ZEND_BOOL_NOT 14 +#define ZEND_BOOL_XOR 15 +#define ZEND_IS_IDENTICAL 16 +#define ZEND_IS_NOT_IDENTICAL 17 +#define ZEND_IS_EQUAL 18 +#define ZEND_IS_NOT_EQUAL 19 +#define ZEND_IS_SMALLER 20 +#define ZEND_IS_SMALLER_OR_EQUAL 21 +#define ZEND_ASSIGN 22 +#define ZEND_ASSIGN_DIM 23 +#define ZEND_ASSIGN_OBJ 24 +#define ZEND_ASSIGN_STATIC_PROP 25 +#define ZEND_ASSIGN_OP 26 +#define ZEND_ASSIGN_DIM_OP 27 +#define ZEND_ASSIGN_OBJ_OP 28 +#define ZEND_ASSIGN_STATIC_PROP_OP 29 +#define ZEND_ASSIGN_REF 30 +#define ZEND_QM_ASSIGN 31 +#define ZEND_ASSIGN_OBJ_REF 32 +#define ZEND_ASSIGN_STATIC_PROP_REF 33 +#define ZEND_PRE_INC 34 +#define ZEND_PRE_DEC 35 +#define ZEND_POST_INC 36 +#define ZEND_POST_DEC 37 +#define ZEND_PRE_INC_STATIC_PROP 38 +#define ZEND_PRE_DEC_STATIC_PROP 39 +#define ZEND_POST_INC_STATIC_PROP 40 +#define ZEND_POST_DEC_STATIC_PROP 41 +#define ZEND_JMP 42 +#define ZEND_JMPZ 43 +#define ZEND_JMPNZ 44 +#define ZEND_JMPZ_EX 46 +#define ZEND_JMPNZ_EX 47 +#define ZEND_CASE 48 +#define ZEND_CHECK_VAR 49 +#define ZEND_SEND_VAR_NO_REF_EX 50 +#define ZEND_CAST 51 +#define ZEND_BOOL 52 +#define ZEND_FAST_CONCAT 53 +#define ZEND_ROPE_INIT 54 +#define ZEND_ROPE_ADD 55 +#define ZEND_ROPE_END 56 +#define ZEND_BEGIN_SILENCE 57 +#define ZEND_END_SILENCE 58 +#define ZEND_INIT_FCALL_BY_NAME 59 +#define ZEND_DO_FCALL 60 +#define ZEND_INIT_FCALL 61 +#define ZEND_RETURN 62 +#define ZEND_RECV 63 +#define ZEND_RECV_INIT 64 +#define ZEND_SEND_VAL 65 +#define ZEND_SEND_VAR_EX 66 +#define ZEND_SEND_REF 67 +#define ZEND_NEW 68 +#define ZEND_INIT_NS_FCALL_BY_NAME 69 +#define ZEND_FREE 70 +#define ZEND_INIT_ARRAY 71 +#define ZEND_ADD_ARRAY_ELEMENT 72 +#define ZEND_INCLUDE_OR_EVAL 73 +#define ZEND_UNSET_VAR 74 +#define ZEND_UNSET_DIM 75 +#define ZEND_UNSET_OBJ 76 +#define ZEND_FE_RESET_R 77 +#define ZEND_FE_FETCH_R 78 +#define ZEND_EXIT 79 +#define ZEND_FETCH_R 80 +#define ZEND_FETCH_DIM_R 81 +#define ZEND_FETCH_OBJ_R 82 +#define ZEND_FETCH_W 83 +#define ZEND_FETCH_DIM_W 84 +#define ZEND_FETCH_OBJ_W 85 +#define ZEND_FETCH_RW 86 +#define ZEND_FETCH_DIM_RW 87 +#define ZEND_FETCH_OBJ_RW 88 +#define ZEND_FETCH_IS 89 +#define ZEND_FETCH_DIM_IS 90 +#define ZEND_FETCH_OBJ_IS 91 +#define ZEND_FETCH_FUNC_ARG 92 +#define ZEND_FETCH_DIM_FUNC_ARG 93 +#define ZEND_FETCH_OBJ_FUNC_ARG 94 +#define ZEND_FETCH_UNSET 95 +#define ZEND_FETCH_DIM_UNSET 96 +#define ZEND_FETCH_OBJ_UNSET 97 +#define ZEND_FETCH_LIST_R 98 +#define ZEND_FETCH_CONSTANT 99 +#define ZEND_CHECK_FUNC_ARG 100 +#define ZEND_EXT_STMT 101 +#define ZEND_EXT_FCALL_BEGIN 102 +#define ZEND_EXT_FCALL_END 103 +#define ZEND_EXT_NOP 104 +#define ZEND_TICKS 105 +#define ZEND_SEND_VAR_NO_REF 106 +#define ZEND_CATCH 107 +#define ZEND_THROW 108 +#define ZEND_FETCH_CLASS 109 +#define ZEND_CLONE 110 +#define ZEND_RETURN_BY_REF 111 +#define ZEND_INIT_METHOD_CALL 112 +#define ZEND_INIT_STATIC_METHOD_CALL 113 +#define ZEND_ISSET_ISEMPTY_VAR 114 +#define ZEND_ISSET_ISEMPTY_DIM_OBJ 115 +#define ZEND_SEND_VAL_EX 116 +#define ZEND_SEND_VAR 117 +#define ZEND_INIT_USER_CALL 118 +#define ZEND_SEND_ARRAY 119 +#define ZEND_SEND_USER 120 +#define ZEND_STRLEN 121 +#define ZEND_DEFINED 122 +#define ZEND_TYPE_CHECK 123 +#define ZEND_VERIFY_RETURN_TYPE 124 +#define ZEND_FE_RESET_RW 125 +#define ZEND_FE_FETCH_RW 126 +#define ZEND_FE_FREE 127 +#define ZEND_INIT_DYNAMIC_CALL 128 +#define ZEND_DO_ICALL 129 +#define ZEND_DO_UCALL 130 +#define ZEND_DO_FCALL_BY_NAME 131 +#define ZEND_PRE_INC_OBJ 132 +#define ZEND_PRE_DEC_OBJ 133 +#define ZEND_POST_INC_OBJ 134 +#define ZEND_POST_DEC_OBJ 135 +#define ZEND_ECHO 136 +#define ZEND_OP_DATA 137 +#define ZEND_INSTANCEOF 138 +#define ZEND_GENERATOR_CREATE 139 +#define ZEND_MAKE_REF 140 +#define ZEND_DECLARE_FUNCTION 141 +#define ZEND_DECLARE_LAMBDA_FUNCTION 142 +#define ZEND_DECLARE_CONST 143 +#define ZEND_DECLARE_CLASS 144 +#define ZEND_DECLARE_CLASS_DELAYED 145 +#define ZEND_DECLARE_ANON_CLASS 146 +#define ZEND_ADD_ARRAY_UNPACK 147 +#define ZEND_ISSET_ISEMPTY_PROP_OBJ 148 +#define ZEND_HANDLE_EXCEPTION 149 +#define ZEND_USER_OPCODE 150 +#define ZEND_ASSERT_CHECK 151 +#define ZEND_JMP_SET 152 +#define ZEND_UNSET_CV 153 +#define ZEND_ISSET_ISEMPTY_CV 154 +#define ZEND_FETCH_LIST_W 155 +#define ZEND_SEPARATE 156 +#define ZEND_FETCH_CLASS_NAME 157 +#define ZEND_CALL_TRAMPOLINE 158 +#define ZEND_DISCARD_EXCEPTION 159 +#define ZEND_YIELD 160 +#define ZEND_GENERATOR_RETURN 161 +#define ZEND_FAST_CALL 162 +#define ZEND_FAST_RET 163 +#define ZEND_RECV_VARIADIC 164 +#define ZEND_SEND_UNPACK 165 +#define ZEND_YIELD_FROM 166 +#define ZEND_COPY_TMP 167 +#define ZEND_BIND_GLOBAL 168 +#define ZEND_COALESCE 169 +#define ZEND_SPACESHIP 170 +#define ZEND_FUNC_NUM_ARGS 171 +#define ZEND_FUNC_GET_ARGS 172 +#define ZEND_FETCH_STATIC_PROP_R 173 +#define ZEND_FETCH_STATIC_PROP_W 174 +#define ZEND_FETCH_STATIC_PROP_RW 175 +#define ZEND_FETCH_STATIC_PROP_IS 176 +#define ZEND_FETCH_STATIC_PROP_FUNC_ARG 177 +#define ZEND_FETCH_STATIC_PROP_UNSET 178 +#define ZEND_UNSET_STATIC_PROP 179 +#define ZEND_ISSET_ISEMPTY_STATIC_PROP 180 +#define ZEND_FETCH_CLASS_CONSTANT 181 +#define ZEND_BIND_LEXICAL 182 +#define ZEND_BIND_STATIC 183 +#define ZEND_FETCH_THIS 184 +#define ZEND_SEND_FUNC_ARG 185 +#define ZEND_ISSET_ISEMPTY_THIS 186 +#define ZEND_SWITCH_LONG 187 +#define ZEND_SWITCH_STRING 188 +#define ZEND_IN_ARRAY 189 +#define ZEND_COUNT 190 +#define ZEND_GET_CLASS 191 +#define ZEND_GET_CALLED_CLASS 192 +#define ZEND_GET_TYPE 193 +#define ZEND_ARRAY_KEY_EXISTS 194 +#define ZEND_MATCH 195 +#define ZEND_CASE_STRICT 196 +#define ZEND_MATCH_ERROR 197 +#define ZEND_JMP_NULL 198 +#define ZEND_CHECK_UNDEF_ARGS 199 +#define ZEND_FETCH_GLOBALS 200 +#define ZEND_VERIFY_NEVER_TYPE 201 +#define ZEND_CALLABLE_CONVERT 202 +#define ZEND_BIND_INIT_STATIC_OR_JMP 203 +#define ZEND_INIT_PARENT_PROPERTY_HOOK_CALL 204 -#define ZEND_VM_LAST_OPCODE 203 +#define ZEND_VM_LAST_OPCODE 204 #endif diff --git a/configure.ac b/configure.ac index fcb629723817b..d339e2969e9aa 100644 --- a/configure.ac +++ b/configure.ac @@ -373,6 +373,16 @@ if test "$ac_cv_func_dlopen" = "yes"; then fi AC_CHECK_LIB(m, sin) +case $host_alias in + riscv64*) + AC_CHECK_LIB(atomic, __atomic_exchange_1, [ + PHP_ADD_LIBRARY(atomic) + ], [ + AC_MSG_ERROR([Problem with enabling atomic. Please check config.log for details.]) + ]) + ;; +esac + dnl Check for inet_aton in -lc, -lbind and -lresolv. PHP_CHECK_FUNC(inet_aton, resolv, bind) @@ -1723,7 +1733,7 @@ PHP_ADD_SOURCES(Zend, \ zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \ zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \ zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_atomic.c \ - zend_max_execution_timer.c \ + zend_max_execution_timer.c zend_property_hooks.c \ Optimizer/zend_optimizer.c \ Optimizer/pass1.c \ Optimizer/pass3.c \ diff --git a/docs/release-process.md b/docs/release-process.md index a082aabc65bef..1a60d1ee7035e 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -988,6 +988,20 @@ volunteers to begin the selection process for the next release managers. git push ``` + `web-php-distributions` is a submodule of `web-php`. You'll now have to update + the commit reference to reflect the change made in web-php-distributions. + + ```shell + cd /path/to/repos/php/web-php + git submodule update + cd distributions # This is the submodule refering to web-php-distributions + git pull origin master + cd .. + git add distributions + git commit --gpg-sign=YOURKEYID -m "Update php-keyring.gpg in distributions" + git push + ``` + 4. Request moderation access to php-announce@lists.php.net and primary-qa-tester@lists.php.net lists, so you are able to moderate your release announcements. All the announcements should be sent from your diff --git a/ext/bcmath/libbcmath/src/bcmath.h b/ext/bcmath/libbcmath/src/bcmath.h index 4e32a3cbacacb..6ce1350956417 100644 --- a/ext/bcmath/libbcmath/src/bcmath.h +++ b/ext/bcmath/libbcmath/src/bcmath.h @@ -129,7 +129,7 @@ int bc_modulo(bc_num num1, bc_num num2, bc_num *resul, int scale); int bc_divmod(bc_num num1, bc_num num2, bc_num *quo, bc_num *rem, int scale); -int bc_raisemod(bc_num base, bc_num expo, bc_num mo, bc_num *result, int scale); +zend_result bc_raisemod(bc_num base, bc_num expo, bc_num mo, bc_num *result, int scale); void bc_raise(bc_num num1, bc_num num2, bc_num *resul, int scale); diff --git a/ext/dom/document.c b/ext/dom/document.c index c60198a3be110..7dd1e7f38ac80 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -297,7 +297,7 @@ readonly=no int dom_document_format_output_read(dom_object *obj, zval *retval) { if (obj->document) { - dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document); + libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document); ZVAL_BOOL(retval, doc_prop->formatoutput); } else { ZVAL_FALSE(retval); @@ -322,7 +322,7 @@ readonly=no int dom_document_validate_on_parse_read(dom_object *obj, zval *retval) { if (obj->document) { - dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document); + libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document); ZVAL_BOOL(retval, doc_prop->validateonparse); } else { ZVAL_FALSE(retval); @@ -347,7 +347,7 @@ readonly=no int dom_document_resolve_externals_read(dom_object *obj, zval *retval) { if (obj->document) { - dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document); + libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document); ZVAL_BOOL(retval, doc_prop->resolveexternals); } else { ZVAL_FALSE(retval); @@ -372,7 +372,7 @@ readonly=no int dom_document_preserve_whitespace_read(dom_object *obj, zval *retval) { if (obj->document) { - dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document); + libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document); ZVAL_BOOL(retval, doc_prop->preservewhitespace); } else { ZVAL_FALSE(retval); @@ -397,7 +397,7 @@ readonly=no int dom_document_recover_read(dom_object *obj, zval *retval) { if (obj->document) { - dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document); + libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document); ZVAL_BOOL(retval, doc_prop->recover); } else { ZVAL_FALSE(retval); @@ -422,7 +422,7 @@ readonly=no int dom_document_substitue_entities_read(dom_object *obj, zval *retval) { if (obj->document) { - dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document); + libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document); ZVAL_BOOL(retval, doc_prop->substituteentities); } else { ZVAL_FALSE(retval); @@ -777,7 +777,6 @@ PHP_METHOD(DOMDocument, getElementsByTagName) size_t name_len; dom_object *intern, *namednode; char *name; - xmlChar *local; id = ZEND_THIS; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { @@ -788,8 +787,7 @@ PHP_METHOD(DOMDocument, getElementsByTagName) php_dom_create_iterator(return_value, DOM_NODELIST); namednode = Z_DOMOBJ_P(return_value); - local = xmlCharStrndup(name, name_len); - dom_namednode_iter(intern, 0, namednode, NULL, local, NULL); + dom_namednode_iter(intern, 0, namednode, NULL, name, name_len, NULL, 0); } /* }}} end dom_document_get_elements_by_tag_name */ @@ -847,6 +845,8 @@ PHP_METHOD(DOMDocument, importNode) } } + php_libxml_invalidate_node_list_cache_from_doc(docp); + DOM_RET_OBJ((xmlNodePtr) retnodep, &ret, intern); } /* }}} end dom_document_import_node */ @@ -991,7 +991,6 @@ PHP_METHOD(DOMDocument, getElementsByTagNameNS) size_t uri_len, name_len; dom_object *intern, *namednode; char *uri, *name; - xmlChar *local, *nsuri; id = ZEND_THIS; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) { @@ -1002,12 +1001,23 @@ PHP_METHOD(DOMDocument, getElementsByTagNameNS) php_dom_create_iterator(return_value, DOM_NODELIST); namednode = Z_DOMOBJ_P(return_value); - local = xmlCharStrndup(name, name_len); - nsuri = xmlCharStrndup(uri ? uri : "", uri_len); - dom_namednode_iter(intern, 0, namednode, NULL, local, nsuri); + dom_namednode_iter(intern, 0, namednode, NULL, name, name_len, uri ? uri : "", uri_len); } /* }}} end dom_document_get_elements_by_tag_name_ns */ +static bool php_dom_is_node_attached(const xmlNode *node) +{ + ZEND_ASSERT(node != NULL); + node = node->parent; + while (node != NULL) { + if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) { + return true; + } + node = node->parent; + } + return false; +} + /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-getElBId Since: DOM Level 2 */ @@ -1030,7 +1040,13 @@ PHP_METHOD(DOMDocument, getElementById) attrp = xmlGetID(docp, (xmlChar *) idname); - if (attrp && attrp->parent) { + /* From the moment an ID is created, libxml2's behaviour is to cache that element, even + * if that element is not yet attached to the document. Similarly, only upon destruction of + * the element the ID is actually removed by libxml2. Since libxml2 has such behaviour deeply + * ingrained in the library, and uses the cache for various purposes, it seems like a bad + * idea and lost cause to fight it. Instead, we'll simply walk the tree upwards to check + * if the node is attached to the document. */ + if (attrp && attrp->parent && php_dom_is_node_attached(attrp->parent)) { DOM_RET_OBJ((xmlNodePtr) attrp->parent, &ret, intern); } else { RETVAL_NULL(); @@ -1070,6 +1086,8 @@ PHP_METHOD(DOMDocument, normalizeDocument) DOM_GET_OBJ(docp, id, xmlDocPtr, intern); + php_libxml_invalidate_node_list_cache_from_doc(docp); + dom_normalize((xmlNodePtr) docp); } /* }}} end dom_document_normalize_document */ @@ -1176,7 +1194,6 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so { xmlDocPtr ret; xmlParserCtxtPtr ctxt = NULL; - dom_doc_propsptr doc_props; dom_object *intern; php_libxml_ref_obj *document = NULL; int validate, recover, resolve_externals, keep_blanks, substitute_ent; @@ -1189,17 +1206,13 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so document = intern->document; } - doc_props = dom_get_doc_props(document); + libxml_doc_props const* doc_props = dom_get_doc_props_read_only(document); validate = doc_props->validateonparse; resolve_externals = doc_props->resolveexternals; keep_blanks = doc_props->preservewhitespace; substitute_ent = doc_props->substituteentities; recover = doc_props->recover; - if (document == NULL) { - efree(doc_props); - } - xmlInitParser(); if (mode == DOM_LOAD_FILE) { @@ -1333,10 +1346,14 @@ static void dom_parse_document(INTERNAL_FUNCTION_PARAMETERS, int mode) { if (id != NULL) { intern = Z_DOMOBJ_P(id); + size_t old_modification_nr = 0; if (intern != NULL) { docp = (xmlDocPtr) dom_object_get_node(intern); doc_prop = NULL; if (docp != NULL) { + const php_libxml_doc_ptr *doc_ptr = docp->_private; + ZEND_ASSERT(doc_ptr != NULL); /* Must exist, we have a document */ + old_modification_nr = doc_ptr->cache_tag.modification_nr; php_libxml_decrement_node_ptr((php_libxml_node_object *) intern); doc_prop = intern->document->doc_props; intern->document->doc_props = NULL; @@ -1353,6 +1370,12 @@ static void dom_parse_document(INTERNAL_FUNCTION_PARAMETERS, int mode) { } php_libxml_increment_node_ptr((php_libxml_node_object *)intern, (xmlNodePtr)newdoc, (void *)intern); + /* Since iterators should invalidate, we need to start the modification number from the old counter */ + if (old_modification_nr != 0) { + php_libxml_doc_ptr* doc_ptr = (php_libxml_doc_ptr*) ((php_libxml_node_object*) intern)->node; /* downcast */ + doc_ptr->cache_tag.modification_nr = old_modification_nr; + php_libxml_invalidate_node_list_cache(doc_ptr); + } RETURN_TRUE; } else { @@ -1387,7 +1410,6 @@ PHP_METHOD(DOMDocument, save) size_t file_len = 0; int bytes, format, saveempty = 0; dom_object *intern; - dom_doc_propsptr doc_props; char *file; zend_long options = 0; @@ -1405,7 +1427,7 @@ PHP_METHOD(DOMDocument, save) /* encoding handled by property on doc */ - doc_props = dom_get_doc_props(intern->document); + libxml_doc_props const* doc_props = dom_get_doc_props_read_only(intern->document); format = doc_props->formatoutput; if (options & LIBXML_SAVE_NOEMPTYTAG) { saveempty = xmlSaveNoEmptyTags; @@ -1433,7 +1455,6 @@ PHP_METHOD(DOMDocument, saveXML) xmlBufferPtr buf; xmlChar *mem; dom_object *intern, *nodeobj; - dom_doc_propsptr doc_props; int size, format, saveempty = 0; zend_long options = 0; @@ -1444,7 +1465,7 @@ PHP_METHOD(DOMDocument, saveXML) DOM_GET_OBJ(docp, id, xmlDocPtr, intern); - doc_props = dom_get_doc_props(intern->document); + libxml_doc_props const* doc_props = dom_get_doc_props_read_only(intern->document); format = doc_props->formatoutput; if (nodep != NULL) { @@ -1570,6 +1591,8 @@ PHP_METHOD(DOMDocument, xinclude) php_dom_remove_xinclude_nodes(root); } + php_libxml_invalidate_node_list_cache_from_doc(docp); + if (err) { RETVAL_LONG(err); } else { @@ -1878,10 +1901,14 @@ static void dom_load_html(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */ if (id != NULL && instanceof_function(Z_OBJCE_P(id), dom_document_class_entry)) { intern = Z_DOMOBJ_P(id); + size_t old_modification_nr = 0; if (intern != NULL) { docp = (xmlDocPtr) dom_object_get_node(intern); doc_prop = NULL; if (docp != NULL) { + const php_libxml_doc_ptr *doc_ptr = docp->_private; + ZEND_ASSERT(doc_ptr != NULL); /* Must exist, we have a document */ + old_modification_nr = doc_ptr->cache_tag.modification_nr; php_libxml_decrement_node_ptr((php_libxml_node_object *) intern); doc_prop = intern->document->doc_props; intern->document->doc_props = NULL; @@ -1898,6 +1925,12 @@ static void dom_load_html(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */ } php_libxml_increment_node_ptr((php_libxml_node_object *)intern, (xmlNodePtr)newdoc, (void *)intern); + /* Since iterators should invalidate, we need to start the modification number from the old counter */ + if (old_modification_nr != 0) { + php_libxml_doc_ptr* doc_ptr = (php_libxml_doc_ptr*) ((php_libxml_node_object*) intern)->node; /* downcast */ + doc_ptr->cache_tag.modification_nr = old_modification_nr; + php_libxml_invalidate_node_list_cache(doc_ptr); + } RETURN_TRUE; } else { @@ -1928,7 +1961,6 @@ PHP_METHOD(DOMDocument, saveHTMLFile) size_t file_len; int bytes, format; dom_object *intern; - dom_doc_propsptr doc_props; char *file; const char *encoding; @@ -1947,7 +1979,7 @@ PHP_METHOD(DOMDocument, saveHTMLFile) encoding = (const char *) htmlGetMetaEncoding(docp); - doc_props = dom_get_doc_props(intern->document); + libxml_doc_props const* doc_props = dom_get_doc_props_read_only(intern->document); format = doc_props->formatoutput; bytes = htmlSaveFileFormat(file, docp, encoding, format); @@ -1969,7 +2001,6 @@ PHP_METHOD(DOMDocument, saveHTML) dom_object *intern, *nodeobj; xmlChar *mem = NULL; int format; - dom_doc_propsptr doc_props; id = ZEND_THIS; if (zend_parse_parameters(ZEND_NUM_ARGS(), @@ -1980,7 +2011,7 @@ PHP_METHOD(DOMDocument, saveHTML) DOM_GET_OBJ(docp, id, xmlDocPtr, intern); - doc_props = dom_get_doc_props(intern->document); + libxml_doc_props const* doc_props = dom_get_doc_props(intern->document); format = doc_props->formatoutput; if (nodep != NULL) { diff --git a/ext/dom/documenttype.c b/ext/dom/documenttype.c index b046b05f80eff..cfc4b043edb22 100644 --- a/ext/dom/documenttype.c +++ b/ext/dom/documenttype.c @@ -65,7 +65,7 @@ int dom_documenttype_entities_read(dom_object *obj, zval *retval) entityht = (xmlHashTable *) doctypep->entities; intern = Z_DOMOBJ_P(retval); - dom_namednode_iter(obj, XML_ENTITY_NODE, intern, entityht, NULL, NULL); + dom_namednode_iter(obj, XML_ENTITY_NODE, intern, entityht, NULL, 0, NULL, 0); return SUCCESS; } @@ -93,7 +93,7 @@ int dom_documenttype_notations_read(dom_object *obj, zval *retval) notationht = (xmlHashTable *) doctypep->notations; intern = Z_DOMOBJ_P(retval); - dom_namednode_iter(obj, XML_NOTATION_NODE, intern, notationht, NULL, NULL); + dom_namednode_iter(obj, XML_NOTATION_NODE, intern, notationht, NULL, 0, NULL, 0); return SUCCESS; } diff --git a/ext/dom/dom_iterators.c b/ext/dom/dom_iterators.c index 72c97104db04d..2cf2c7bb6e7ce 100644 --- a/ext/dom/dom_iterators.c +++ b/ext/dom/dom_iterators.c @@ -179,7 +179,7 @@ static void php_dom_iterator_move_forward(zend_object_iterator *iter) /* {{{ */ dom_object *intern; dom_object *nnmap; dom_nnodemap_object *objmap; - int previndex=0; + int previndex; HashTable *nodeht; zval *entry; bool do_curobj_undef = 1; @@ -205,23 +205,32 @@ static void php_dom_iterator_move_forward(zend_object_iterator *iter) /* {{{ */ do_curobj_undef = 0; } } else { - curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node; if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) { + curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node; curnode = curnode->next; } else { - /* Nav the tree evey time as this is LIVE */ + /* The collection is live, we nav the tree from the base object if we cannot + * use the cache to restart from the last point. */ basenode = dom_object_get_node(objmap->baseobj); - if (basenode && (basenode->type == XML_DOCUMENT_NODE || - basenode->type == XML_HTML_DOCUMENT_NODE)) { - basenode = xmlDocGetRootElement((xmlDoc *) basenode); - } else if (basenode) { - basenode = basenode->children; - } else { + if (UNEXPECTED(!basenode)) { goto err; } + if (php_dom_is_cache_tag_stale_from_node(&iterator->cache_tag, basenode)) { + php_dom_mark_cache_tag_up_to_date_from_node(&iterator->cache_tag, basenode); + previndex = 0; + if (basenode && (basenode->type == XML_DOCUMENT_NODE || + basenode->type == XML_HTML_DOCUMENT_NODE)) { + curnode = xmlDocGetRootElement((xmlDoc *) basenode); + } else { + curnode = basenode->children; + } + } else { + previndex = iter->index - 1; + curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node; + } curnode = dom_get_elements_by_tag_name_ns_raw( - basenode, (char *) objmap->ns, (char *) objmap->local, &previndex, iter->index); + basenode, curnode, (char *) objmap->ns, (char *) objmap->local, &previndex, iter->index); } } } else { @@ -258,7 +267,7 @@ zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, i { dom_object *intern; dom_nnodemap_object *objmap; - xmlNodePtr nodep, curnode=NULL; + xmlNodePtr curnode=NULL; int curindex = 0; HashTable *nodeht; zval *entry; @@ -270,6 +279,7 @@ zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, i } iterator = emalloc(sizeof(php_dom_iterator)); zend_iterator_init(&iterator->intern); + iterator->cache_tag.modification_nr = 0; ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &php_dom_iterator_funcs; @@ -288,24 +298,25 @@ zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, i ZVAL_COPY(&iterator->curobj, entry); } } else { - nodep = (xmlNode *)dom_object_get_node(objmap->baseobj); - if (!nodep) { + xmlNodePtr basep = (xmlNode *)dom_object_get_node(objmap->baseobj); + if (!basep) { goto err; } if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) { if (objmap->nodetype == XML_ATTRIBUTE_NODE) { - curnode = (xmlNodePtr) nodep->properties; + curnode = (xmlNodePtr) basep->properties; } else { - curnode = (xmlNodePtr) nodep->children; + curnode = (xmlNodePtr) basep->children; } } else { + xmlNodePtr nodep = basep; if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) { nodep = xmlDocGetRootElement((xmlDoc *) nodep); } else { nodep = nodep->children; } curnode = dom_get_elements_by_tag_name_ns_raw( - nodep, (char *) objmap->ns, (char *) objmap->local, &curindex, 0); + basep, nodep, (char *) objmap->ns, (char *) objmap->local, &curindex, 0); } } } else { diff --git a/ext/dom/element.c b/ext/dom/element.c index 19cef5834657a..9b8fe667707fd 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -511,7 +511,6 @@ PHP_METHOD(DOMElement, getElementsByTagName) size_t name_len; dom_object *intern, *namednode; char *name; - xmlChar *local; id = ZEND_THIS; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { @@ -522,8 +521,7 @@ PHP_METHOD(DOMElement, getElementsByTagName) php_dom_create_iterator(return_value, DOM_NODELIST); namednode = Z_DOMOBJ_P(return_value); - local = xmlCharStrndup(name, name_len); - dom_namednode_iter(intern, 0, namednode, NULL, local, NULL); + dom_namednode_iter(intern, 0, namednode, NULL, name, name_len, NULL, 0); } /* }}} end dom_element_get_elements_by_tag_name */ @@ -930,7 +928,6 @@ PHP_METHOD(DOMElement, getElementsByTagNameNS) size_t uri_len, name_len; dom_object *intern, *namednode; char *uri, *name; - xmlChar *local, *nsuri; id = ZEND_THIS; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) { @@ -941,9 +938,7 @@ PHP_METHOD(DOMElement, getElementsByTagNameNS) php_dom_create_iterator(return_value, DOM_NODELIST); namednode = Z_DOMOBJ_P(return_value); - local = xmlCharStrndup(name, name_len); - nsuri = xmlCharStrndup(uri ? uri : "", uri_len); - dom_namednode_iter(intern, 0, namednode, NULL, local, nsuri); + dom_namednode_iter(intern, 0, namednode, NULL, name, name_len, uri ? uri : "", uri_len); } /* }}} end dom_element_get_elements_by_tag_name_ns */ @@ -1234,7 +1229,7 @@ PHP_METHOD(DOMElement, prepend) } /* }}} end DOMElement::prepend */ -/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend +/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren Since: DOM Living Standard (DOM4) */ PHP_METHOD(DOMElement, replaceWith) @@ -1251,8 +1246,7 @@ PHP_METHOD(DOMElement, replaceWith) id = ZEND_THIS; DOM_GET_OBJ(context, id, xmlNodePtr, intern); - dom_parent_node_after(intern, args, argc); - dom_child_node_remove(intern); + dom_child_replace_with(intern, args, argc); } /* }}} end DOMElement::prepend */ diff --git a/ext/dom/namednodemap.c b/ext/dom/namednodemap.c index 99103ce30b7ad..dadab115a1c2a 100644 --- a/ext/dom/namednodemap.c +++ b/ext/dom/namednodemap.c @@ -142,9 +142,9 @@ PHP_METHOD(DOMNamedNodeMap, item) int count; id = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); if (index < 0 || ZEND_LONG_INT_OVFL(index)) { zend_argument_value_error(1, "must be between 0 and %d", INT_MAX); RETURN_THROWS(); diff --git a/ext/dom/node.c b/ext/dom/node.c index bc7108e087e75..d7410fc4c7eb1 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -195,6 +195,8 @@ int dom_node_node_value_write(dom_object *obj, zval *newval) break; } + php_libxml_invalidate_node_list_cache_from_doc(nodep->doc); + zend_string_release_ex(str, 0); return SUCCESS; } @@ -274,7 +276,7 @@ int dom_node_child_nodes_read(dom_object *obj, zval *retval) php_dom_create_iterator(retval, DOM_NODELIST); intern = Z_DOMOBJ_P(retval); - dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, NULL); + dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, 0, NULL, 0); return SUCCESS; } @@ -482,7 +484,7 @@ int dom_node_attributes_read(dom_object *obj, zval *retval) if (nodep->type == XML_ELEMENT_NODE) { php_dom_create_iterator(retval, DOM_NAMEDNODEMAP); intern = Z_DOMOBJ_P(retval); - dom_namednode_iter(obj, XML_ATTRIBUTE_NODE, intern, NULL, NULL, NULL); + dom_namednode_iter(obj, XML_ATTRIBUTE_NODE, intern, NULL, NULL, 0, NULL, 0); } else { ZVAL_NULL(retval); } @@ -769,17 +771,30 @@ int dom_node_text_content_write(dom_object *obj, zval *newval) return FAILURE; } - if (nodep->type == XML_ELEMENT_NODE || nodep->type == XML_ATTRIBUTE_NODE) { + php_libxml_invalidate_node_list_cache_from_doc(nodep->doc); + + const xmlChar *xmlChars = (const xmlChar *) ZSTR_VAL(str); + int type = nodep->type; + + /* We can't directly call xmlNodeSetContent, because it might encode the string through + * xmlStringLenGetNodeList for types XML_DOCUMENT_FRAG_NODE, XML_ELEMENT_NODE, XML_ATTRIBUTE_NODE. + * See tree.c:xmlNodeSetContent in libxml. + * In these cases we need to use a text node to avoid the encoding. + * For the other cases, we *can* rely on xmlNodeSetContent because it is either a no-op, or handles + * the content without encoding. */ + if (type == XML_DOCUMENT_FRAG_NODE || type == XML_ELEMENT_NODE || type == XML_ATTRIBUTE_NODE) { if (nodep->children) { node_list_unlink(nodep->children); php_libxml_node_free_list((xmlNodePtr) nodep->children); nodep->children = NULL; } + + xmlNode *textNode = xmlNewText(xmlChars); + xmlAddChild(nodep, textNode); + } else { + xmlNodeSetContent(nodep, xmlChars); } - /* we have to use xmlNodeAddContent() to get the same behavior as with xmlNewText() */ - xmlNodeSetContent(nodep, (xmlChar *) ""); - xmlNodeAddContent(nodep, (xmlChar *) ZSTR_VAL(str)); zend_string_release_ex(str, 0); return SUCCESS; @@ -886,6 +901,8 @@ PHP_METHOD(DOMNode, insertBefore) php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL); } + php_libxml_invalidate_node_list_cache_from_doc(parentp->doc); + if (ref != NULL) { DOM_GET_OBJ(refp, ref, xmlNodePtr, refpobj); if (refp->parent != parentp) { @@ -932,12 +949,20 @@ PHP_METHOD(DOMNode, insertBefore) return; } } + new_child = xmlAddPrevSibling(refp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern, childobj); - } - - if (new_child == NULL) { + dom_reconcile_ns_list(parentp->doc, new_child, last); + } else { new_child = xmlAddPrevSibling(refp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } + dom_reconcile_ns(parentp->doc, new_child); } } else { if (child->parent != NULL){ @@ -974,23 +999,28 @@ PHP_METHOD(DOMNode, insertBefore) return; } } + new_child = xmlAddChild(parentp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern, childobj); - } - if (new_child == NULL) { + dom_reconcile_ns_list(parentp->doc, new_child, last); + } else { new_child = xmlAddChild(parentp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } + dom_reconcile_ns(parentp->doc, new_child); } } - if (NULL == new_child) { - zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode"); - RETURN_THROWS(); - } - - dom_reconcile_ns(parentp->doc, new_child); - DOM_RET_OBJ(new_child, &ret, intern); - + return; +cannot_add: + zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode"); + RETURN_THROWS(); } /* }}} end dom_node_insert_before */ @@ -1055,9 +1085,10 @@ PHP_METHOD(DOMNode, replaceChild) xmlUnlinkNode(oldchild); + xmlNodePtr last = newchild->last; newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern, newchildobj); if (newchild) { - dom_reconcile_ns(nodep->doc, newchild); + dom_reconcile_ns_list(nodep->doc, newchild, last); } } else if (oldchild != newchild) { xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc); @@ -1075,6 +1106,7 @@ PHP_METHOD(DOMNode, replaceChild) nodep->doc->intSubset = (xmlDtd *) newchild; } } + php_libxml_invalidate_node_list_cache_from_doc(nodep->doc); DOM_RET_OBJ(oldchild, &ret, intern); } /* }}} end dom_node_replace_child */ @@ -1116,6 +1148,7 @@ PHP_METHOD(DOMNode, removeChild) } xmlUnlinkNode(child); + php_libxml_invalidate_node_list_cache_from_doc(nodep->doc); DOM_RET_OBJ(child, &ret, intern); } /* }}} end dom_node_remove_child */ @@ -1204,22 +1237,30 @@ PHP_METHOD(DOMNode, appendChild) php_libxml_node_free_resource((xmlNodePtr) lastattr); } } + new_child = xmlAddChild(nodep, child); + if (UNEXPECTED(new_child == NULL)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj); - } - - if (new_child == NULL) { + dom_reconcile_ns_list(nodep->doc, new_child, last); + } else { new_child = xmlAddChild(nodep, child); - if (new_child == NULL) { - // TODO Convert to Error? - php_error_docref(NULL, E_WARNING, "Couldn't append node"); - RETURN_FALSE; + if (UNEXPECTED(new_child == NULL)) { + goto cannot_add; } + dom_reconcile_ns(nodep->doc, new_child); } - dom_reconcile_ns(nodep->doc, new_child); + php_libxml_invalidate_node_list_cache_from_doc(nodep->doc); DOM_RET_OBJ(new_child, &ret, intern); + return; +cannot_add: + // TODO Convert to Error? + php_error_docref(NULL, E_WARNING, "Couldn't append node"); + RETURN_FALSE; } /* }}} end dom_node_append_child */ @@ -1328,6 +1369,8 @@ PHP_METHOD(DOMNode, normalize) DOM_GET_OBJ(nodep, id, xmlNodePtr, intern); + php_libxml_invalidate_node_list_cache_from_doc(nodep->doc); + dom_normalize(nodep); } @@ -1560,6 +1603,8 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ RETURN_THROWS(); } + php_libxml_invalidate_node_list_cache_from_doc(docp); + if (xpath_array == NULL) { if (nodep->type != XML_DOCUMENT_NODE) { ctxp = xmlXPathNewContext(docp); diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c index b03ebe1acd90a..55073b255016c 100644 --- a/ext/dom/nodelist.c +++ b/ext/dom/nodelist.c @@ -31,6 +31,24 @@ * Since: */ +static zend_always_inline void objmap_cache_release_cached_obj(dom_nnodemap_object *objmap) +{ + if (objmap->cached_obj) { + /* Since the DOM is a tree there can be no cycles. */ + if (GC_DELREF(&objmap->cached_obj->std) == 0) { + zend_objects_store_del(&objmap->cached_obj->std); + } + objmap->cached_obj = NULL; + objmap->cached_obj_index = 0; + } +} + +static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap) +{ + objmap_cache_release_cached_obj(objmap); + objmap->cached_length = -1; +} + static int get_nodelist_length(dom_object *obj) { dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr; @@ -52,6 +70,17 @@ static int get_nodelist_length(dom_object *obj) return 0; } + if (!php_dom_is_cache_tag_stale_from_node(&objmap->cache_tag, nodep)) { + if (objmap->cached_length >= 0) { + return objmap->cached_length; + } + /* Only the length is out-of-date, the cache tag is still valid. + * Therefore, only overwrite the length and keep the currently cached object. */ + } else { + php_dom_mark_cache_tag_up_to_date_from_node(&objmap->cache_tag, nodep); + reset_objmap_cache(objmap); + } + int count = 0; if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) { xmlNodePtr curnode = nodep->children; @@ -63,15 +92,18 @@ static int get_nodelist_length(dom_object *obj) } } } else { + xmlNodePtr basep = nodep; if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) { nodep = xmlDocGetRootElement((xmlDoc *) nodep); } else { nodep = nodep->children; } dom_get_elements_by_tag_name_ns_raw( - nodep, (char *) objmap->ns, (char *) objmap->local, &count, -1); + basep, nodep, (char *) objmap->ns, (char *) objmap->local, &count, INT_MAX - 1 /* because of <= */); } + objmap->cached_length = count; + return count; } @@ -113,17 +145,18 @@ PHP_METHOD(DOMNodeList, item) zval *id; zend_long index; int ret; + bool cache_itemnode = false; dom_object *intern; xmlNodePtr itemnode = NULL; dom_nnodemap_object *objmap; - xmlNodePtr nodep, curnode; + xmlNodePtr basep; int count = 0; id = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); if (index >= 0) { intern = Z_DOMOBJ_P(id); @@ -145,23 +178,51 @@ PHP_METHOD(DOMNodeList, item) return; } } else if (objmap->baseobj) { - nodep = dom_object_get_node(objmap->baseobj); - if (nodep) { + basep = dom_object_get_node(objmap->baseobj); + if (basep) { + xmlNodePtr nodep = basep; + /* For now we're only able to use cache for forward search. + * TODO: in the future we could extend the logic of the node list such that backwards searches + * are also possible. */ + bool restart = true; + int relative_index = index; + if (index >= objmap->cached_obj_index && objmap->cached_obj && !php_dom_is_cache_tag_stale_from_node(&objmap->cache_tag, nodep)) { + xmlNodePtr cached_obj_xml_node = dom_object_get_node(objmap->cached_obj); + + /* The node cannot be NULL if the cache is valid. If it is NULL, then it means we + * forgot an invalidation somewhere. Take the defensive programming approach and invalidate + * it here if it's NULL (except in debug mode where we would want to catch this). */ + if (UNEXPECTED(cached_obj_xml_node == NULL)) { +#if ZEND_DEBUG + ZEND_UNREACHABLE(); +#endif + reset_objmap_cache(objmap); + } else { + restart = false; + relative_index -= objmap->cached_obj_index; + nodep = cached_obj_xml_node; + } + } if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) { - curnode = nodep->children; - while (count < index && curnode != NULL) { + if (restart) { + nodep = nodep->children; + } + while (count < relative_index && nodep != NULL) { count++; - curnode = curnode->next; + nodep = nodep->next; } - itemnode = curnode; + itemnode = nodep; } else { - if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) { - nodep = xmlDocGetRootElement((xmlDoc *) nodep); - } else { - nodep = nodep->children; + if (restart) { + if (basep->type == XML_DOCUMENT_NODE || basep->type == XML_HTML_DOCUMENT_NODE) { + nodep = xmlDocGetRootElement((xmlDoc*) basep); + } else { + nodep = basep->children; + } } - itemnode = dom_get_elements_by_tag_name_ns_raw(nodep, (char *) objmap->ns, (char *) objmap->local, &count, index); + itemnode = dom_get_elements_by_tag_name_ns_raw(basep, nodep, (char *) objmap->ns, (char *) objmap->local, &count, relative_index); } + cache_itemnode = true; } } } @@ -169,6 +230,25 @@ PHP_METHOD(DOMNodeList, item) if (itemnode) { DOM_RET_OBJ(itemnode, &ret, objmap->baseobj); + if (cache_itemnode) { + /* Hold additional reference for the cache, must happen before releasing the cache + * because we might be the last reference holder. + * Instead of storing and copying zvals, we store the object pointer directly. + * This saves us some bytes because a pointer is smaller than a zval. + * This also means we have to manually refcount the objects here, and remove the reference count + * in reset_objmap_cache() and the destructor. */ + dom_object *cached_obj = Z_DOMOBJ_P(return_value); + GC_ADDREF(&cached_obj->std); + /* If the tag is stale, all cached data is useless. Otherwise only the cached object is useless. */ + if (php_dom_is_cache_tag_stale_from_node(&objmap->cache_tag, itemnode)) { + php_dom_mark_cache_tag_up_to_date_from_node(&objmap->cache_tag, itemnode); + reset_objmap_cache(objmap); + } else { + objmap_cache_release_cached_obj(objmap); + } + objmap->cached_obj_index = index; + objmap->cached_obj = cached_obj; + } return; } } diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c index 571da9b6411d6..555296e2ec358 100644 --- a/ext/dom/parentnode.c +++ b/ext/dom/parentnode.c @@ -124,6 +124,23 @@ int dom_parent_node_child_element_count(dom_object *obj, zval *retval) } /* }}} */ +static bool dom_is_node_in_list(const zval *nodes, int nodesc, const xmlNodePtr node_to_find) +{ + for (int i = 0; i < nodesc; i++) { + if (Z_TYPE(nodes[i]) == IS_OBJECT) { + const zend_class_entry *ce = Z_OBJCE(nodes[i]); + + if (instanceof_function(ce, dom_node_class_entry)) { + if (dom_object_get_node(Z_DOMOBJ_P(nodes + i)) == node_to_find) { + return true; + } + } + } + } + + return false; +} + xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, int nodesc) { int i; @@ -177,17 +194,16 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod goto hierarchy_request_err; } - /* - * xmlNewDocText function will always returns same address to the second parameter if the parameters are greater than or equal to three. - * If it's text, that's fine, but if it's an object, it can cause invalid pointer because many new nodes point to the same memory address. - * So we must copy the new node to avoid this situation. - */ - if (nodesc > 1) { + /* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild): + * "Add a new node to @parent, at the end of the child (or property) list merging adjacent TEXT nodes (in which case @cur is freed)". + * So we must take a copy if this situation arises to prevent a use-after-free. */ + bool will_free = newNode->type == XML_TEXT_NODE && fragment->last && fragment->last->type == XML_TEXT_NODE; + if (will_free) { newNode = xmlCopyNode(newNode, 1); } if (!xmlAddChild(fragment, newNode)) { - if (nodesc > 1) { + if (will_free) { xmlFreeNode(newNode); } goto hierarchy_request_err; @@ -201,8 +217,6 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod } else if (Z_TYPE(nodes[i]) == IS_STRING) { newNode = xmlNewDocText(documentNode, (xmlChar *) Z_STRVAL(nodes[i])); - xmlSetTreeDoc(newNode, documentNode); - if (!xmlAddChild(fragment, newNode)) { xmlFreeNode(newNode); goto hierarchy_request_err; @@ -239,10 +253,35 @@ static void dom_fragment_assign_parent_node(xmlNodePtr parentNode, xmlNodePtr fr fragment->last = NULL; } +static zend_result dom_hierarchy_node_list(xmlNodePtr parentNode, zval *nodes, int nodesc) +{ + for (int i = 0; i < nodesc; i++) { + if (Z_TYPE(nodes[i]) == IS_OBJECT) { + const zend_class_entry *ce = Z_OBJCE(nodes[i]); + + if (instanceof_function(ce, dom_node_class_entry)) { + if (dom_hierarchy(parentNode, dom_object_get_node(Z_DOMOBJ_P(nodes + i))) != SUCCESS) { + return FAILURE; + } + } + } + } + + return SUCCESS; +} + void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc) { xmlNode *parentNode = dom_object_get_node(context); xmlNodePtr newchild, prevsib; + + if (UNEXPECTED(dom_hierarchy_node_list(parentNode, nodes, nodesc) != SUCCESS)) { + php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(context->document)); + return; + } + + php_libxml_invalidate_node_list_cache_from_doc(parentNode->doc); + xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); if (fragment == NULL) { @@ -259,13 +298,14 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc) parentNode->children = newchild; } - parentNode->last = fragment->last; + xmlNodePtr last = fragment->last; + parentNode->last = last; newchild->prev = prevsib; dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(parentNode->doc, newchild); + dom_reconcile_ns_list(parentNode->doc, newchild, last); } xmlFree(fragment); @@ -280,6 +320,13 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc) return; } + if (UNEXPECTED(dom_hierarchy_node_list(parentNode, nodes, nodesc) != SUCCESS)) { + php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(context->document)); + return; + } + + php_libxml_invalidate_node_list_cache_from_doc(parentNode->doc); + xmlNodePtr newchild, nextsib; xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); @@ -291,37 +338,79 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc) nextsib = parentNode->children; if (newchild) { + xmlNodePtr last = fragment->last; parentNode->children = newchild; fragment->last->next = nextsib; - nextsib->prev = fragment->last; + nextsib->prev = last; dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(parentNode->doc, newchild); + dom_reconcile_ns_list(parentNode->doc, newchild, last); } xmlFree(fragment); } +static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr fragment) +{ + if (!insertion_point) { + /* Place it as last node */ + if (parentNode->children) { + /* There are children */ + fragment->last->prev = parentNode->last; + newchild->prev = parentNode->last->prev; + parentNode->last->next = newchild; + } else { + /* No children, because they moved out when they became a fragment */ + parentNode->children = newchild; + parentNode->last = newchild; + } + } else { + /* Insert fragment before insertion_point */ + fragment->last->next = insertion_point; + if (insertion_point->prev) { + insertion_point->prev->next = newchild; + newchild->prev = insertion_point->prev; + } + insertion_point->prev = newchild; + if (parentNode->children == insertion_point) { + parentNode->children = newchild; + } + } +} + void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc) { + /* Spec link: https://dom.spec.whatwg.org/#dom-childnode-after */ + xmlNode *prevsib = dom_object_get_node(context); xmlNodePtr newchild, parentNode; - xmlNode *fragment, *nextsib; + xmlNode *fragment; xmlDoc *doc; - bool afterlastchild; - - int stricterror = dom_get_strict_error(context->document); - if (!prevsib->parent) { - php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror); + /* Spec step 1 */ + parentNode = prevsib->parent; + /* Spec step 2 */ + if (!parentNode) { + int stricterror = dom_get_strict_error(context->document); + php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); return; } + /* Spec step 3: find first following child not in nodes; otherwise null */ + xmlNodePtr viable_next_sibling = prevsib->next; + while (viable_next_sibling) { + if (!dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) { + break; + } + viable_next_sibling = viable_next_sibling->next; + } + doc = prevsib->doc; - parentNode = prevsib->parent; - nextsib = prevsib->next; - afterlastchild = (nextsib == NULL); + + php_libxml_invalidate_node_list_cache_from_doc(doc); + + /* Spec step 4: convert nodes into fragment */ fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); if (fragment == NULL) { @@ -331,42 +420,13 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc) newchild = fragment->children; if (newchild) { - /* first node and last node are both both parameters to DOMElement::after() method so nextsib and prevsib are null. */ - if (!parentNode->children) { - prevsib = nextsib = NULL; - } else if (afterlastchild) { - /* - * The new node will be inserted after last node, prevsib is last node. - * The first node is the parameter to DOMElement::after() if parentNode->children == prevsib is true - * and prevsib does not change, otherwise prevsib is parentNode->last (first node). - */ - prevsib = parentNode->children == prevsib ? prevsib : parentNode->last; - } else { - /* - * The new node will be inserted after first node, prevsib is first node. - * The first node is not the parameter to DOMElement::after() if parentNode->children == prevsib is true - * and prevsib does not change otherwise prevsib is null to mean that parentNode->children is the new node. - */ - prevsib = parentNode->children == prevsib ? prevsib : NULL; - } + xmlNodePtr last = fragment->last; - if (prevsib) { - fragment->last->next = prevsib->next; - if (prevsib->next) { - prevsib->next->prev = fragment->last; - } - prevsib->next = newchild; - } else { - parentNode->children = newchild; - if (nextsib) { - fragment->last->next = nextsib; - nextsib->prev = fragment->last; - } - } + /* Step 5: place fragment into the parent before viable_next_sibling */ + dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment); - newchild->prev = prevsib; dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(doc, newchild); + dom_reconcile_ns_list(doc, newchild, last); } xmlFree(fragment); @@ -374,17 +434,36 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc) void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc) { + /* Spec link: https://dom.spec.whatwg.org/#dom-childnode-before */ + xmlNode *nextsib = dom_object_get_node(context); - xmlNodePtr newchild, prevsib, parentNode; - xmlNode *fragment, *afternextsib; + xmlNodePtr newchild, parentNode; + xmlNode *fragment; xmlDoc *doc; - bool beforefirstchild; - doc = nextsib->doc; - prevsib = nextsib->prev; - afternextsib = nextsib->next; + /* Spec step 1 */ parentNode = nextsib->parent; - beforefirstchild = !prevsib; + /* Spec step 2 */ + if (!parentNode) { + int stricterror = dom_get_strict_error(context->document); + php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); + return; + } + + /* Spec step 3: find first following child not in nodes; otherwise null */ + xmlNodePtr viable_previous_sibling = nextsib->prev; + while (viable_previous_sibling) { + if (!dom_is_node_in_list(nodes, nodesc, viable_previous_sibling)) { + break; + } + viable_previous_sibling = viable_previous_sibling->prev; + } + + doc = nextsib->doc; + + php_libxml_invalidate_node_list_cache_from_doc(doc); + + /* Spec step 4: convert nodes into fragment */ fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); if (fragment == NULL) { @@ -394,74 +473,66 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc) newchild = fragment->children; if (newchild) { - /* first node and last node are both both parameters to DOMElement::before() method so nextsib is null. */ - if (!parentNode->children) { - nextsib = NULL; - } else if (beforefirstchild) { - /* - * The new node will be inserted before first node, nextsib is first node and afternextsib is last node. - * The first node is not the parameter to DOMElement::before() if parentNode->children == nextsib is true - * and nextsib does not change, otherwise nextsib is the last node. - */ - nextsib = parentNode->children == nextsib ? nextsib : afternextsib; - } else { - /* - * The new node will be inserted before last node, prevsib is first node and nestsib is last node. - * The first node is not the parameter to DOMElement::before() if parentNode->children == prevsib is true - * but last node may be, so use prevsib->next to determine the value of nextsib, otherwise nextsib does not change. - */ - nextsib = parentNode->children == prevsib ? prevsib->next : nextsib; - } + xmlNodePtr last = fragment->last; - if (parentNode->children == nextsib) { - parentNode->children = newchild; + /* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */ + if (!viable_previous_sibling) { + viable_previous_sibling = parentNode->children; } else { - prevsib->next = newchild; + viable_previous_sibling = viable_previous_sibling->next; } - - fragment->last->next = nextsib; - if (nextsib) { - nextsib->prev = fragment->last; - } - - newchild->prev = prevsib; + /* Step 6: place fragment into the parent after viable_previous_sibling */ + dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment); dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(doc, newchild); + dom_reconcile_ns_list(doc, newchild, last); } xmlFree(fragment); } -void dom_child_node_remove(dom_object *context) +static zend_result dom_child_removal_preconditions(const xmlNodePtr child, int stricterror) { - xmlNode *child = dom_object_get_node(context); - xmlNodePtr children; - int stricterror; - - stricterror = dom_get_strict_error(context->document); - if (dom_node_is_read_only(child) == SUCCESS || (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) { php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror); - return; + return FAILURE; } if (!child->parent) { php_dom_throw_error(NOT_FOUND_ERR, stricterror); - return; + return FAILURE; } if (dom_node_children_valid(child->parent) == FAILURE) { - return; + return FAILURE; } - children = child->parent->children; + xmlNodePtr children = child->parent->children; if (!children) { php_dom_throw_error(NOT_FOUND_ERR, stricterror); + return FAILURE; + } + + return SUCCESS; +} + +void dom_child_node_remove(dom_object *context) +{ + xmlNode *child = dom_object_get_node(context); + xmlNodePtr children; + int stricterror; + + stricterror = dom_get_strict_error(context->document); + + if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) { return; } + children = child->parent->children; + + php_libxml_invalidate_node_list_cache_from_doc(context->document->ptr); + while (children) { if (children == child) { xmlUnlinkNode(child); @@ -473,4 +544,41 @@ void dom_child_node_remove(dom_object *context) php_dom_throw_error(NOT_FOUND_ERR, stricterror); } +void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc) +{ + xmlNodePtr child = dom_object_get_node(context); + xmlNodePtr parentNode = child->parent; + + int stricterror = dom_get_strict_error(context->document); + if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) { + return; + } + + xmlNodePtr insertion_point = child->next; + + xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); + if (UNEXPECTED(fragment == NULL)) { + return; + } + + xmlNodePtr newchild = fragment->children; + xmlDocPtr doc = parentNode->doc; + + if (newchild) { + xmlNodePtr last = fragment->last; + + /* Unlink and free it unless it became a part of the fragment. */ + if (child->parent != fragment) { + xmlUnlinkNode(child); + } + + dom_pre_insert(insertion_point, parentNode, newchild, fragment); + + dom_fragment_assign_parent_node(parentNode, fragment); + dom_reconcile_ns_list(doc, newchild, last); + } + + xmlFree(fragment); +} + #endif diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 662c6e9ef7ce1..26528056ad785 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -140,6 +140,17 @@ int dom_node_children_valid(xmlNodePtr node) { } /* }}} end dom_node_children_valid */ +static const libxml_doc_props default_doc_props = { + .formatoutput = false, + .validateonparse = false, + .resolveexternals = false, + .preservewhitespace = true, + .substituteentities = false, + .stricterror = true, + .recover = false, + .classmap = NULL, +}; + /* {{{ dom_get_doc_props() */ dom_doc_propsptr dom_get_doc_props(php_libxml_ref_obj *document) { @@ -149,28 +160,31 @@ dom_doc_propsptr dom_get_doc_props(php_libxml_ref_obj *document) return document->doc_props; } else { doc_props = emalloc(sizeof(libxml_doc_props)); - doc_props->formatoutput = 0; - doc_props->validateonparse = 0; - doc_props->resolveexternals = 0; - doc_props->preservewhitespace = 1; - doc_props->substituteentities = 0; - doc_props->stricterror = 1; - doc_props->recover = 0; - doc_props->classmap = NULL; + memcpy(doc_props, &default_doc_props, sizeof(libxml_doc_props)); if (document) { document->doc_props = doc_props; } return doc_props; } } +/* }}} */ + +libxml_doc_props const* dom_get_doc_props_read_only(const php_libxml_ref_obj *document) +{ + if (document && document->doc_props) { + return document->doc_props; + } else { + return &default_doc_props; + } +} static void dom_copy_doc_props(php_libxml_ref_obj *source_doc, php_libxml_ref_obj *dest_doc) { - dom_doc_propsptr source, dest; + dom_doc_propsptr dest; if (source_doc && dest_doc) { - source = dom_get_doc_props(source_doc); + libxml_doc_props const* source = dom_get_doc_props_read_only(source_doc); dest = dom_get_doc_props(dest_doc); dest->formatoutput = source->formatoutput; @@ -212,10 +226,8 @@ void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece zend_class_entry *dom_get_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece) { - dom_doc_propsptr doc_props; - if (document) { - doc_props = dom_get_doc_props(document); + libxml_doc_props const* doc_props = dom_get_doc_props_read_only(document); if (doc_props->classmap) { zend_class_entry *ce = zend_hash_find_ptr(doc_props->classmap, basece->name); if (ce) { @@ -230,16 +242,7 @@ zend_class_entry *dom_get_doc_classmap(php_libxml_ref_obj *document, zend_class_ /* {{{ dom_get_strict_error() */ int dom_get_strict_error(php_libxml_ref_obj *document) { - int stricterror; - dom_doc_propsptr doc_props; - - doc_props = dom_get_doc_props(document); - stricterror = doc_props->stricterror; - if (document == NULL) { - efree(doc_props); - } - - return stricterror; + return dom_get_doc_props_read_only(document)->stricterror; } /* }}} */ @@ -939,7 +942,7 @@ void dom_objects_free_storage(zend_object *object) } /* }}} */ -void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xmlHashTablePtr ht, xmlChar *local, xmlChar *ns) /* {{{ */ +void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xmlHashTablePtr ht, const char *local, size_t local_len, const char *ns, size_t ns_len) /* {{{ */ { dom_nnodemap_object *mapptr = (dom_nnodemap_object *) intern->ptr; @@ -947,11 +950,33 @@ void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xml ZVAL_OBJ_COPY(&mapptr->baseobj_zv, &basenode->std); + xmlDocPtr doc = basenode->document ? basenode->document->ptr : NULL; + mapptr->baseobj = basenode; mapptr->nodetype = ntype; mapptr->ht = ht; - mapptr->local = local; - mapptr->ns = ns; + + const xmlChar* tmp; + + if (local) { + int len = local_len > INT_MAX ? -1 : (int) local_len; + if (doc != NULL && (tmp = xmlDictExists(doc->dict, (const xmlChar *)local, len)) != NULL) { + mapptr->local = (xmlChar*) tmp; + } else { + mapptr->local = xmlCharStrndup(local, len); + mapptr->free_local = true; + } + } + + if (ns) { + int len = ns_len > INT_MAX ? -1 : (int) ns_len; + if (doc != NULL && (tmp = xmlDictExists(doc->dict, (const xmlChar *)ns, len)) != NULL) { + mapptr->ns = (xmlChar*) tmp; + } else { + mapptr->ns = xmlCharStrndup(ns, len); + mapptr->free_ns = true; + } + } } /* }}} */ @@ -1007,10 +1032,13 @@ void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */ dom_nnodemap_object *objmap = (dom_nnodemap_object *)intern->ptr; if (objmap) { - if (objmap->local) { + if (objmap->cached_obj && GC_DELREF(&objmap->cached_obj->std) == 0) { + zend_objects_store_del(&objmap->cached_obj->std); + } + if (objmap->free_local) { xmlFree(objmap->local); } - if (objmap->ns) { + if (objmap->free_ns) { xmlFree(objmap->ns); } if (!Z_ISUNDEF(objmap->baseobj_zv)) { @@ -1039,7 +1067,13 @@ zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */ objmap->nodetype = 0; objmap->ht = NULL; objmap->local = NULL; + objmap->free_local = false; objmap->ns = NULL; + objmap->free_ns = false; + objmap->cache_tag.modification_nr = 0; + objmap->cached_length = -1; + objmap->cached_obj = NULL; + objmap->cached_obj_index = 0; return &intern->std; } @@ -1217,14 +1251,25 @@ bool dom_has_feature(zend_string *feature, zend_string *version) } /* }}} end dom_has_feature */ -xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *local, int *cur, int index) /* {{{ */ +xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr basep, xmlNodePtr nodep, char *ns, char *local, int *cur, int index) /* {{{ */ { + /* Can happen with detached document */ + if (UNEXPECTED(nodep == NULL)) { + return NULL; + } + xmlNodePtr ret = NULL; + bool local_match_any = local[0] == '*' && local[1] == '\0'; + + /* Note: The spec says that ns == '' must be transformed to ns == NULL. In other words, they are equivalent. + * PHP however does not do this and internally uses the empty string everywhere when the user provides ns == NULL. + * This is because for PHP ns == NULL has another meaning: "match every namespace" instead of "match the empty namespace". */ + bool ns_match_any = ns == NULL || (ns[0] == '*' && ns[1] == '\0'); - while (nodep != NULL && (*cur <= index || index == -1)) { + while (*cur <= index) { if (nodep->type == XML_ELEMENT_NODE) { - if (xmlStrEqual(nodep->name, (xmlChar *)local) || xmlStrEqual((xmlChar *)"*", (xmlChar *)local)) { - if (ns == NULL || (!strcmp(ns, "") && nodep->ns == NULL) || (nodep->ns != NULL && (xmlStrEqual(nodep->ns->href, (xmlChar *)ns) || xmlStrEqual((xmlChar *)"*", (xmlChar *)ns)))) { + if (local_match_any || xmlStrEqual(nodep->name, (xmlChar *)local)) { + if (ns_match_any || (ns[0] == '\0' && nodep->ns == NULL) || (nodep->ns != NULL && xmlStrEqual(nodep->ns->href, (xmlChar *)ns))) { if (*cur == index) { ret = nodep; break; @@ -1232,16 +1277,33 @@ xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *l (*cur)++; } } - ret = dom_get_elements_by_tag_name_ns_raw(nodep->children, ns, local, cur, index); - if (ret != NULL) { - break; + + if (nodep->children) { + nodep = nodep->children; + continue; } } - nodep = nodep->next; + + if (nodep->next) { + nodep = nodep->next; + } else { + /* Go upwards, until we find a parent node with a next sibling, or until we hit the base. */ + do { + nodep = nodep->parent; + if (nodep == basep) { + return NULL; + } + /* This shouldn't happen, unless there's an invalidation bug somewhere. */ + if (UNEXPECTED(nodep == NULL)) { + zend_throw_error(NULL, "Current node in traversal is not in the document. Please report this as a bug in php-src."); + return NULL; + } + } while (nodep->next == NULL); + nodep = nodep->next; + } } return ret; } -/* }}} */ /* }}} end dom_element_get_elements_by_tag_name_ns_raw */ static inline bool is_empty_node(xmlNodePtr nodep) @@ -1331,38 +1393,73 @@ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) { } /* }}} end dom_set_old_ns */ -void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ +static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep) { xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL; - if (nodep->type == XML_ELEMENT_NODE) { - /* Following if block primarily used for inserting nodes created via createElementNS */ - if (nodep->nsDef != NULL) { - curns = nodep->nsDef; - while (curns) { - nsdftptr = curns->next; - if (curns->href != NULL) { - if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) && - (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) { - curns->next = NULL; - if (prevns == NULL) { - nodep->nsDef = nsdftptr; - } else { - prevns->next = nsdftptr; - } - dom_set_old_ns(doc, curns); - curns = prevns; + /* Following if block primarily used for inserting nodes created via createElementNS */ + if (nodep->nsDef != NULL) { + curns = nodep->nsDef; + while (curns) { + nsdftptr = curns->next; + if (curns->href != NULL) { + if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) && + (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) { + curns->next = NULL; + if (prevns == NULL) { + nodep->nsDef = nsdftptr; + } else { + prevns->next = nsdftptr; } + dom_set_old_ns(doc, curns); + curns = prevns; } - prevns = curns; - curns = nsdftptr; } + prevns = curns; + curns = nsdftptr; } + } +} + +void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ +{ + if (nodep->type == XML_ELEMENT_NODE) { + dom_reconcile_ns_internal(doc, nodep); xmlReconciliateNs(doc, nodep); } } /* }}} */ +static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last) +{ + ZEND_ASSERT(nodep != NULL); + while (true) { + if (nodep->type == XML_ELEMENT_NODE) { + dom_reconcile_ns_internal(doc, nodep); + if (nodep->children) { + dom_reconcile_ns_list_internal(doc, nodep->children, nodep->last /* process the whole children list */); + } + } + if (nodep == last) { + break; + } + nodep = nodep->next; + } +} + +void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last) +{ + dom_reconcile_ns_list_internal(doc, nodep, last); + /* Outside of the recursion above because xmlReconciliateNs() performs its own recursion. */ + while (true) { + xmlReconciliateNs(doc, nodep); + if (nodep == last) { + break; + } + nodep = nodep->next; + } +} + /* http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index fdfdd4e7a31ca..9e01a2ca63964 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -82,21 +82,29 @@ typedef struct _dom_nnodemap_object { dom_object *baseobj; zval baseobj_zv; int nodetype; + int cached_length; xmlHashTable *ht; xmlChar *local; xmlChar *ns; + php_libxml_cache_tag cache_tag; + dom_object *cached_obj; + int cached_obj_index; + bool free_local : 1; + bool free_ns : 1; } dom_nnodemap_object; typedef struct { zend_object_iterator intern; zval curobj; HashPosition pos; + php_libxml_cache_tag cache_tag; } php_dom_iterator; #include "domexception.h" dom_object *dom_object_get_data(xmlNodePtr obj); dom_doc_propsptr dom_get_doc_props(php_libxml_ref_obj *document); +libxml_doc_props const* dom_get_doc_props_read_only(const php_libxml_ref_obj *document); zend_object *dom_objects_new(zend_class_entry *class_type); zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type); #ifdef LIBXML_XPATH_ENABLED @@ -110,16 +118,17 @@ int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, i xmlNsPtr dom_get_ns(xmlNodePtr node, char *uri, int *errorcode, char *prefix); void dom_set_old_ns(xmlDoc *doc, xmlNs *ns); void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep); +void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last); xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName); void dom_normalize (xmlNodePtr nodep); -xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *local, int *cur, int index); +xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr basep, xmlNodePtr nodep, char *ns, char *local, int *cur, int index); void php_dom_create_implementation(zval *retval); int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child); bool dom_has_feature(zend_string *feature, zend_string *version); int dom_node_is_read_only(xmlNodePtr node); int dom_node_children_valid(xmlNodePtr node); void php_dom_create_iterator(zval *return_value, int ce_type); -void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xmlHashTablePtr ht, xmlChar *local, xmlChar *ns); +void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xmlHashTablePtr ht, const char *local, size_t local_len, const char *ns, size_t ns_len); xmlNodePtr create_notation(const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID); xmlNode *php_dom_libxml_hash_iter(xmlHashTable *ht, int index); xmlNode *php_dom_libxml_notation_iter(xmlHashTable *ht, int index); @@ -131,6 +140,7 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc); void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc); void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc); void dom_child_node_remove(dom_object *context); +void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc); #define DOM_GET_OBJ(__ptr, __id, __prtype, __intern) { \ __intern = Z_DOMOBJ_P(__id); \ @@ -152,6 +162,33 @@ void dom_child_node_remove(dom_object *context); #define DOM_NODELIST 0 #define DOM_NAMEDNODEMAP 1 +static zend_always_inline bool php_dom_is_cache_tag_stale_from_doc_ptr(const php_libxml_cache_tag *cache_tag, const php_libxml_doc_ptr *doc_ptr) +{ + ZEND_ASSERT(cache_tag != NULL); + ZEND_ASSERT(doc_ptr != NULL); + /* See overflow comment in php_libxml_invalidate_node_list_cache(). */ +#if SIZEOF_SIZE_T == 8 + return cache_tag->modification_nr != doc_ptr->cache_tag.modification_nr; +#else + return cache_tag->modification_nr != doc_ptr->cache_tag.modification_nr || UNEXPECTED(doc_ptr->cache_tag.modification_nr == SIZE_MAX); +#endif +} + +static zend_always_inline bool php_dom_is_cache_tag_stale_from_node(const php_libxml_cache_tag *cache_tag, const xmlNodePtr node) +{ + ZEND_ASSERT(node != NULL); + return !node->doc || !node->doc->_private || php_dom_is_cache_tag_stale_from_doc_ptr(cache_tag, node->doc->_private); +} + +static zend_always_inline void php_dom_mark_cache_tag_up_to_date_from_node(php_libxml_cache_tag *cache_tag, const xmlNodePtr node) +{ + ZEND_ASSERT(cache_tag != NULL); + if (node->doc && node->doc->_private) { + const php_libxml_doc_ptr* doc_ptr = node->doc->_private; + cache_tag->modification_nr = doc_ptr->cache_tag.modification_nr; + } +} + PHP_MINIT_FUNCTION(dom); PHP_MSHUTDOWN_FUNCTION(dom); PHP_MINFO_FUNCTION(dom); diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 21886e5015084..0ef5c4e5cf0bf 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -467,7 +467,7 @@ public function count(): int {} public function getIterator(): Iterator {} - /** @return DOMNode|DOMNameSpaceNode|null */ + /** @return DOMElement|DOMNode|DOMNameSpaceNode|null */ public function item(int $index) {} } diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index d1234f1404011..3a3fb9afd82e4 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 9a6a0c2a5626466aa6397f0892ee5b08ec335e14 */ + * Stub hash: 060166e1fd79f7447f0eaafb626b33e12791e93b */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) diff --git a/ext/dom/processinginstruction.c b/ext/dom/processinginstruction.c index 465ecb431e73a..c40d24d18ce23 100644 --- a/ext/dom/processinginstruction.c +++ b/ext/dom/processinginstruction.c @@ -128,6 +128,8 @@ int dom_processinginstruction_data_write(dom_object *obj, zval *newval) return FAILURE; } + php_libxml_invalidate_node_list_cache_from_doc(nodep->doc); + xmlNodeSetContentLen(nodep, (xmlChar *) ZSTR_VAL(str), ZSTR_LEN(str) + 1); zend_string_release_ex(str, 0); diff --git a/ext/dom/tests/DOMDocument_getElementsByTagNameNS_match_any_namespace.phpt b/ext/dom/tests/DOMDocument_getElementsByTagNameNS_match_any_namespace.phpt new file mode 100644 index 0000000000000..888d1ef9b8057 --- /dev/null +++ b/ext/dom/tests/DOMDocument_getElementsByTagNameNS_match_any_namespace.phpt @@ -0,0 +1,82 @@ +--TEST-- +DOMDocument::getElementsByTagNameNS() match any namespace +--EXTENSIONS-- +dom +--FILE-- + + +Books of the other guy.. + + + + xinclude: book.xml not found + + + + This is another namespace + + + +EOD; +$dom = new DOMDocument; + +// load the XML string defined above +$dom->loadXML($xml); + +function test($namespace, $local) { + global $dom; + $namespace_str = $namespace !== NULL ? "'$namespace'" : "null"; + echo "-- getElementsByTagNameNS($namespace_str, '$local') --\n"; + foreach ($dom->getElementsByTagNameNS($namespace, $local) as $element) { + echo 'local name: \'', $element->localName, '\', prefix: \'', $element->prefix, "'\n"; + } +} + +// Should *also* include objects even without a namespace +test(null, '*'); +// Should *also* include objects even without a namespace +test('*', '*'); +// Should *only* include objects without a namespace +test('', '*'); +// Should *only* include objects with the specified namespace +test('http://www.w3.org/2001/XInclude', '*'); +// Should not give any output +test('', 'fallback'); +// Should not give any output, because the null namespace is the same as the empty namespace +test(null, 'fallback'); +// Should only output the include from the empty namespace +test(null, 'include'); + +?> +--EXPECT-- +-- getElementsByTagNameNS(null, '*') -- +local name: 'chapter', prefix: '' +local name: 'title', prefix: '' +local name: 'para', prefix: '' +local name: 'error', prefix: '' +local name: 'include', prefix: '' +-- getElementsByTagNameNS('*', '*') -- +local name: 'chapter', prefix: '' +local name: 'title', prefix: '' +local name: 'para', prefix: '' +local name: 'include', prefix: 'xi' +local name: 'fallback', prefix: 'xi' +local name: 'error', prefix: '' +local name: 'include', prefix: '' +-- getElementsByTagNameNS('', '*') -- +local name: 'chapter', prefix: '' +local name: 'title', prefix: '' +local name: 'para', prefix: '' +local name: 'error', prefix: '' +local name: 'include', prefix: '' +-- getElementsByTagNameNS('http://www.w3.org/2001/XInclude', '*') -- +local name: 'include', prefix: 'xi' +local name: 'fallback', prefix: 'xi' +-- getElementsByTagNameNS('', 'fallback') -- +-- getElementsByTagNameNS(null, 'fallback') -- +-- getElementsByTagNameNS(null, 'include') -- +local name: 'include', prefix: '' diff --git a/ext/dom/tests/DOMDocument_getElementsByTagName_liveness.phpt b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness.phpt new file mode 100644 index 0000000000000..2b4622d10d389 --- /dev/null +++ b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness.phpt @@ -0,0 +1,47 @@ +--TEST-- +DOMDocument::getElementsByTagName() is live +--EXTENSIONS-- +dom +--FILE-- +loadXML( '' ); +$root = $doc->documentElement; + +$i = 0; + +/* Note that the list is live. The explanation for the output is as follows: + Before the loop we have the following (writing only the attributes): + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + + Now the loop starts, the current element is marked with a V. $i == 0: + V + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + 1 gets printed. $i == 0, which is even, so 1 gets removed, which results in: + V + 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + Note that everything shifted to the left. + Because the list is live, the current element pointer still refers to the first index, which now corresponds to element with attribute 2. + Now the foreach body ends, which means we go to the next element, which is now 3 instead of 2. + V + 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + 3 gets printed. $i == 1, which is odd, so nothing happens and we move on to the next element: + V + 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + 4 gets printed. $i == 2, which is even, so 4 gets removed, which results in: + V + 2 3 5 6 7 8 9 10 11 12 13 14 15 + Note again everything shifted to the left. + Now the foreach body ends, which means we go to the next element, which is now 6 instead of 5. + V + 2 3 5 6 7 8 9 10 11 12 13 14 15 + 6 gets printed, etc... */ +foreach ($doc->getElementsByTagName('e') as $node) { + print $node->getAttribute('i') . ' '; + if ($i++ % 2 == 0) + $root->removeChild($node); +} +print "\n"; +?> +--EXPECT-- +1 3 4 6 7 9 10 12 13 15 diff --git a/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_simplexml.phpt b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_simplexml.phpt new file mode 100644 index 0000000000000..0ac52cd5d662f --- /dev/null +++ b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_simplexml.phpt @@ -0,0 +1,29 @@ +--TEST-- +DOMDocument::getElementsByTagName() liveness with simplexml_import_dom +--EXTENSIONS-- +dom +simplexml +--FILE-- +loadXML( '' ); +$list = $doc->getElementsByTagName('e'); +print $list->item(5)->getAttribute('i')."\n"; +echo "before import\n"; +$s = simplexml_import_dom($doc->documentElement); +echo "after import\n"; + +unset($s->e[5]); +print $list->item(5)->getAttribute('i')."\n"; + +unset($s->e[5]); +print $list->item(5)->getAttribute('i')."\n"; + +?> +--EXPECT-- +6 +before import +after import +7 +8 diff --git a/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_tree_walk.phpt b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_tree_walk.phpt new file mode 100644 index 0000000000000..91d810df51bc6 --- /dev/null +++ b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_tree_walk.phpt @@ -0,0 +1,89 @@ +--TEST-- +DOMDocument::getElementsByTagName() liveness tree walk +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + +echo "-- On first child, for --\n"; +$list = $doc->documentElement->firstChild->getElementsByTagName('b'); +var_dump($list->length); +for ($i = 0; $i < $list->length; $i++) { + echo $i, " ", $list->item($i)->getAttribute('i'), "\n"; +} +// Try to access one beyond to check if we don't get excess elements +var_dump($list->item($i)); + +echo "-- On first child, foreach --\n"; +foreach ($list as $item) { + echo $item->getAttribute('i'), "\n"; +} + +echo "-- On document, for --\n"; +$list = $doc->getElementsByTagName('b'); +var_dump($list->length); +for ($i = 0; $i < $list->length; $i++) { + echo $i, " ", $list->item($i)->getAttribute('i'), "\n"; +} +// Try to access one beyond to check if we don't get excess elements +var_dump($list->item($i)); + +echo "-- On document, foreach --\n"; +foreach ($list as $item) { + echo $item->getAttribute('i'), "\n"; +} + +echo "-- On document, after caching followed by removing --\n"; + +$list = $doc->documentElement->firstChild->getElementsByTagName('b'); +$list->item(0); // Activate item cache +$list->item(0)->remove(); +$list->item(0)->remove(); +$list->item(0)->remove(); +var_dump($list->length); +var_dump($list->item(0)); +foreach ($list as $item) { + echo "Should not execute\n"; +} + +echo "-- On document, clean list after removal --\n"; +$list = $doc->documentElement->firstChild->getElementsByTagName('b'); +var_dump($list->length); +var_dump($list->item(0)); +foreach ($list as $item) { + echo "Should not execute\n"; +} + +?> +--EXPECT-- +-- On first child, for -- +int(3) +0 1 +1 2 +2 3 +NULL +-- On first child, foreach -- +1 +2 +3 +-- On document, for -- +int(4) +0 1 +1 2 +2 3 +3 4 +NULL +-- On document, foreach -- +1 +2 +3 +4 +-- On document, after caching followed by removing -- +int(0) +NULL +-- On document, clean list after removal -- +int(0) +NULL diff --git a/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_write_properties.phpt b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_write_properties.phpt new file mode 100644 index 0000000000000..af8af51844c9d --- /dev/null +++ b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_write_properties.phpt @@ -0,0 +1,43 @@ +--TEST-- +DOMDocument::getElementsByTagName() liveness affected by writing properties +--EXTENSIONS-- +dom +--FILE-- +'; +$fields = ['nodeValue', 'textContent']; + +foreach ($fields as $field) { + $doc = new DOMDocument; + $doc->loadXML($xml); + $list = $doc->getElementsByTagName('a'); + var_dump($list->item(0) === NULL); + $doc->documentElement->{$field} = 'new_content'; + var_dump($list->item(0) === NULL); + print $doc->saveXML(); +} + +// Shouldn't be affected +$doc = new DOMDocument; +$doc->loadXML($xml); +$list = $doc->getElementsByTagNameNS('foo', 'a'); +var_dump($list->item(0) === NULL); +$doc->documentElement->firstChild->prefix = 'ns2'; +var_dump($list->item(0) === NULL); +print $doc->saveXML(); + +?> +--EXPECT-- +bool(false) +bool(true) + +new_content +bool(false) +bool(true) + +new_content +bool(false) +bool(false) + + diff --git a/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_xinclude.phpt b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_xinclude.phpt new file mode 100644 index 0000000000000..2c14a2080569e --- /dev/null +++ b/ext/dom/tests/DOMDocument_getElementsByTagName_liveness_xinclude.phpt @@ -0,0 +1,43 @@ +--TEST-- +DOMDocument::getElementsByTagName() liveness with DOMDocument::xinclude() +--EXTENSIONS-- +dom +--FILE-- + + +

Hello

+ + + +

xinclude: book.xml not found

+
+
+
+
+EOD; + +$dom = new DOMDocument; +$dom->loadXML($xml); +$elements = $dom->getElementsByTagName('p'); +var_dump($elements->item(0)->textContent); +@$dom->xinclude(); +var_dump($elements->item(1)->textContent); +echo $dom->saveXML(); + +?> +--EXPECT-- +string(5) "Hello" +string(28) "xinclude: book.xml not found" + + +

Hello

+ + +

xinclude: book.xml not found

+ +
+
diff --git a/ext/dom/tests/DOMDocument_item_cache_invalidation.phpt b/ext/dom/tests/DOMDocument_item_cache_invalidation.phpt new file mode 100644 index 0000000000000..dad532b8167fe --- /dev/null +++ b/ext/dom/tests/DOMDocument_item_cache_invalidation.phpt @@ -0,0 +1,69 @@ +--TEST-- +DOMDocument node list item cache invalidation +--EXTENSIONS-- +dom +--FILE-- +loadHTML('

hello

world

'); + +$elements = $doc->getElementsByTagName('p'); +$elements->item(0); // Activate item cache +$doc->loadHTML('

A

B

C

'); +var_dump($elements); +var_dump($elements->item(0)->textContent); // First lookup +var_dump($elements->item(2)->textContent); // Uses cache +var_dump($elements->item(1)->textContent); // Does not use cache + +echo "-- Remove cached item test --\n"; + +$doc = new DOMDocument(); +$doc->loadHTML('

hello

world

!

'); + +$elements = $doc->getElementsByTagName('p'); +$item = $elements->item(0); // Activate item cache +var_dump($item->textContent); +$item->remove(); +// Now element 0 means "world", and 1 means "!" +unset($item); +$item = $elements->item(1); +var_dump($item->textContent); + +echo "-- Removal of cached item in loop test --\n"; + +$doc = new DOMDocument; +$doc->loadXML( '' ); +$root = $doc->documentElement; + +$i = 0; +$elements = $root->getElementsByTagName('e'); +for ($i = 0; $i < 11; $i++) { + $node = $elements->item($i); + print $node->getAttribute('i') . ' '; + if ($i++ % 2 == 0) + $root->removeChild( $node ); +} +print "\n"; + +?> +--EXPECTF-- +-- Switch document test -- +object(DOMNodeList)#2 (1) { + ["length"]=> + int(3) +} +string(1) "A" +string(1) "C" +string(1) "B" +-- Remove cached item test -- +string(5) "hello" +string(1) "!" +-- Removal of cached item in loop test -- +1 4 7 10 13 +Fatal error: Uncaught Error: Call to a member function getAttribute() on null in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/ext/dom/tests/DOMDocument_length_cache_invalidation.phpt b/ext/dom/tests/DOMDocument_length_cache_invalidation.phpt new file mode 100644 index 0000000000000..7a3633894a381 --- /dev/null +++ b/ext/dom/tests/DOMDocument_length_cache_invalidation.phpt @@ -0,0 +1,34 @@ +--TEST-- +DOMDocument node list length cache invalidation +--EXTENSIONS-- +dom +--FILE-- +loadHTML('

hello

world

!

'); + +$elements = $doc->getElementsByTagName('p'); +$item = $elements->item(0); // Activate item cache +var_dump($elements->length); // Length not cached yet, should still compute +$item->remove(); +// Now element 0 means "world", and 1 means "!" +unset($item); +var_dump($elements->length); +$item = $elements->item(1); +var_dump($item->textContent); +$item = $elements->item(1); +var_dump($item->textContent); +$item = $elements->item(0); +var_dump($item->textContent); +$item = $elements->item(1); +var_dump($item->textContent); + +?> +--EXPECT-- +int(3) +int(2) +string(1) "!" +string(1) "!" +string(5) "world" +string(1) "!" diff --git a/ext/dom/tests/DOMDocument_liveness_caching_invalidation.phpt b/ext/dom/tests/DOMDocument_liveness_caching_invalidation.phpt new file mode 100644 index 0000000000000..e05bd1ac6f646 --- /dev/null +++ b/ext/dom/tests/DOMDocument_liveness_caching_invalidation.phpt @@ -0,0 +1,43 @@ +--TEST-- +DOMDocument liveness caching invalidation by textContent +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); +$root = $doc->documentElement; + +$i = 0; + +echo "-- Overwrite during iteration --\n"; + +foreach ($doc->getElementsByTagName('e') as $node) { + if ($i++ == 2) { + $root->textContent = 'overwrite'; + } + var_dump($node->tagName, $node->getAttribute('id')); +} + +echo "-- Empty iteration --\n"; +foreach ($doc->getElementsByTagName('e') as $node) { + echo "Should not execute\n"; +} + +echo "-- After adding an element again --\n"; +$root->appendChild(new DOMElement('e')); +foreach ($doc->getElementsByTagName('e') as $node) { + echo "Should execute once\n"; +} +?> +--EXPECT-- +-- Overwrite during iteration -- +string(1) "e" +string(1) "1" +string(1) "e" +string(1) "2" +string(1) "e" +string(1) "3" +-- Empty iteration -- +-- After adding an element again -- +Should execute once diff --git a/ext/dom/tests/DOMElement_append_hierarchy_test.phpt b/ext/dom/tests/DOMElement_append_hierarchy_test.phpt new file mode 100644 index 0000000000000..2d70b10fe9f70 --- /dev/null +++ b/ext/dom/tests/DOMElement_append_hierarchy_test.phpt @@ -0,0 +1,89 @@ +--TEST-- +DOMElement::append() with hierarchy changes and errors +--EXTENSIONS-- +dom +--FILE-- +loadXML('

helloworld

'); + +echo "-- Append hello with world --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_hello->append($b_world); +var_dump($dom->saveHTML()); + +echo "-- Append hello with world's child --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_hello->append($b_world->firstChild); +var_dump($dom->saveHTML()); + +echo "-- Append world's child with hello --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_world->firstChild->append($b_hello); +var_dump($dom->saveHTML()); + +echo "-- Append hello with itself --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +try { + $b_hello->append($b_hello); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->saveHTML()); + +echo "-- Append world's i tag with the parent --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +try { + $b_world->firstChild->append($b_world); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->saveHTML()); + +echo "-- Append from another document --\n"; +$dom = clone $dom_original; +$dom2 = new DOMDocument; +$dom2->loadXML('

other

'); +try { + $dom->firstChild->firstChild->prepend($dom2->firstChild); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom2->saveHTML()); +var_dump($dom->saveHTML()); + +?> +--EXPECT-- +-- Append hello with world -- +string(39) "

helloworld

+" +-- Append hello with world's child -- +string(39) "

helloworld

+" +-- Append world's child with hello -- +string(39) "

worldhello

+" +-- Append hello with itself -- +Hierarchy Request Error +string(39) "

helloworld

+" +-- Append world's i tag with the parent -- +Hierarchy Request Error +string(39) "

helloworld

+" +-- Append from another document -- +Wrong Document Error +string(13) "

other

+" +string(39) "

helloworld

+" diff --git a/ext/dom/tests/DOMElement_getElementsByTagName_without_document.phpt b/ext/dom/tests/DOMElement_getElementsByTagName_without_document.phpt new file mode 100644 index 0000000000000..9aebf3139cdf9 --- /dev/null +++ b/ext/dom/tests/DOMElement_getElementsByTagName_without_document.phpt @@ -0,0 +1,16 @@ +--TEST-- +Node list cache should not break on DOMElement::getElementsByTagName() without document +--EXTENSIONS-- +dom +--FILE-- +getElementsByTagName("b") as $x) { + var_dump($x); +} + +?> +Done +--EXPECT-- +Done diff --git a/ext/dom/tests/DOMElement_prepend_hierarchy_test.phpt b/ext/dom/tests/DOMElement_prepend_hierarchy_test.phpt new file mode 100644 index 0000000000000..4d9cf24a61828 --- /dev/null +++ b/ext/dom/tests/DOMElement_prepend_hierarchy_test.phpt @@ -0,0 +1,89 @@ +--TEST-- +DOMElement::prepend() with hierarchy changes and errors +--EXTENSIONS-- +dom +--FILE-- +loadXML('

helloworld

'); + +echo "-- Prepend hello with world --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_hello->prepend($b_world); +var_dump($dom->saveHTML()); + +echo "-- Prepend hello with world's child --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_hello->prepend($b_world->firstChild); +var_dump($dom->saveHTML()); + +echo "-- Prepend world's child with hello --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_world->firstChild->prepend($b_hello); +var_dump($dom->saveHTML()); + +echo "-- Prepend hello with itself --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +try { + $b_hello->prepend($b_hello); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->saveHTML()); + +echo "-- Prepend world's i tag with the parent --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +try { + $b_world->firstChild->prepend($b_world); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->saveHTML()); + +echo "-- Append from another document --\n"; +$dom = clone $dom_original; +$dom2 = new DOMDocument; +$dom2->loadXML('

other

'); +try { + $dom->firstChild->firstChild->prepend($dom2->firstChild); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom2->saveHTML()); +var_dump($dom->saveHTML()); + +?> +--EXPECT-- +-- Prepend hello with world -- +string(39) "

worldhello

+" +-- Prepend hello with world's child -- +string(39) "

worldhello

+" +-- Prepend world's child with hello -- +string(39) "

helloworld

+" +-- Prepend hello with itself -- +Hierarchy Request Error +string(39) "

helloworld

+" +-- Prepend world's i tag with the parent -- +Hierarchy Request Error +string(39) "

helloworld

+" +-- Append from another document -- +Wrong Document Error +string(13) "

other

+" +string(39) "

helloworld

+" diff --git a/ext/dom/tests/bug67440.phpt b/ext/dom/tests/bug67440.phpt new file mode 100644 index 0000000000000..3e30f69b9ae4d --- /dev/null +++ b/ext/dom/tests/bug67440.phpt @@ -0,0 +1,151 @@ +--TEST-- +Bug #67440 (append_node of a DOMDocumentFragment does not reconcile namespaces) +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + $fragment = $document->createDocumentFragment(); + $fragment->appendChild($document->createTextNode("\n")); + $fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '1')); + $fragment->appendChild($document->createTextNode("\n")); + $fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '2')); + $fragment->appendChild($document->createTextNode("\n")); + return array($document, $fragment); +} + +function case1($method) { + list($document, $fragment) = createDocument(); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +function case2($method) { + list($document, $fragment) = createDocument(); + $childNodes = iterator_to_array($fragment->childNodes); + foreach ($childNodes as $childNode) { + $document->documentElement->{$method}($childNode); + } + echo $document->saveXML(); +} + +function case3($method) { + list($document, $fragment) = createDocument(); + $fragment->removeChild($fragment->firstChild); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +function case4($method) { + list($document, $fragment) = createDocument(); + $fragment->childNodes[1]->appendChild($document->createElementNS('http://example/ns2', 'myns2:childNode', '3')); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +echo "== appendChild ==\n"; +echo "-- fragment to document element --\n"; case1('appendChild'); echo "\n"; +echo "-- children manually document element --\n"; case2('appendChild'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('appendChild'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('appendChild'); echo "\n"; + +echo "== insertBefore ==\n"; +echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n"; +echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n"; + +echo "== insertAfter ==\n"; +echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n"; +echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n"; + +?> +--EXPECT-- +== appendChild == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 + + +== insertBefore == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 + + +== insertAfter == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 + diff --git a/ext/dom/tests/bug77686.phpt b/ext/dom/tests/bug77686.phpt new file mode 100644 index 0000000000000..ddd7c3364786c --- /dev/null +++ b/ext/dom/tests/bug77686.phpt @@ -0,0 +1,40 @@ +--TEST-- +Bug #77686 (Removed elements are still returned by getElementById) +--EXTENSIONS-- +dom +--FILE-- +loadHTML('before
hello
after'); +$body = $doc->getElementById('x'); +$div = $doc->getElementById('y'); +var_dump($doc->getElementById('y')->textContent); + +// Detached from document, should not find it anymore +$body->removeChild($div); +var_dump($doc->getElementById('y')); + +// Added again, should find it +$body->appendChild($div); +var_dump($doc->getElementById('y')->textContent); + +// Should find root element without a problem +var_dump($doc->getElementById('htmlelement')->textContent); + +// Created element but not yet attached, should not find it before it is added +$new_element = $doc->createElement('p'); +$new_element->textContent = 'my new text'; +$new_element->setAttribute('id', 'myp'); +var_dump($doc->getElementById('myp')); +$body->appendChild($new_element); +var_dump($doc->getElementById('myp')->textContent); + +?> +--EXPECT-- +string(5) "hello" +NULL +string(5) "hello" +string(16) "beforeafterhello" +NULL +string(11) "my new text" diff --git a/ext/dom/tests/bug80602.phpt b/ext/dom/tests/bug80602.phpt index 9f041f686f516..844d829cb08d0 100644 --- a/ext/dom/tests/bug80602.phpt +++ b/ext/dom/tests/bug80602.phpt @@ -8,84 +8,84 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "1 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "2 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "3 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($doc->documentElement->firstChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "4 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target, $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "5 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($doc->documentElement->lastChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "6 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($target, $doc->documentElement->firstChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "7 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($doc->documentElement->firstChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "8 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before('bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "9 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before('bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "10 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target, 'bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "11 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before('bar', $target, 'baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "12 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before('bar', 'baz', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "13 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -93,19 +93,19 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($target, 'bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "14 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before('bar', $target, 'baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "15 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before('bar', 'baz', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "16 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -113,21 +113,21 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before('bar', $target, $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "17 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target, 'bar', $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "18 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target, $doc->documentElement->lastChild, 'bar'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "19 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -136,43 +136,43 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before('bar', $doc->documentElement->firstChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "20 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($doc->documentElement->firstChild, 'bar', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "21 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($doc->documentElement->firstChild, $target, 'bar'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "22 ", $doc->saveXML($doc->documentElement).PHP_EOL; ?> --EXPECTF-- -foo -foo -foo -foo -foo -foo -foo -foo -barbazfoo -foobarbaz -foobarbaz -barfoobaz -barbazfoo -foobarbaz -foobarbaz -foobarbaz -barfoo -foobar -foobar -barfoo -foobar -foobar +1 foo +2 foo +3 foo +4 foo +5 foo +6 foo +7 foo +8 foo +9 barbazfoo +10 foobarbaz +11 foobarbaz +12 barfoobaz +13 barbazfoo +14 foobarbaz +15 foobarbaz +16 foobarbaz +17 barfoo +18 foobar +19 foobar +20 barfoo +21 foobar +22 foobar diff --git a/ext/dom/tests/bug80602_2.phpt b/ext/dom/tests/bug80602_2.phpt index 1151417c0f845..7c5070f51424c 100644 --- a/ext/dom/tests/bug80602_2.phpt +++ b/ext/dom/tests/bug80602_2.phpt @@ -8,84 +8,84 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "1 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "2 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "3 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($doc->documentElement->firstChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "4 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target, $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "5 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($doc->documentElement->lastChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "6 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($target, $doc->documentElement->firstChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "7 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($doc->documentElement->firstChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "8 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after('bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "9 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after('bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "10 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target, 'bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "11 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after('bar', $target, 'baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "12 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after('bar', 'baz', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "13 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -93,19 +93,19 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($target, 'bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "14 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after('bar', $target, 'baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "15 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after('bar', 'baz', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "16 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -113,21 +113,21 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after('bar', $target, $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "17 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target, 'bar', $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "18 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target, $doc->documentElement->lastChild, 'bar'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "19 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -136,43 +136,43 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after('bar', $doc->documentElement->firstChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "20 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($doc->documentElement->firstChild, 'bar', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "21 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($doc->documentElement->firstChild, $target, 'bar'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "22 ", $doc->saveXML($doc->documentElement).PHP_EOL; ?> --EXPECTF-- -foo -foo -foo -foo -foo -foo -foo -foo -foobarbaz -foobarbaz -foobarbaz -barfoobaz -barbazfoo -foobarbaz -foobarbaz -foobarbaz -barfoo -foobar -foobar -barfoo -foobar -foobar +1 foo +2 foo +3 foo +4 foo +5 foo +6 foo +7 foo +8 foo +9 foobarbaz +10 foobarbaz +11 foobarbaz +12 barfoobaz +13 barbazfoo +14 foobarbaz +15 foobarbaz +16 foobarbaz +17 barfoo +18 foobar +19 foobar +20 barfoo +21 foobar +22 foobar diff --git a/ext/dom/tests/bug80602_3.phpt b/ext/dom/tests/bug80602_3.phpt new file mode 100644 index 0000000000000..f9bf67e778da5 --- /dev/null +++ b/ext/dom/tests/bug80602_3.phpt @@ -0,0 +1,120 @@ +--TEST-- +Bug #80602 (Segfault when using DOMChildNode::before()) - use-after-free variation +--FILE-- +loadXML('foo'); +$target = $doc->documentElement->lastChild; +$target->before('bar', $doc->documentElement->firstChild, 'baz'); +echo $doc->saveXML($doc->documentElement), "\n"; +var_dump($target); + +$doc = new \DOMDocument(); +$doc->loadXML('foo'); +$target = $doc->documentElement->lastChild; +// Note: after instead of before +$target->after('bar', $doc->documentElement->firstChild, 'baz'); +echo $doc->saveXML($doc->documentElement), "\n"; +var_dump($target); + +?> +--EXPECTF-- +barfoobaz +object(DOMElement)#3 (23) { + ["schemaTypeInfo"]=> + NULL + ["tagName"]=> + string(4) "last" + ["firstElementChild"]=> + NULL + ["lastElementChild"]=> + NULL + ["childElementCount"]=> + int(0) + ["previousElementSibling"]=> + NULL + ["nextElementSibling"]=> + NULL + ["nodeName"]=> + string(4) "last" + ["nodeValue"]=> + string(0) "" + ["nodeType"]=> + int(1) + ["parentNode"]=> + string(22) "(object value omitted)" + ["childNodes"]=> + string(22) "(object value omitted)" + ["firstChild"]=> + NULL + ["lastChild"]=> + NULL + ["previousSibling"]=> + string(22) "(object value omitted)" + ["nextSibling"]=> + NULL + ["attributes"]=> + string(22) "(object value omitted)" + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["namespaceURI"]=> + NULL + ["prefix"]=> + string(0) "" + ["localName"]=> + string(4) "last" + ["baseURI"]=> + string(%d) %s + ["textContent"]=> + string(0) "" +} +barfoobaz +object(DOMElement)#2 (23) { + ["schemaTypeInfo"]=> + NULL + ["tagName"]=> + string(4) "last" + ["firstElementChild"]=> + NULL + ["lastElementChild"]=> + NULL + ["childElementCount"]=> + int(0) + ["previousElementSibling"]=> + NULL + ["nextElementSibling"]=> + NULL + ["nodeName"]=> + string(4) "last" + ["nodeValue"]=> + string(0) "" + ["nodeType"]=> + int(1) + ["parentNode"]=> + string(22) "(object value omitted)" + ["childNodes"]=> + string(22) "(object value omitted)" + ["firstChild"]=> + NULL + ["lastChild"]=> + NULL + ["previousSibling"]=> + NULL + ["nextSibling"]=> + string(22) "(object value omitted)" + ["attributes"]=> + string(22) "(object value omitted)" + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["namespaceURI"]=> + NULL + ["prefix"]=> + string(0) "" + ["localName"]=> + string(4) "last" + ["baseURI"]=> + string(%d) %s + ["textContent"]=> + string(0) "" +} diff --git a/ext/dom/tests/bug80602_4.phpt b/ext/dom/tests/bug80602_4.phpt new file mode 100644 index 0000000000000..a1df8d10caa31 --- /dev/null +++ b/ext/dom/tests/bug80602_4.phpt @@ -0,0 +1,33 @@ +--TEST-- +Bug #80602 (Segfault when using DOMChildNode::before()) - after text merge variation +--FILE-- +loadXML('foobar'); +$foo = $doc->firstChild->firstChild; +$bar = $doc->firstChild->lastChild; + +$foo->after($bar); + +var_dump($doc->saveXML()); + +$foo->nodeValue = "x"; + +var_dump($doc->saveXML()); + +$bar->nodeValue = "y"; + +var_dump($doc->saveXML()); + +?> +--EXPECT-- +string(43) " +foobar +" +string(41) " +xbar +" +string(39) " +xy +" diff --git a/ext/dom/tests/bug81642.phpt b/ext/dom/tests/bug81642.phpt new file mode 100644 index 0000000000000..7bf3dde50588e --- /dev/null +++ b/ext/dom/tests/bug81642.phpt @@ -0,0 +1,49 @@ +--TEST-- +Bug #81642 (DOMChildNode::replaceWith() bug when replacing a node with itself) +--EXTENSIONS-- +dom +--FILE-- +appendChild($target = $doc->createElement('test')); +$target->replaceWith($target); +var_dump($doc->saveXML()); + +// Replace with itself + another element +$doc = new DOMDocument(); +$doc->appendChild($target = $doc->createElement('test')); +$target->replaceWith($target, $doc->createElement('foo')); +var_dump($doc->saveXML()); + +// Replace with text node +$doc = new DOMDocument(); +$doc->appendChild($target = $doc->createElement('test')); +$target->replaceWith($target, 'foo'); +var_dump($doc->saveXML()); + +// Replace with text node variant 2 +$doc = new DOMDocument(); +$doc->appendChild($target = $doc->createElement('test')); +$target->replaceWith('bar', $target, 'foo'); +var_dump($doc->saveXML()); + +?> +--EXPECT-- +string(30) " + +" +string(37) " + + +" +string(34) " + +foo +" +string(38) " +bar + +foo +" diff --git a/ext/dom/tests/gh10234.phpt b/ext/dom/tests/gh10234.phpt new file mode 100644 index 0000000000000..5edc8fc6c1ff1 --- /dev/null +++ b/ext/dom/tests/gh10234.phpt @@ -0,0 +1,93 @@ +--TEST-- +GH-10234 (Setting DOMAttr::textContent results in an empty attribute value.) +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); +$attribute = $document->documentElement->getAttributeNode('attribute'); + +echo "-- Attribute tests --\n"; + +var_dump($document->saveHTML()); +var_dump($attribute->textContent); + +$attribute->textContent = 'new value'; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = 'hello & world'; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = 'hi'; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = 'quote "test"'; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = "quote 'test'"; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = "quote '\"test\"'"; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +echo "-- Document element tests --\n"; + +$document->documentElement->textContent = 'hello & world'; +var_dump($document->documentElement->textContent); +var_dump($document->saveHTML()); + +$document->documentElement->textContent = 'hi'; +var_dump($document->documentElement->textContent); +var_dump($document->saveHTML()); + +$document->documentElement->textContent = 'quote "test"'; +var_dump($document->documentElement->textContent); +var_dump($document->saveHTML()); + +$document->documentElement->textContent = "quote 'test'"; +var_dump($document->documentElement->textContent); +var_dump($document->saveHTML()); +?> +--EXPECT-- +-- Attribute tests -- +string(38) " +" +string(5) "value" +string(9) "new value" +string(42) " +" +string(13) "hello & world" +string(50) " +" +string(9) "hi" +string(54) " +" +string(12) "quote "test"" +string(45) " +" +string(12) "quote 'test'" +string(45) " +" +string(14) "quote '"test"'" +string(57) " +" +-- Document element tests -- +string(13) "hello & world" +string(74) "hello & world +" +string(9) "hi" +string(78) "<b>hi</b> +" +string(12) "quote "test"" +string(69) "quote "test" +" +string(12) "quote 'test'" +string(69) "quote 'test' +" diff --git a/ext/dom/tests/gh11288.phpt b/ext/dom/tests/gh11288.phpt new file mode 100644 index 0000000000000..f70bea80d9085 --- /dev/null +++ b/ext/dom/tests/gh11288.phpt @@ -0,0 +1,67 @@ +--TEST-- +GH-11288 (Error: Couldn't fetch DOMElement introduced in 8.2.6, 8.1.19) +--FILE-- + + +Loremipsum + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator()); +foreach ($spans as $span) { + if ('unwrap_me' === $span->getAttribute('class')) { + $fragment = $dom->createDocumentFragment(); + $fragment->append(...$span->childNodes); + $span->parentNode?->replaceChild($fragment, $span); + } +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); + +$html = << + +Loremipsum + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator()); +foreach ($spans as $span) { + if ('unwrap_me' === $span->getAttribute('class')) { + $span->replaceWith(...$span->childNodes); + } +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); + +$html = << + +Loremipsum + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator()); +foreach ($spans as $span) { + if ('unwrap_me' === $span->getAttribute('class')) { + $span->replaceWith('abc'); + } +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); +?> +--EXPECT-- +string(108) "Loremipsum" +string(108) "Loremipsum" +string(44) "abc" diff --git a/ext/dom/tests/gh11289.phpt b/ext/dom/tests/gh11289.phpt new file mode 100644 index 0000000000000..7771a486bd66b --- /dev/null +++ b/ext/dom/tests/gh11289.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-11289 (DOMException: Not Found Error introduced in 8.2.6, 8.1.19) +--FILE-- + + + +
+ + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$divs = iterator_to_array($dom->getElementsByTagName('div')->getIterator()); +foreach ($divs as $div) { + $fragment = $dom->createDocumentFragment(); + $fragment->appendXML('

Hi!

'); + $div->replaceWith(...$fragment->childNodes); +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); +?> +--EXPECT-- +string(55) "

Hi!

" diff --git a/ext/dom/tests/gh11290.phpt b/ext/dom/tests/gh11290.phpt new file mode 100644 index 0000000000000..2900720301041 --- /dev/null +++ b/ext/dom/tests/gh11290.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-11290 (DOMElement::replaceWith causes crash) +--FILE-- + + + +

Loremipsumdolor

+ + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator()); +foreach ($spans as $span) { + if ('unwrap_me' === $span->getAttribute('class')) { + $span->replaceWith(...$span->childNodes); + } +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); +?> +--EXPECT-- +string(67) "

Loremipsumdolor

" diff --git a/ext/dom/tests/gh11347.phpt b/ext/dom/tests/gh11347.phpt new file mode 100644 index 0000000000000..189231f925081 --- /dev/null +++ b/ext/dom/tests/gh11347.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-11347 (Memory leak when calling a static method inside an xpath query) +--EXTENSIONS-- +dom +--FILE-- +loadHTML('hello'); +$xpath = new DOMXpath($doc); +$xpath->registerNamespace("php", "http://php.net/xpath"); +$xpath->registerPHPFunctions(); +$xpath->query("//a[php:function('MyClass::dump', string(@href))]"); + +?> +Done +--EXPECT-- +string(15) "https://php.net" +Done diff --git a/ext/dom/tests/gh9142.phpt b/ext/dom/tests/gh9142.phpt new file mode 100644 index 0000000000000..f72dfa823f38c --- /dev/null +++ b/ext/dom/tests/gh9142.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-9142 (DOMChildNode replaceWith() double-free error when replacing elements not separated by any whitespace) +--FILE-- +OneTwo'; + +($dom = new DOMDocument('1.0', 'UTF-8'))->loadHTML($document); + +foreach ((new DOMXPath($dom))->query('//var') as $var) { + $var->replaceWith($dom->createElement('p', $var->nodeValue)); +} + +var_dump($dom->saveHTML()); + +?> +--EXPECT-- +string(154) " +

One

Two

+" diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c index 876d8b00dae0e..f546733a436d1 100644 --- a/ext/dom/xpath.c +++ b/ext/dom/xpath.c @@ -182,7 +182,7 @@ static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, } cleanup: zend_string_release_ex(callable, 0); - zval_ptr_dtor_str(&fci.function_name); + zval_ptr_dtor_nogc(&fci.function_name); if (fci.param_count > 0) { for (i = 0; i < nargs - 1; i++) { zval_ptr_dtor(&fci.params[i]); diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c index eca10c27d195b..b956f885ee001 100644 --- a/ext/ffi/ffi_parser.c +++ b/ext/ffi/ffi_parser.c @@ -3552,7 +3552,7 @@ static void parse(void) { } } -int zend_ffi_parse_decl(const char *str, size_t len) { +zend_result zend_ffi_parse_decl(const char *str, size_t len) { if (SETJMP(FFI_G(bailout))==0) { FFI_G(allow_vla) = 0; FFI_G(attribute_parsing) = 0; @@ -3565,7 +3565,7 @@ int zend_ffi_parse_decl(const char *str, size_t len) { } } -int zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl) { +zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl) { int sym; if (SETJMP(FFI_G(bailout))==0) { diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 14fd86d73426c..c56e21df2ba61 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -28,6 +28,7 @@ #include "php_json_encoder.h" #include #include "zend_enum.h" +#include "zend_property_hooks.h" static const char digits[] = "0123456789abcdef"; @@ -114,14 +115,17 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, { int i, r, need_comma = 0; HashTable *myht, *prop_ht; + zend_refcounted *recursion_rc; if (Z_TYPE_P(val) == IS_ARRAY) { myht = Z_ARRVAL_P(val); + recursion_rc = (zend_refcounted *)myht; prop_ht = NULL; r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val); } else if (Z_OBJ_P(val)->properties == NULL && Z_OBJ_HT_P(val)->get_properties_for == NULL - && Z_OBJ_HT_P(val)->get_properties == zend_std_get_properties) { + && Z_OBJ_HT_P(val)->get_properties == zend_std_get_properties + && Z_OBJ_P(val)->ce->num_hooked_props == 0) { /* Optimized version without rebuilding properties HashTable */ zend_object *obj = Z_OBJ_P(val); zend_class_entry *ce = obj->ce; @@ -198,18 +202,26 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, smart_str_appendc(buf, '}'); return SUCCESS; } else { + zend_object *obj = Z_OBJ_P(val); prop_ht = myht = zend_get_properties_for(val, ZEND_PROP_PURPOSE_JSON); + if (obj->ce->num_hooked_props == 0) { + recursion_rc = (zend_refcounted *)prop_ht; + } else { + /* Protecting the object itself is fine here because myht is temporary and can't be + * referenced from a different place in the object graph. */ + recursion_rc = (zend_refcounted *)obj; + } r = PHP_JSON_OUTPUT_OBJECT; } - if (myht && GC_IS_RECURSIVE(myht)) { + if (recursion_rc && GC_IS_RECURSIVE(recursion_rc)) { encoder->error_code = PHP_JSON_ERROR_RECURSION; smart_str_appendl(buf, "null", 4); zend_release_properties(prop_ht); return FAILURE; } - PHP_JSON_HASH_PROTECT_RECURSION(myht); + PHP_JSON_HASH_PROTECT_RECURSION(recursion_rc); if (r == PHP_JSON_OUTPUT_ARRAY) { smart_str_appendc(buf, '['); @@ -278,16 +290,31 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, php_json_pretty_print_char(buf, options, ' '); } + /* data is IS_PTR for properties with hooks. */ + zval tmp; + ZVAL_UNDEF(&tmp); + if (UNEXPECTED(Z_TYPE_P(data) == IS_PTR)) { + zend_property_info *prop_info = Z_PTR_P(data); + zend_read_property_ex(prop_info->ce, Z_OBJ_P(val), prop_info->name, /* silent */ true, &tmp); + if (EG(exception)) { + PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc); + zend_release_properties(prop_ht); + return FAILURE; + } + data = &tmp; + } if (php_json_encode_zval(buf, data, options, encoder) == FAILURE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc); zend_release_properties(prop_ht); + zval_ptr_dtor(&tmp); return FAILURE; } + zval_ptr_dtor(&tmp); } ZEND_HASH_FOREACH_END(); } - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc); if (encoder->depth > encoder->max_depth) { encoder->error_code = PHP_JSON_ERROR_DEPTH; diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 71ed3f911cca7..5af3443069aba 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -1163,8 +1163,14 @@ PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object, object->node->_private = private_data; } } else { + if (UNEXPECTED(node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE)) { + php_libxml_doc_ptr *doc_ptr = emalloc(sizeof(php_libxml_doc_ptr)); + doc_ptr->cache_tag.modification_nr = 1; /* iterators start at 0, such that they will start in an uninitialised state */ + object->node = (php_libxml_node_ptr *) doc_ptr; /* downcast */ + } else { + object->node = emalloc(sizeof(php_libxml_node_ptr)); + } ret_refcount = 1; - object->node = emalloc(sizeof(php_libxml_node_ptr)); object->node->node = node; object->node->refcount = 1; object->node->_private = private_data; diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h index ff8a634e0cf9b..a23ff6ee57c13 100644 --- a/ext/libxml/php_libxml.h +++ b/ext/libxml/php_libxml.h @@ -47,14 +47,14 @@ ZEND_BEGIN_MODULE_GLOBALS(libxml) ZEND_END_MODULE_GLOBALS(libxml) typedef struct _libxml_doc_props { - int formatoutput; - int validateonparse; - int resolveexternals; - int preservewhitespace; - int substituteentities; - int stricterror; - int recover; HashTable *classmap; + bool formatoutput; + bool validateonparse; + bool resolveexternals; + bool preservewhitespace; + bool substituteentities; + bool stricterror; + bool recover; } libxml_doc_props; typedef struct _php_libxml_ref_obj { @@ -69,6 +69,16 @@ typedef struct _php_libxml_node_ptr { void *_private; } php_libxml_node_ptr; +typedef struct { + size_t modification_nr; +} php_libxml_cache_tag; + +/* extends php_libxml_node_ptr */ +typedef struct { + php_libxml_node_ptr node_ptr; + php_libxml_cache_tag cache_tag; +} php_libxml_doc_ptr; + typedef struct _php_libxml_node_object { php_libxml_node_ptr *node; php_libxml_ref_obj *document; @@ -81,6 +91,27 @@ static inline php_libxml_node_object *php_libxml_node_fetch_object(zend_object * return (php_libxml_node_object *)((char*)(obj) - obj->handlers->offset); } +static zend_always_inline void php_libxml_invalidate_node_list_cache(php_libxml_doc_ptr *doc_ptr) +{ +#if SIZEOF_SIZE_T == 8 + /* If one operation happens every nanosecond, then it would still require 584 years to overflow + * the counter. So we'll just assume this never happens. */ + doc_ptr->cache_tag.modification_nr++; +#else + size_t new_modification_nr = doc_ptr->cache_tag.modification_nr + 1; + if (EXPECTED(new_modification_nr > 0)) { /* unsigned overflow; checking after addition results in one less instruction */ + doc_ptr->cache_tag.modification_nr = new_modification_nr; + } +#endif +} + +static zend_always_inline void php_libxml_invalidate_node_list_cache_from_doc(xmlDocPtr docp) +{ + if (docp && docp->_private) { /* docp is NULL for detached nodes */ + php_libxml_invalidate_node_list_cache(docp->_private); + } +} + #define Z_LIBXML_NODE_P(zv) php_libxml_node_fetch_object(Z_OBJ_P((zv))) typedef void * (*php_libxml_export_node) (zval *object); diff --git a/ext/mbstring/libmbfl/filters/mbfilter_cjk.c b/ext/mbstring/libmbfl/filters/mbfilter_cjk.c index a1ecdab986253..13635764326f3 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_cjk.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_cjk.c @@ -8050,7 +8050,7 @@ const mbfl_encoding mbfl_encoding_sjis2004 = { * 0x7E will go to 0x7E when converting Shift-JIS to CP932. */ -static const unsigned char mblen_table_sjiswin[] = { /* 0x80-0x9F,0xE0-0xFF */ +static const unsigned char mblen_table_sjiswin[] = { /* 0x81-0x9F,0xE0-0xFF */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -8059,7 +8059,7 @@ static const unsigned char mblen_table_sjiswin[] = { /* 0x80-0x9F,0xE0-0xFF */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, diff --git a/ext/mbstring/tests/mb_strlen.phpt b/ext/mbstring/tests/mb_strlen.phpt index b3fb28309bcbe..c8279a3c8bfd5 100644 --- a/ext/mbstring/tests/mb_strlen.phpt +++ b/ext/mbstring/tests/mb_strlen.phpt @@ -35,6 +35,13 @@ print "-- Testing illegal bytes 0x80,0xFD-FF --\n"; print mb_strlen("\x80\xA1", 'SJIS') . "\n"; print mb_strlen("abc\xFD\xFE\xFF", 'SJIS') . "\n"; +echo "== CP932 ==\n"; +print mb_strlen("\x80\xA1", "CP932") . "\n"; +// 0xFD, 0xFE, 0xFF is reserved. +print mb_strlen("abc\xFD\xFE\xFF", 'CP932') . "\n"; +print mb_strlen("\x80\xA1", "SJIS-win") . "\n"; +print mb_strlen("abc\xFD\xFE\xFF", 'SJIS-win') . "\n"; + echo "== MacJapanese ==\n"; print mb_strlen("\x80\xA1", 'MacJapanese') . "\n"; print mb_strlen("abc\xFD\xFE\xFF", 'MacJapanese') . "\n"; @@ -107,6 +114,11 @@ try { -- Testing illegal bytes 0x80,0xFD-FF -- 2 6 +== CP932 == +2 +6 +2 +6 == MacJapanese == 2 7 diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 11ab472631b56..a4113f954ef18 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -390,6 +390,10 @@ static inline void accel_unlock_all(void) #ifdef ZEND_WIN32 accel_deactivate_sub(); #else + if (lock_file == -1) { + return; + } + struct flock mem_usage_unlock_all; mem_usage_unlock_all.l_type = F_UNLCK; diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index f168ec190eee6..2e4357f843d37 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -671,10 +671,12 @@ static zend_property_info* zend_get_known_property_info(const zend_op_array *op_ } } + // TODO: Treat property hooks more precisely. info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member); if (info == NULL || !IS_VALID_PROPERTY_OFFSET(info->offset) || - (info->flags & ZEND_ACC_STATIC)) { + (info->flags & ZEND_ACC_STATIC) || + info->hooks) { return NULL; } @@ -707,10 +709,12 @@ static bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string *memb } } + // TODO: Treat property hooks more precisely. info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member); if (info == NULL || !IS_VALID_PROPERTY_OFFSET(info->offset) || - (info->flags & ZEND_ACC_STATIC)) { + (info->flags & ZEND_ACC_STATIC) || + info->hooks) { return 1; } diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 191e4eebbbd2b..be5f15d328394 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -7188,8 +7188,6 @@ static void zend_jit_stop_hot_trace_counters(zend_op_array *op_array) uint32_t i; jit_extension = (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); - zend_shared_alloc_lock(); - SHM_UNPROTECT(); for (i = 0; i < op_array->last; i++) { /* Opline with Jit-ed code handler is skipped. */ if (jit_extension->trace_info[i].trace_flags & @@ -7201,8 +7199,6 @@ static void zend_jit_stop_hot_trace_counters(zend_op_array *op_array) op_array->opcodes[i].handler = jit_extension->trace_info[i].orig_handler; } } - SHM_PROTECT(); - zend_shared_alloc_unlock(); } /* Get the tracing op_array. */ @@ -7241,6 +7237,9 @@ static void zend_jit_stop_persistent_script(zend_persistent_script *script) /* Get all scripts which are accelerated by JIT */ static void zend_jit_stop_counter_handlers(void) { + zend_shared_alloc_lock(); + /* mprotect has an extreme overhead, avoid calls to it for every function. */ + SHM_UNPROTECT(); for (uint32_t i = 0; i < ZCSG(hash).max_num_entries; i++) { zend_accel_hash_entry *cache_entry; for (cache_entry = ZCSG(hash).hash_table[i]; cache_entry; cache_entry = cache_entry->next) { @@ -7250,6 +7249,8 @@ static void zend_jit_stop_counter_handlers(void) zend_jit_stop_persistent_script(script); } } + SHM_PROTECT(); + zend_shared_alloc_unlock(); } static void zend_jit_blacklist_root_trace(const zend_op *opline, size_t offset) diff --git a/ext/opcache/tests/dump_property_hooks.phpt b/ext/opcache/tests/dump_property_hooks.phpt new file mode 100644 index 0000000000000..eca72e785e727 --- /dev/null +++ b/ext/opcache/tests/dump_property_hooks.phpt @@ -0,0 +1,64 @@ +--TEST-- +Optimizer optimizes hooks, OpCache dump emits them +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.opt_debug_level=0x20000 +--EXTENSIONS-- +opcache +--FILE-- +prop); +$a->prop = 41; + +?> +--EXPECTF-- +$_main: + ; (lines=10, args=0, vars=1, tmps=1) + ; (after optimizer) + ; %sdump_property_hooks.php:1-22 +0000 V1 = NEW 0 string("A") +0001 DO_FCALL +0002 ASSIGN CV0($a) V1 +0003 INIT_FCALL 1 96 string("var_dump") +0004 T1 = FETCH_OBJ_R CV0($a) string("prop") +0005 SEND_VAL T1 1 +0006 DO_ICALL +0007 ASSIGN_OBJ CV0($a) string("prop") +0008 OP_DATA int(41) +0009 RETURN int(1) +LIVE RANGES: + 1: 0001 - 0002 (new) + +A::$prop::get: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %sdump_property_hooks.php:5-8 +0000 RETURN int(42) + +A::$prop::set: + ; (lines=4, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %sdump_property_hooks.php:9-13 +0000 CV0($value) = RECV 1 +0001 T1 = FAST_CONCAT string("Setting ") CV0($value) +0002 ECHO T1 +0003 RETURN null +int(42) +Setting 41 diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 791032f0e1f26..ae6e12013007c 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -702,9 +702,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) { @@ -712,9 +711,9 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce) if (op_array->fn_flags & ZEND_ACC_ARENA_ALLOCATED) { old_op_array = zend_shared_alloc_get_xlat_entry(op_array); if (old_op_array) { - Z_PTR_P(zv) = old_op_array; + return old_op_array; } else { - op_array = Z_PTR_P(zv) = zend_shared_memdup_put(op_array, sizeof(zend_internal_function)); + op_array = zend_shared_memdup_put(op_array, sizeof(zend_internal_function)); if (op_array->scope) { void *persist_ptr; @@ -734,19 +733,18 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce) } } } - return; + return op_array; } if ((op_array->fn_flags & ZEND_ACC_IMMUTABLE) && !ZCG(current_persistent_script)->corrupted && zend_accel_in_shm(op_array)) { zend_shared_alloc_register_xlat_entry(op_array, op_array); - return; + return op_array; } old_op_array = zend_shared_alloc_get_xlat_entry(op_array); if (old_op_array) { - Z_PTR_P(zv) = old_op_array; if (op_array->refcount && --(*op_array->refcount) == 0) { efree(op_array->refcount); } @@ -758,9 +756,10 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce) if (old_function_name) { zend_string_release_ex(old_function_name, 0); } - return; + return old_op_array; } - op_array = Z_PTR_P(zv) = zend_shared_memdup_put(op_array, sizeof(zend_op_array)); + + op_array = zend_shared_memdup_put(op_array, sizeof(zend_op_array)); zend_persist_op_array_ex(op_array, NULL); if (ce->ce_flags & ZEND_ACC_IMMUTABLE) { op_array->fn_flags |= ZEND_ACC_IMMUTABLE; @@ -774,6 +773,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) @@ -799,6 +799,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->hooks) { + prop->hooks = zend_shared_memdup_put(prop->hooks, ZEND_PROPERTY_HOOK_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop->hooks[i]) { + prop->hooks[i] = (zend_function *) zend_persist_class_method( + &prop->hooks[i]->op_array, ce); + } + } + } zend_persist_type(&prop->type); return prop; } @@ -887,7 +896,7 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) ZEND_HASH_MAP_FOREACH_BUCKET(&ce->function_table, p) { ZEND_ASSERT(p->key != NULL); zend_accel_store_interned_string(p->key); - zend_persist_class_method(&p->val, ce); + Z_PTR(p->val) = zend_persist_class_method(Z_PTR(p->val), ce); } ZEND_HASH_FOREACH_END(); HT_FLAGS(&ce->function_table) &= (HASH_FLAG_UNINITIALIZED | HASH_FLAG_STATIC_KEYS); if (ce->default_properties_table) { diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index dfc281eb7f6f7..6d8eeefcf6f81 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -328,9 +328,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) { @@ -339,7 +338,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; @@ -355,8 +354,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. */ @@ -379,6 +378,14 @@ static void zend_persist_property_info_calc(zend_property_info *prop) if (prop->attributes) { zend_persist_attributes_calc(prop->attributes); } + if (prop->hooks) { + ADD_SIZE(ZEND_PROPERTY_HOOK_STRUCT_SIZE); + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop->hooks[i]) { + zend_persist_class_method_calc(&prop->hooks[i]->op_array); + } + } + } } static void zend_persist_class_constant_calc(zval *zv) @@ -427,7 +434,7 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) ZEND_HASH_MAP_FOREACH_BUCKET(&ce->function_table, p) { ZEND_ASSERT(p->key != NULL); ADD_INTERNED_STRING(p->key); - zend_persist_class_method_calc(&p->val); + zend_persist_class_method_calc(Z_PTR(p->val)); } ZEND_HASH_FOREACH_END(); if (ce->default_properties_table) { int i; diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c index 81324f9a50957..befcbe442abb1 100644 --- a/ext/opcache/zend_shared_alloc.c +++ b/ext/opcache/zend_shared_alloc.c @@ -59,7 +59,7 @@ zend_smm_shared_globals *smm_shared_globals; #ifdef ZTS static MUTEX_T zts_lock; #endif -int lock_file; +int lock_file = -1; static char lockfile_name[MAXPATHLEN]; #endif @@ -211,6 +211,7 @@ int zend_shared_alloc_startup(size_t requested_size, size_t reserved_size) } #if ENABLE_FILE_CACHE_FALLBACK if (ALLOC_FALLBACK == res) { + smm_shared_globals = NULL; return ALLOC_FALLBACK; } #endif @@ -236,6 +237,7 @@ int zend_shared_alloc_startup(size_t requested_size, size_t reserved_size) } #if ENABLE_FILE_CACHE_FALLBACK if (ALLOC_FALLBACK == res) { + smm_shared_globals = NULL; return ALLOC_FALLBACK; } #endif diff --git a/ext/readline/tests/bug77812-readline.phpt b/ext/readline/tests/bug77812-readline.phpt index a18686781718b..a2d6c212c536a 100644 --- a/ext/readline/tests/bug77812-readline.phpt +++ b/ext/readline/tests/bug77812-readline.phpt @@ -13,7 +13,6 @@ $php = getenv('TEST_PHP_EXECUTABLE'); $ini = getenv('TEST_PHP_EXTRA_ARGS'); $descriptorspec = [['pipe', 'r'], STDOUT, STDERR]; $proc = proc_open("$php $ini -a", $descriptorspec, $pipes); -var_dump($proc); fwrite($pipes[0], "echo << --EXPECTF-- -resource(%d) of type (process) Interactive shell php > echo <<ce; - if (prop_info->flags & ZEND_ACC_STATIC) { + if (prop_info->flags & ZEND_ACC_VIRTUAL) { + return NULL; + } else if (prop_info->flags & ZEND_ACC_STATIC) { zval *prop = &ce->default_static_members_table[prop_info->offset]; ZVAL_DEINDIRECT(prop); return prop; @@ -923,7 +925,7 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha smart_str_append_printf(str, "$%s", prop_name); zval *default_value = property_get_default(prop); - if (!Z_ISUNDEF_P(default_value)) { + if (default_value && !Z_ISUNDEF_P(default_value)) { smart_str_appends(str, " = "); if (format_default_value(str, default_value) == FAILURE) { return; @@ -4062,7 +4064,7 @@ static void add_class_vars(zend_class_entry *ce, bool statics, zval *return_valu } prop = property_get_default(prop_info); - if (Z_ISUNDEF_P(prop)) { + if (!prop || Z_ISUNDEF_P(prop)) { continue; } @@ -5603,6 +5605,16 @@ ZEND_METHOD(ReflectionProperty, isReadOnly) _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_READONLY); } +ZEND_METHOD(ReflectionProperty, isAbstract) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_ABSTRACT); +} + +ZEND_METHOD(ReflectionProperty, isVirtual) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_VIRTUAL); +} + /* {{{ Returns whether this property is default (declared at compilation time). */ ZEND_METHOD(ReflectionProperty, isDefault) { @@ -5629,7 +5641,7 @@ ZEND_METHOD(ReflectionProperty, getModifiers) { reflection_object *intern; property_reference *ref; - uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY; + uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY | ZEND_ACC_ABSTRACT | ZEND_ACC_VIRTUAL; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); @@ -5880,7 +5892,7 @@ ZEND_METHOD(ReflectionProperty, hasDefaultValue) } prop = property_get_default(prop_info); - RETURN_BOOL(!Z_ISUNDEF_P(prop)); + RETURN_BOOL(prop && !Z_ISUNDEF_P(prop)); } /* }}} */ @@ -5905,7 +5917,7 @@ ZEND_METHOD(ReflectionProperty, getDefaultValue) } prop = property_get_default(prop_info); - if (Z_ISUNDEF_P(prop)) { + if (!prop || Z_ISUNDEF_P(prop)) { return; } @@ -5923,6 +5935,64 @@ ZEND_METHOD(ReflectionProperty, getDefaultValue) } /* }}} */ +ZEND_METHOD(ReflectionProperty, getHooks) +{ + reflection_object *intern; + property_reference *ref; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(ref); + + if (!ref->prop->hooks) { + RETURN_EMPTY_ARRAY(); + } + + array_init(return_value); + if (ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) { + zval hook_obj; + zend_function *hook = ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]; + reflection_method_factory(hook->common.scope, hook, NULL, &hook_obj); + zend_hash_update(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_GET), &hook_obj); + } + if (ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + zval hook_obj; + zend_function *hook = ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]; + reflection_method_factory(hook->common.scope, hook, NULL, &hook_obj); + zend_hash_update(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_SET), &hook_obj); + } +} + +ZEND_METHOD(ReflectionProperty, getHook) +{ + reflection_object *intern; + property_reference *ref; + zend_string *name; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); + + GET_REFLECTION_OBJECT_PTR(ref); + + zend_function *hook; + if (zend_string_equals_literal_ci(name, "get")) { + hook = ref->prop->hooks ? ref->prop->hooks[ZEND_PROPERTY_HOOK_GET] : NULL; + } else if (zend_string_equals_literal_ci(name, "set")) { + hook = ref->prop->hooks ? ref->prop->hooks[ZEND_PROPERTY_HOOK_SET] : NULL; + } else { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Property hook \"%s\" does not exist", ZSTR_VAL(name)); + RETURN_THROWS(); + } + + if (!hook) { + RETURN_NULL(); + } + + reflection_method_factory(hook->common.scope, hook, NULL, return_value); +} + /* {{{ Constructor. Throws an Exception in case the given extension does not exist */ ZEND_METHOD(ReflectionExtension, __construct) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index de0d83d244a85..f5a9e5dcf9f3d 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -478,6 +478,11 @@ class ReflectionProperty implements Reflector * @cvalue ZEND_ACC_PRIVATE */ public const IS_PRIVATE = UNKNOWN; + /** + * @var int + * @cvalue ZEND_ACC_ABSTRACT + */ + public const IS_ABSTRACT = UNKNOWN; public string $name; public string $class; @@ -518,6 +523,10 @@ public function isReadOnly(): bool {} /** @tentative-return-type */ public function isDefault(): bool {} + public function isAbstract(): bool {} + + public function isVirtual(): bool {} + public function isPromoted(): bool {} /** @tentative-return-type */ @@ -544,6 +553,11 @@ public function hasDefaultValue(): bool {} public function getDefaultValue(): mixed {} public function getAttributes(?string $name = null, int $flags = 0): array {} + + /** @return array */ + public function getHooks(): array {} + + public function getHook(string $name): ?ReflectionMethod {} } /** @not-serializable */ diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index ddc445f5206d3..e652d9c771fac 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: 75d10a475cce503d94bd8471764adf495f0ddd34 */ + * Stub hash: 580ec0215b857f0157382c59415c6690b68d716a */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -362,6 +362,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionProperty_isAbstract arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + +#define arginfo_class_ReflectionProperty_isVirtual arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + #define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType #define arginfo_class_ReflectionProperty_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters @@ -383,6 +387,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes +#define arginfo_class_ReflectionProperty_getHooks arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionProperty_getHook, 0, 1, ReflectionMethod, 1) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionClassConstant___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionClassConstant___construct, 0, 0, 2) @@ -741,6 +751,8 @@ ZEND_METHOD(ReflectionProperty, isProtected); ZEND_METHOD(ReflectionProperty, isStatic); ZEND_METHOD(ReflectionProperty, isReadOnly); ZEND_METHOD(ReflectionProperty, isDefault); +ZEND_METHOD(ReflectionProperty, isAbstract); +ZEND_METHOD(ReflectionProperty, isVirtual); ZEND_METHOD(ReflectionProperty, isPromoted); ZEND_METHOD(ReflectionProperty, getModifiers); ZEND_METHOD(ReflectionProperty, getDeclaringClass); @@ -751,6 +763,8 @@ ZEND_METHOD(ReflectionProperty, hasType); ZEND_METHOD(ReflectionProperty, hasDefaultValue); ZEND_METHOD(ReflectionProperty, getDefaultValue); ZEND_METHOD(ReflectionProperty, getAttributes); +ZEND_METHOD(ReflectionProperty, getHooks); +ZEND_METHOD(ReflectionProperty, getHook); ZEND_METHOD(ReflectionClassConstant, __construct); ZEND_METHOD(ReflectionClassConstant, __toString); ZEND_METHOD(ReflectionClassConstant, getName); @@ -1023,6 +1037,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, isStatic, arginfo_class_ReflectionProperty_isStatic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isReadOnly, arginfo_class_ReflectionProperty_isReadOnly, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isDefault, arginfo_class_ReflectionProperty_isDefault, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isAbstract, arginfo_class_ReflectionProperty_isAbstract, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isVirtual, arginfo_class_ReflectionProperty_isVirtual, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPromoted, arginfo_class_ReflectionProperty_isPromoted, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getModifiers, arginfo_class_ReflectionProperty_getModifiers, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getDeclaringClass, arginfo_class_ReflectionProperty_getDeclaringClass, ZEND_ACC_PUBLIC) @@ -1033,6 +1049,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, getHooks, arginfo_class_ReflectionProperty_getHooks, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, getHook, arginfo_class_ReflectionProperty_getHook, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -1422,6 +1440,12 @@ static zend_class_entry *register_class_ReflectionProperty(zend_class_entry *cla zend_declare_class_constant_ex(class_entry, const_IS_PRIVATE_name, &const_IS_PRIVATE_value, ZEND_ACC_PUBLIC, NULL); zend_string_release(const_IS_PRIVATE_name); + zval const_IS_ABSTRACT_value; + ZVAL_LONG(&const_IS_ABSTRACT_value, ZEND_ACC_ABSTRACT); + zend_string *const_IS_ABSTRACT_name = zend_string_init_interned("IS_ABSTRACT", sizeof("IS_ABSTRACT") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_IS_ABSTRACT_name, &const_IS_ABSTRACT_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_IS_ABSTRACT_name); + zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); diff --git a/ext/reflection/tests/property_hooks/ReflectionClass_getMethods.phpt b/ext/reflection/tests/property_hooks/ReflectionClass_getMethods.phpt new file mode 100644 index 0000000000000..c22c92016a4d3 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionClass_getMethods.phpt @@ -0,0 +1,15 @@ +--TEST-- +ReflectionClass::getMethods() does not contain property hooks +--FILE-- +getMethods()); + +?> +--EXPECT-- +array(0) { +} diff --git a/ext/reflection/tests/property_hooks/ReflectionProperty_getHook_inheritance.phpt b/ext/reflection/tests/property_hooks/ReflectionProperty_getHook_inheritance.phpt new file mode 100644 index 0000000000000..bfde55a319f35 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionProperty_getHook_inheritance.phpt @@ -0,0 +1,43 @@ +--TEST-- +ReflectionClass::get{Get,Set}() inheritance +--FILE-- +getHook('get')->invoke($a)), "\n"; +echo ((new ReflectionProperty(A::class, 'foo'))->getHook('get')->invoke($b)), "\n"; +echo ((new ReflectionProperty(B::class, 'foo'))->getHook('get')->invoke($b)), "\n"; + +((new ReflectionProperty(A::class, 'foo'))->getHook('set')->invoke($a, null)); +((new ReflectionProperty(A::class, 'foo'))->getHook('set')->invoke($b, null)); +((new ReflectionProperty(B::class, 'foo'))->getHook('set')->invoke($b, null)); + +?> +--EXPECT-- +A::$foo::get +A::$foo::get +B::$foo +A::$foo::set +A::$foo::set +A::$foo::set diff --git a/ext/reflection/tests/property_hooks/ReflectionProperty_getHooks.phpt b/ext/reflection/tests/property_hooks/ReflectionProperty_getHooks.phpt new file mode 100644 index 0000000000000..ab6c0ea291be1 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionProperty_getHooks.phpt @@ -0,0 +1,55 @@ +--TEST-- +ReflectionProperty::getHooks() +--FILE-- +getHooks()); +} + +?> +--EXPECT-- +array(0) { +} +array(2) { + ["get"]=> + object(ReflectionMethod)#1 (2) { + ["name"]=> + string(11) "$prop2::get" + ["class"]=> + string(4) "Test" + } + ["set"]=> + object(ReflectionMethod)#3 (2) { + ["name"]=> + string(11) "$prop2::set" + ["class"]=> + string(4) "Test" + } +} +array(1) { + ["get"]=> + object(ReflectionMethod)#2 (2) { + ["name"]=> + string(11) "$prop3::get" + ["class"]=> + string(4) "Test" + } +} +array(1) { + ["set"]=> + object(ReflectionMethod)#3 (2) { + ["name"]=> + string(11) "$prop4::set" + ["class"]=> + string(4) "Test" + } +} diff --git a/ext/reflection/tests/property_hooks/ReflectionProperty_getSetValue.phpt b/ext/reflection/tests/property_hooks/ReflectionProperty_getSetValue.phpt new file mode 100644 index 0000000000000..9daca77330680 --- /dev/null +++ b/ext/reflection/tests/property_hooks/ReflectionProperty_getSetValue.phpt @@ -0,0 +1,28 @@ +--TEST-- +ReflectionProperty::{get,set}Value() invokes hooks +--FILE-- +getValue($test)); +$propertyReflection->setValue($test, 'Baz'); + +?> +--EXPECT-- +Get called +string(3) "Foo" +Set called with value Baz diff --git a/ext/reflection/tests/property_hooks/basics.phpt b/ext/reflection/tests/property_hooks/basics.phpt new file mode 100644 index 0000000000000..41d8fa05fc84f --- /dev/null +++ b/ext/reflection/tests/property_hooks/basics.phpt @@ -0,0 +1,99 @@ +--TEST-- +Property hook reflection +--FILE-- +getModifiers(); + echo "Abstract: "; + echo $rp->isAbstract() ? "true" : "false"; + echo " "; + echo $modifiers & ReflectionProperty::IS_ABSTRACT ? "true" : "false"; + echo "\n"; +} + +$test = new Test; + +$rp1 = new ReflectionProperty(Test::class, 'prop1'); +var_dump($rp1->getHook('get')); +var_dump($rp1->getHook('set')); +dumpFlags($rp1); +echo "\n"; + +$rp2 = new ReflectionProperty(Test::class, 'prop2'); +var_dump($g = $rp2->getHook('get')); +var_dump($s = $rp2->getHook('set')); +var_dump($g->invoke($test)); +try { + $s->invoke($test, 42); +} catch (ReflectionException $e) { + echo $e->getMessage(), "\n"; +} +$s->setAccessible(true); +$s->invoke($test, 42); +var_dump($test->prop2); +dumpFlags($rp2); +echo "\n"; + +$rp3 = new ReflectionProperty(Test::class, 'prop3'); +var_dump($g = $rp3->getHook('get')); +var_dump($s = $rp3->getHook('set')); +$g->invoke($test); +$s->invoke($test, 42); +dumpFlags($rp3); +echo "\n"; + +$rp4 = new ReflectionProperty(Test2::class, 'prop4'); +dumpFlags($rp4); + +?> +--EXPECT-- +NULL +NULL +Abstract: false false + +object(ReflectionMethod)#4 (2) { + ["name"]=> + string(11) "$prop2::get" + ["class"]=> + string(4) "Test" +} +object(ReflectionMethod)#5 (2) { + ["name"]=> + string(11) "$prop2::set" + ["class"]=> + string(4) "Test" +} +NULL +NULL +Abstract: false false + +object(ReflectionMethod)#7 (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) +Abstract: false false + +Abstract: true true diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index d3f2865e12036..7d9bed8ad6c40 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -33,13 +33,12 @@ #include "zend_interfaces.h" #include "ext/spl/spl_iterators.h" -zend_class_entry *sxe_class_entry = NULL; PHP_SXE_API zend_class_entry *ce_SimpleXMLIterator; PHP_SXE_API zend_class_entry *ce_SimpleXMLElement; PHP_SXE_API zend_class_entry *sxe_get_element_class_entry(void) /* {{{ */ { - return sxe_class_entry; + return ce_SimpleXMLElement; } /* }}} */ @@ -101,7 +100,7 @@ static inline int match_ns(php_sxe_object *sxe, xmlNodePtr node, xmlChar *name, return 1; } - if (node->ns && !xmlStrcmp(prefix ? node->ns->prefix : node->ns->href, name)) { + if (node->ns && xmlStrEqual(prefix ? node->ns->prefix : node->ns->href, name)) { return 1; } @@ -127,7 +126,7 @@ static xmlNodePtr sxe_get_element_by_offset(php_sxe_object *sxe, zend_long offse SKIP_TEXT(node) if (node->type == XML_ELEMENT_NODE && match_ns(sxe, node, sxe->iter.nsprefix, sxe->iter.isprefix)) { if (sxe->iter.type == SXE_ITER_CHILD || ( - sxe->iter.type == SXE_ITER_ELEMENT && !xmlStrcmp(node->name, sxe->iter.name))) { + sxe->iter.type == SXE_ITER_ELEMENT && xmlStrEqual(node->name, sxe->iter.name))) { if (nodendx == offset) { break; } @@ -151,7 +150,7 @@ static xmlNodePtr sxe_find_element_by_name(php_sxe_object *sxe, xmlNodePtr node, while (node) { SKIP_TEXT(node) if (node->type == XML_ELEMENT_NODE && match_ns(sxe, node, sxe->iter.nsprefix, sxe->iter.isprefix)) { - if (!xmlStrcmp(node->name, name)) { + if (xmlStrEqual(node->name, name)) { return node; } } @@ -161,11 +160,10 @@ static xmlNodePtr sxe_find_element_by_name(php_sxe_object *sxe, xmlNodePtr node, return NULL; } /* }}} */ -static xmlNodePtr sxe_get_element_by_name(php_sxe_object *sxe, xmlNodePtr node, char **name, SXE_ITER *type) /* {{{ */ +static xmlNodePtr sxe_get_element_by_name(php_sxe_object *sxe, xmlNodePtr node, char *name, SXE_ITER *type) /* {{{ */ { int orgtype; xmlNodePtr orgnode = node; - xmlNodePtr retnode = NULL; if (sxe->iter.type != SXE_ITER_ATTRLIST) { @@ -188,26 +186,15 @@ static xmlNodePtr sxe_get_element_by_name(php_sxe_object *sxe, xmlNodePtr node, while (node) { SKIP_TEXT(node) if (node->type == XML_ELEMENT_NODE && match_ns(sxe, node, sxe->iter.nsprefix, sxe->iter.isprefix)) { - if (!xmlStrcmp(node->name, (xmlChar *)*name)) { - if (1||retnode) - { - *type = SXE_ITER_ELEMENT; - return orgnode; - } - retnode = node; + if (xmlStrEqual(node->name, (xmlChar *)name)) { + *type = SXE_ITER_ELEMENT; + return orgnode; } } next_iter: node = node->next; } - if (retnode) - { - *type = SXE_ITER_NONE; - *name = NULL; - return retnode; - } - return NULL; } /* }}} */ @@ -281,7 +268,7 @@ static zval *sxe_prop_dim_read(zend_object *object, zval *member, bool elements, if (Z_TYPE_P(member) != IS_LONG || sxe->iter.type == SXE_ITER_ATTRLIST) { if (Z_TYPE_P(member) == IS_LONG) { while (attr && nodendx <= Z_LVAL_P(member)) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { if (nodendx == Z_LVAL_P(member)) { _node_as_zval(sxe, (xmlNodePtr) attr, rv, SXE_ITER_NONE, NULL, sxe->iter.nsprefix, sxe->iter.isprefix); break; @@ -292,7 +279,7 @@ static zval *sxe_prop_dim_read(zend_object *object, zval *member, bool elements, } } else { while (attr) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && !xmlStrcmp(attr->name, (xmlChar *)name) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && xmlStrEqual(attr->name, (xmlChar *)name) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { _node_as_zval(sxe, (xmlNodePtr) attr, rv, SXE_ITER_NONE, NULL, sxe->iter.nsprefix, sxe->iter.isprefix); break; } @@ -442,6 +429,8 @@ static zval *sxe_prop_dim_write(zend_object *object, zval *member, zval *value, GET_NODE(sxe, node); + php_libxml_invalidate_node_list_cache_from_doc(node->doc); + if (sxe->iter.type == SXE_ITER_ATTRLIST) { attribs = 1; elements = 0; @@ -481,7 +470,7 @@ static zval *sxe_prop_dim_write(zend_object *object, zval *member, zval *value, value_str = zval_get_string(value); break; case IS_OBJECT: - if (Z_OBJCE_P(value) == sxe_class_entry) { + if (Z_OBJCE_P(value) == ce_SimpleXMLElement) { zval zval_copy; if (sxe_object_cast_ex(Z_OBJ_P(value), &zval_copy, IS_STRING) == FAILURE) { zend_throw_error(NULL, "Unable to cast node to string"); @@ -505,7 +494,7 @@ static zval *sxe_prop_dim_write(zend_object *object, zval *member, zval *value, if (attribs) { if (Z_TYPE_P(member) == IS_LONG) { while (attr && nodendx <= Z_LVAL_P(member)) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { if (nodendx == Z_LVAL_P(member)) { is_attr = 1; ++counter; @@ -517,7 +506,7 @@ static zval *sxe_prop_dim_write(zend_object *object, zval *member, zval *value, } } else { while (attr) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && !xmlStrcmp(attr->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && xmlStrEqual(attr->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { is_attr = 1; ++counter; break; @@ -556,7 +545,7 @@ static zval *sxe_prop_dim_write(zend_object *object, zval *member, zval *value, while (node) { SKIP_TEXT(node); - if (!xmlStrcmp(node->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, node, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if (xmlStrEqual(node->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, node, sxe->iter.nsprefix, sxe->iter.isprefix)) { newnode = node; ++counter; } @@ -645,7 +634,7 @@ static zval *sxe_property_get_adr(zend_object *object, zend_string *zname, int f sxe = php_sxe_fetch_object(object); GET_NODE(sxe, node); name = ZSTR_VAL(zname); - node = sxe_get_element_by_name(sxe, node, &name, &type); + node = sxe_get_element_by_name(sxe, node, name, &type); if (node) { return NULL; } @@ -719,7 +708,7 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt int nodendx = 0; while (attr && nodendx <= Z_LVAL_P(member)) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { if (nodendx == Z_LVAL_P(member)) { exists = 1; break; @@ -730,7 +719,7 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt } } else { while (attr) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && !xmlStrcmp(attr->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && xmlStrEqual(attr->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { exists = 1; break; } @@ -739,7 +728,7 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt } } if (exists && check_empty == 1 && - (!attr->children || !attr->children->content || !attr->children->content[0] || !xmlStrcmp(attr->children->content, (const xmlChar *) "0")) ) { + (!attr->children || !attr->children->content || !attr->children->content[0] || xmlStrEqual(attr->children->content, (const xmlChar *) "0")) ) { /* Attribute with no content in it's text node */ exists = 0; } @@ -758,7 +747,7 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt exists = 1; if (check_empty == 1 && (!node->children || (node->children->type == XML_TEXT_NODE && !node->children->next && - (!node->children->content || !node->children->content[0] || !xmlStrcmp(node->children->content, (const xmlChar *) "0")))) ) { + (!node->children->content || !node->children->content[0] || xmlStrEqual(node->children->content, (const xmlChar *) "0")))) ) { exists = 0; } } @@ -813,6 +802,8 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements GET_NODE(sxe, node); + php_libxml_invalidate_node_list_cache_from_doc(node->doc); + if (Z_TYPE_P(member) == IS_LONG) { if (sxe->iter.type != SXE_ITER_ATTRLIST) { attribs = 0; @@ -841,7 +832,7 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements int nodendx = 0; while (attr && nodendx <= Z_LVAL_P(member)) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { if (nodendx == Z_LVAL_P(member)) { xmlUnlinkNode((xmlNodePtr) attr); php_libxml_node_free_resource((xmlNodePtr) attr); @@ -854,7 +845,7 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements } else { while (attr) { anext = attr->next; - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && !xmlStrcmp(attr->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && xmlStrEqual(attr->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, (xmlNodePtr) attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { xmlUnlinkNode((xmlNodePtr) attr); php_libxml_node_free_resource((xmlNodePtr) attr); break; @@ -881,7 +872,7 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements SKIP_TEXT(node); - if (!xmlStrcmp(node->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, node, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if (xmlStrEqual(node->name, (xmlChar *)Z_STRVAL_P(member)) && match_ns(sxe, node, sxe->iter.nsprefix, sxe->iter.isprefix)) { xmlUnlinkNode(node); php_libxml_node_free_resource(node); } @@ -1006,7 +997,7 @@ static int sxe_prop_is_empty(zend_object *object) /* {{{ */ attr = node ? (xmlAttrPtr)node->properties : NULL; test = sxe->iter.name && sxe->iter.type == SXE_ITER_ATTRLIST; while (attr) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr)attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr)attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { return 0; } attr = attr->next; @@ -1116,7 +1107,7 @@ static HashTable *sxe_get_prop_hash(zend_object *object, int is_debug) /* {{{ */ ZVAL_UNDEF(&zattr); test = sxe->iter.name && sxe->iter.type == SXE_ITER_ATTRLIST; while (attr) { - if ((!test || !xmlStrcmp(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr)attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { + if ((!test || xmlStrEqual(attr->name, sxe->iter.name)) && match_ns(sxe, (xmlNodePtr)attr, sxe->iter.nsprefix, sxe->iter.isprefix)) { ZVAL_STR(&value, sxe_xmlNodeListGetString((xmlDocPtr) sxe->document->ptr, attr->children, 1)); namelen = xmlStrlen(attr->name); if (Z_ISUNDEF(zattr)) { @@ -1686,6 +1677,8 @@ PHP_METHOD(SimpleXMLElement, addChild) sxe = Z_SXEOBJ_P(ZEND_THIS); GET_NODE(sxe, node); + php_libxml_invalidate_node_list_cache_from_doc(node->doc); + if (sxe->iter.type == SXE_ITER_ATTRLIST) { php_error_docref(NULL, E_WARNING, "Cannot add element to attributes"); return; @@ -2190,7 +2183,7 @@ static zend_function* php_sxe_find_fptr_count(zend_class_entry *ce) int inherited = 0; while (parent) { - if (parent == sxe_class_entry) { + if (parent == ce_SimpleXMLElement) { break; } parent = parent->parent; @@ -2248,7 +2241,7 @@ PHP_FUNCTION(simplexml_load_file) char *ns = NULL; size_t ns_len = 0; zend_long options = 0; - zend_class_entry *ce= sxe_class_entry; + zend_class_entry *ce= ce_SimpleXMLElement; zend_function *fptr_count; bool isprefix = 0; @@ -2268,7 +2261,7 @@ PHP_FUNCTION(simplexml_load_file) } if (!ce) { - ce = sxe_class_entry; + ce = ce_SimpleXMLElement; fptr_count = NULL; } else { fptr_count = php_sxe_find_fptr_count(ce); @@ -2293,7 +2286,7 @@ PHP_FUNCTION(simplexml_load_string) char *ns = NULL; size_t ns_len = 0; zend_long options = 0; - zend_class_entry *ce= sxe_class_entry; + zend_class_entry *ce= ce_SimpleXMLElement; zend_function *fptr_count; bool isprefix = 0; @@ -2321,7 +2314,7 @@ PHP_FUNCTION(simplexml_load_string) } if (!ce) { - ce = sxe_class_entry; + ce = ce_SimpleXMLElement; fptr_count = NULL; } else { fptr_count = php_sxe_find_fptr_count(ce); @@ -2399,7 +2392,7 @@ static xmlNodePtr php_sxe_iterator_fetch(php_sxe_object *sxe, xmlNodePtr node, i if (sxe->iter.name) { while (node) { if (node->type == XML_ATTRIBUTE_NODE) { - if (!xmlStrcmp(node->name, sxe->iter.name) && match_ns(sxe, node, prefix, isprefix)) { + if (xmlStrEqual(node->name, sxe->iter.name) && match_ns(sxe, node, prefix, isprefix)) { break; } } @@ -2418,7 +2411,7 @@ static xmlNodePtr php_sxe_iterator_fetch(php_sxe_object *sxe, xmlNodePtr node, i } else if (sxe->iter.type == SXE_ITER_ELEMENT && sxe->iter.name) { while (node) { if (node->type == XML_ELEMENT_NODE) { - if (!xmlStrcmp(node->name, sxe->iter.name) && match_ns(sxe, node, prefix, isprefix)) { + if (xmlStrEqual(node->name, sxe->iter.name) && match_ns(sxe, node, prefix, isprefix)) { break; } } @@ -2595,7 +2588,7 @@ PHP_FUNCTION(simplexml_import_dom) zval *node; php_libxml_node_object *object; xmlNodePtr nodep = NULL; - zend_class_entry *ce = sxe_class_entry; + zend_class_entry *ce = ce_SimpleXMLElement; zend_function *fptr_count; if (zend_parse_parameters(ZEND_NUM_ARGS(), "o|C!", &node, &ce) == FAILURE) { @@ -2620,7 +2613,7 @@ PHP_FUNCTION(simplexml_import_dom) if (nodep && nodep->type == XML_ELEMENT_NODE) { if (!ce) { - ce = sxe_class_entry; + ce = ce_SimpleXMLElement; fptr_count = NULL; } else { fptr_count = php_sxe_find_fptr_count(ce); @@ -2670,10 +2663,10 @@ ZEND_GET_MODULE(simplexml) /* {{{ PHP_MINIT_FUNCTION(simplexml) */ PHP_MINIT_FUNCTION(simplexml) { - sxe_class_entry = register_class_SimpleXMLElement(zend_ce_stringable, zend_ce_countable, spl_ce_RecursiveIterator); - sxe_class_entry->create_object = sxe_object_new; - sxe_class_entry->default_object_handlers = &sxe_object_handlers; - sxe_class_entry->get_iterator = php_sxe_get_iterator; + ce_SimpleXMLElement = register_class_SimpleXMLElement(zend_ce_stringable, zend_ce_countable, spl_ce_RecursiveIterator); + ce_SimpleXMLElement->create_object = sxe_object_new; + ce_SimpleXMLElement->default_object_handlers = &sxe_object_handlers; + ce_SimpleXMLElement->get_iterator = php_sxe_get_iterator; memcpy(&sxe_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); sxe_object_handlers.offset = XtOffsetOf(php_sxe_object, zo); @@ -2696,12 +2689,9 @@ PHP_MINIT_FUNCTION(simplexml) sxe_object_handlers.get_closure = NULL; sxe_object_handlers.get_gc = sxe_get_gc; - /* TODO: Why do we have two variables for this? */ - ce_SimpleXMLElement = sxe_class_entry; - ce_SimpleXMLIterator = register_class_SimpleXMLIterator(ce_SimpleXMLElement); - php_libxml_register_export(sxe_class_entry, simplexml_export_node); + php_libxml_register_export(ce_SimpleXMLElement, simplexml_export_node); return SUCCESS; } @@ -2710,7 +2700,7 @@ PHP_MINIT_FUNCTION(simplexml) /* {{{ PHP_MSHUTDOWN_FUNCTION(simplexml) */ PHP_MSHUTDOWN_FUNCTION(simplexml) { - sxe_class_entry = NULL; + ce_SimpleXMLElement = NULL; return SUCCESS; } /* }}} */ diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 029edcdfb21de..378f707f44c3b 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -454,7 +454,9 @@ static void spl_filesystem_info_set_filename(spl_filesystem_object *intern, zend path_len = ZSTR_LEN(path); if (path_len > 1 && IS_SLASH_AT(ZSTR_VAL(path), path_len-1)) { - path_len--; + do { + path_len--; + } while (path_len > 1 && IS_SLASH_AT(ZSTR_VAL(path), path_len - 1)); intern->file_name = zend_string_init(ZSTR_VAL(path), path_len, 0); } else { intern->file_name = zend_string_copy(path); diff --git a/ext/spl/spl_dllist.c b/ext/spl/spl_dllist.c index 176989936ed8f..f1445fa5d905e 100644 --- a/ext/spl/spl_dllist.c +++ b/ext/spl/spl_dllist.c @@ -72,8 +72,8 @@ typedef struct _spl_dllist_it spl_dllist_it; struct _spl_dllist_object { spl_ptr_llist *llist; - int traverse_position; spl_ptr_llist_element *traverse_pointer; + int traverse_position; int flags; zend_function *fptr_offset_get; zend_function *fptr_offset_set; diff --git a/ext/spl/tests/gh11338.phpt b/ext/spl/tests/gh11338.phpt new file mode 100644 index 0000000000000..0a59cea9e7468 --- /dev/null +++ b/ext/spl/tests/gh11338.phpt @@ -0,0 +1,47 @@ +--TEST-- +GH-11338 (SplFileInfo empty getBasename with more than on slash) +--FILE-- +getBasename()); + var_dump($file->getFilename()); +} + +test('/dir/anotherdir/basedir//'); +test('/dir/anotherdir/basedir/'); +test('/dir/anotherdir/basedir'); +test('/dir/anotherdir//basedir'); +test('///'); +test('//'); +test('/'); +test(''); + +?> +--EXPECT-- +Testing: '/dir/anotherdir/basedir//' +string(7) "basedir" +string(7) "basedir" +Testing: '/dir/anotherdir/basedir/' +string(7) "basedir" +string(7) "basedir" +Testing: '/dir/anotherdir/basedir' +string(7) "basedir" +string(7) "basedir" +Testing: '/dir/anotherdir//basedir' +string(7) "basedir" +string(7) "basedir" +Testing: '///' +string(0) "" +string(1) "/" +Testing: '//' +string(0) "" +string(1) "/" +Testing: '/' +string(0) "" +string(1) "/" +Testing: '' +string(0) "" +string(0) "" diff --git a/ext/standard/array.c b/ext/standard/array.c index 86751acfb07cb..6bb146eb46888 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1233,15 +1233,58 @@ PHP_FUNCTION(min) } } else { /* mixed min ( mixed $value1 , mixed $value2 [, mixed $value3... ] ) */ - zval *min, result; + zval *min; uint32_t i; min = &args[0]; + zend_long min_lval; + double min_dval; - for (i = 1; i < argc; i++) { - is_smaller_function(&result, &args[i], min); - if (Z_TYPE(result) == IS_TRUE) { - min = &args[i]; + if (Z_TYPE_P(min) == IS_LONG) { + min_lval = Z_LVAL_P(min); + + for (i = 1; i < argc; i++) { + if (EXPECTED(Z_TYPE(args[i]) == IS_LONG)) { + if (min_lval > Z_LVAL(args[i])) { + min_lval = Z_LVAL(args[i]); + min = &args[i]; + } + } else if (Z_TYPE(args[i]) == IS_DOUBLE && (zend_dval_to_lval((double) min_lval) == min_lval)) { + /* if min_lval can be exactly represented as a double, go to double dedicated code */ + min_dval = (double) min_lval; + goto double_compare; + } else { + goto generic_compare; + } + } + + RETURN_LONG(min_lval); + } else if (Z_TYPE_P(min) == IS_DOUBLE) { + min_dval = Z_DVAL_P(min); + + for (i = 1; i < argc; i++) { + if (EXPECTED(Z_TYPE(args[i]) == IS_DOUBLE)) { + double_compare: + if (min_dval > Z_DVAL(args[i])) { + min_dval = Z_DVAL(args[i]); + min = &args[i]; + } + } else if (Z_TYPE(args[i]) == IS_LONG && (zend_dval_to_lval((double) Z_LVAL(args[i])) == Z_LVAL(args[i]))) { + /* if the value can be exactly represented as a double, use double dedicated code otherwise generic */ + if (min_dval > (double)Z_LVAL(args[i])) { + min_dval = (double)Z_LVAL(args[i]); + min = &args[i]; + } + } else { + goto generic_compare; + } + } + } else { + for (i = 1; i < argc; i++) { + generic_compare: + if (zend_compare(&args[i], min) < 0) { + min = &args[i]; + } } } @@ -1279,15 +1322,58 @@ PHP_FUNCTION(max) } } else { /* mixed max ( mixed $value1 , mixed $value2 [, mixed $value3... ] ) */ - zval *max, result; + zval *max; uint32_t i; max = &args[0]; + zend_long max_lval; + double max_dval; - for (i = 1; i < argc; i++) { - is_smaller_or_equal_function(&result, &args[i], max); - if (Z_TYPE(result) == IS_FALSE) { - max = &args[i]; + if (Z_TYPE_P(max) == IS_LONG) { + max_lval = Z_LVAL_P(max); + + for (i = 1; i < argc; i++) { + if (EXPECTED(Z_TYPE(args[i]) == IS_LONG)) { + if (max_lval < Z_LVAL(args[i])) { + max_lval = Z_LVAL(args[i]); + max = &args[i]; + } + } else if (Z_TYPE(args[i]) == IS_DOUBLE && (zend_dval_to_lval((double) max_lval) == max_lval)) { + /* if max_lval can be exactly represented as a double, go to double dedicated code */ + max_dval = (double) max_lval; + goto double_compare; + } else { + goto generic_compare; + } + } + + RETURN_LONG(max_lval); + } else if (Z_TYPE_P(max) == IS_DOUBLE) { + max_dval = Z_DVAL_P(max); + + for (i = 1; i < argc; i++) { + if (EXPECTED(Z_TYPE(args[i]) == IS_DOUBLE)) { + double_compare: + if (max_dval < Z_DVAL(args[i])) { + max_dval = Z_DVAL(args[i]); + max = &args[i]; + } + } else if (Z_TYPE(args[i]) == IS_LONG && (zend_dval_to_lval((double) Z_LVAL(args[i])) == Z_LVAL(args[i]))) { + /* if the value can be exactly represented as a double, use double dedicated code otherwise generic */ + if (max_dval < (double)Z_LVAL(args[i])) { + max_dval = (double)Z_LVAL(args[i]); + max = &args[i]; + } + } else { + goto generic_compare; + } + } + } else { + for (i = 1; i < argc; i++) { + generic_compare: + if (zend_compare(&args[i], max) > 0) { + max = &args[i]; + } } } @@ -5782,8 +5868,10 @@ PHP_FUNCTION(array_multisort) * of the input arrays + 1. The last column is UNDEF to indicate the end * of the row. It also stores the original position for stable sorting. */ indirect = (Bucket **)safe_emalloc(array_size, sizeof(Bucket *), 0); + /* Move num_arrays multiplication to size because it's essentially impossible to overflow. */ + Bucket *indirects = (Bucket *)safe_emalloc(array_size, sizeof(Bucket) * (num_arrays + 1), 0); for (i = 0; i < array_size; i++) { - indirect[i] = (Bucket *)safe_emalloc((num_arrays + 1), sizeof(Bucket), 0); + indirect[i] = indirects + (i * (num_arrays + 1)); } for (i = 0; i < num_arrays; i++) { k = 0; @@ -5847,9 +5935,7 @@ PHP_FUNCTION(array_multisort) RETVAL_TRUE; clean_up: - for (i = 0; i < array_size; i++) { - efree(indirect[i]); - } + efree(indirects); efree(indirect); efree(func); efree(arrays); diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index c382ba4116c09..daaaa41b00f9b 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -160,6 +160,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, return NULL; } + ZEND_ASSERT(resource->scheme); if (!zend_string_equals_literal_ci(resource->scheme, "http") && !zend_string_equals_literal_ci(resource->scheme, "https")) { if (!context || @@ -183,7 +184,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, return NULL; } - use_ssl = resource->scheme && (ZSTR_LEN(resource->scheme) > 4) && ZSTR_VAL(resource->scheme)[4] == 's'; + use_ssl = (ZSTR_LEN(resource->scheme) > 4) && ZSTR_VAL(resource->scheme)[4] == 's'; /* choose default ports */ if (use_ssl && resource->port == 0) resource->port = 443; diff --git a/ext/standard/tests/array/max_int_float_optimisation.phpt b/ext/standard/tests/array/max_int_float_optimisation.phpt new file mode 100644 index 0000000000000..0f5df35d12a7c --- /dev/null +++ b/ext/standard/tests/array/max_int_float_optimisation.phpt @@ -0,0 +1,61 @@ +--TEST-- +Check max() optimisation for int and float types +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Start as int optimisation: +int(10) +int(10) +int(10) +int(10) +int(10) +int(10) +string(2) "15" +Check that int not representable as float works: +int(-9223372036854775807) +float(1.8446744073709552E+19) +float(INF) +Start as float optimisation: +float(10.5) +float(10.5) +float(10.5) +float(10.5) +float(10.5) +float(10.5) +string(4) "15.5" +Check that int not representable as float works: +int(-9223372036854775807) +float(1.8446744073709552E+19) +float(INF) diff --git a/ext/standard/tests/array/min_int_float_optimisation.phpt b/ext/standard/tests/array/min_int_float_optimisation.phpt new file mode 100644 index 0000000000000..e383b833694c7 --- /dev/null +++ b/ext/standard/tests/array/min_int_float_optimisation.phpt @@ -0,0 +1,61 @@ +--TEST-- +Check min() optimisation for int and float types +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Start as int optimisation: +int(2) +int(2) +int(2) +int(2) +int(2) +int(2) +string(1) "1" +Check that int not representable as float works: +int(9223372036854775806) +float(-1.8446744073709552E+19) +int(9223372036854775806) +Start as float optimisation: +float(2.5) +float(2.5) +float(2.5) +float(2.5) +float(2.5) +float(2.5) +string(3) "1.5" +Check that int not representable as float works: +int(9223372036854775806) +float(-1.8446744073709552E+19) +int(9223372036854775806) diff --git a/ext/standard/tests/serialize/bug68594.phpt b/ext/standard/tests/serialize/bug68594.phpt index 69ff9e2bf4cd3..e2233187be9d1 100644 --- a/ext/standard/tests/serialize/bug68594.phpt +++ b/ext/standard/tests/serialize/bug68594.phpt @@ -9,7 +9,7 @@ for ($i=4; $i<100; $i++) { $m->aaa = array(1,2,&$u,4,5); $m->bbb = 1; - $m->ccc = &$u; + // $m->ccc = &$u; $m->ddd = str_repeat("A", $i); $z = serialize($m); diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index a050fb5f74a70..0865f248624be 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -539,7 +539,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; @@ -551,21 +551,24 @@ static int is_property_visibility_changed(zend_class_entry *ce, zval *key) if (unmangled_class == NULL) { existing_propinfo = zend_hash_find_ptr(&ce->properties_info, Z_STR_P(key)); - if (existing_propinfo != NULL) { - zval_ptr_dtor_str(key); - ZVAL_STR_COPY(key, existing_propinfo->name); - return 1; - } } else { if (!strcmp(unmangled_class, "*") || !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))) { existing_propinfo = zend_hash_str_find_ptr( &ce->properties_info, unmangled_prop, unmangled_prop_len); - if (existing_propinfo != NULL) { - zval_ptr_dtor_str(key); - ZVAL_STR_COPY(key, existing_propinfo->name); - return 1; - } + } + } + + if (existing_propinfo != NULL) { + if (!(existing_propinfo->flags & ZEND_ACC_VIRTUAL)) { + zval_ptr_dtor_str(key); + ZVAL_STR_COPY(key, existing_propinfo->name); + return 1; + } else { + php_error_docref(NULL, E_WARNING, + "Cannot unserialize value for hooked property %s::$%s", + ZSTR_VAL(existing_propinfo->ce->name), Z_STRVAL_P(key)); + return -1; } } } diff --git a/ext/tokenizer/tests/001.phpt b/ext/tokenizer/tests/001.phpt index 52eb469a970f8..15bc8cf8a3cfb 100644 --- a/ext/tokenizer/tests/001.phpt +++ b/ext/tokenizer/tests/001.phpt @@ -99,6 +99,7 @@ echo token_name(T_LIST), "\n"; echo token_name(T_ARRAY), "\n"; echo token_name(T_CLASS_C), "\n"; echo token_name(T_FUNC_C), "\n"; +echo token_name(T_PROPERTY_C), "\n"; echo token_name(T_METHOD_C), "\n"; echo token_name(T_LINE), "\n"; echo token_name(T_FILE), "\n"; @@ -223,6 +224,7 @@ T_LIST T_ARRAY T_CLASS_C T_FUNC_C +T_PROPERTY_C T_METHOD_C T_LINE T_FILE diff --git a/ext/tokenizer/tests/token_get_all_variation12.phpt b/ext/tokenizer/tests/token_get_all_variation12.phpt index 44f7673c7894c..fc9224ba63972 100644 --- a/ext/tokenizer/tests/token_get_all_variation12.phpt +++ b/ext/tokenizer/tests/token_get_all_variation12.phpt @@ -10,6 +10,7 @@ tokenizer * __CLASS__ - T_CLASS_C * __TRAIT__ - T_TRAIT_C * __FUNCTION__ - T_FUNC_C + * __PROPERTY__ - T_PROPERTY_C * __LINE__ - T_LINE * __METHOD__ - T_METHOD_C */ diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 77b67de6b1c6e..b7a3b9d6ebebd 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -39,6 +39,7 @@ char *get_token_type_name(int token_type) case T_CONSTANT_ENCAPSED_STRING: return "T_CONSTANT_ENCAPSED_STRING"; case T_STRING_VARNAME: return "T_STRING_VARNAME"; case T_NUM_STRING: return "T_NUM_STRING"; + case T_PARENT_PROPERTY_HOOK_NAME: return "T_PARENT_PROPERTY_HOOK_NAME"; case T_INCLUDE: return "T_INCLUDE"; case T_INCLUDE_ONCE: return "T_INCLUDE_ONCE"; case T_EVAL: return "T_EVAL"; @@ -117,6 +118,7 @@ char *get_token_type_name(int token_type) case T_TRAIT_C: return "T_TRAIT_C"; case T_METHOD_C: return "T_METHOD_C"; case T_FUNC_C: return "T_FUNC_C"; + case T_PROPERTY_C: return "T_PROPERTY_C"; case T_NS_C: return "T_NS_C"; case T_ATTRIBUTE: return "T_ATTRIBUTE"; case T_PLUS_EQUAL: return "T_PLUS_EQUAL"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 42c69c4f82eff..03c5d71f916ef 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -62,6 +62,11 @@ * @cvalue T_NUM_STRING */ const T_NUM_STRING = UNKNOWN; +/** + * @var int + * @cvalue T_PARENT_PROPERTY_HOOK_NAME + */ +const T_PARENT_PROPERTY_HOOK_NAME = UNKNOWN; /** * @var int * @cvalue T_INCLUDE @@ -452,6 +457,11 @@ * @cvalue T_FUNC_C */ const T_FUNC_C = UNKNOWN; +/** + * @var int + * @cvalue T_PROPERTY_C + */ +const T_PROPERTY_C = UNKNOWN; /** * @var int * @cvalue T_NS_C diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index ef665193b2ff3..796434a2096fa 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1dd42ee5b5b818c5bd131b5c4bbb13c153d99499 */ + * Stub hash: a4ebf01ece6ea9c9fe1dc442db68509978c7378c */ @@ -17,6 +17,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_CONSTANT_ENCAPSED_STRING", T_CONSTANT_ENCAPSED_STRING, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_STRING_VARNAME", T_STRING_VARNAME, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NUM_STRING", T_NUM_STRING, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_PARENT_PROPERTY_HOOK_NAME", T_PARENT_PROPERTY_HOOK_NAME, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INCLUDE", T_INCLUDE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INCLUDE_ONCE", T_INCLUDE_ONCE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_EVAL", T_EVAL, CONST_PERSISTENT); @@ -95,6 +96,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_TRAIT_C", T_TRAIT_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_METHOD_C", T_METHOD_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_FUNC_C", T_FUNC_C, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_PROPERTY_C", T_PROPERTY_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NS_C", T_NS_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ATTRIBUTE", T_ATTRIBUTE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PLUS_EQUAL", T_PLUS_EQUAL, CONST_PERSISTENT); diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c index 7f3d1e0e42170..5f1482ea31b67 100644 --- a/ext/xsl/xsltprocessor.c +++ b/ext/xsl/xsltprocessor.c @@ -289,7 +289,7 @@ static void xsl_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int t zval_ptr_dtor(&retval); } zend_string_release_ex(callable, 0); - zval_ptr_dtor(&handler); + zval_ptr_dtor_nogc(&handler); if (fci.param_count > 0) { for (i = 0; i < nargs - 1; i++) { zval_ptr_dtor(&args[i]); diff --git a/main/streams/memory.c b/main/streams/memory.c index f53084a6c3a77..444f963761729 100644 --- a/main/streams/memory.c +++ b/main/streams/memory.c @@ -349,7 +349,7 @@ static ssize_t php_stream_temp_write(php_stream *stream, const char *buf, size_t } if (php_stream_is(ts->innerstream, PHP_STREAM_IS_MEMORY)) { zend_off_t pos = php_stream_tell(ts->innerstream); - + if (pos + count >= ts->smax) { zend_string *membuf = php_stream_memory_get_buffer(ts->innerstream); php_stream *file = php_stream_fopen_temporary_file(ts->tmpdir, "php", NULL); @@ -614,6 +614,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con int base64 = 0; zend_string *base64_comma = NULL; + ZEND_ASSERT(mode); + ZVAL_NULL(&meta); if (memcmp(path, "data:", 5)) { return NULL; @@ -729,7 +731,7 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con stream->ops = &php_stream_rfc2397_ops; ts = (php_stream_temp_data*)stream->abstract; assert(ts != NULL); - ts->mode = mode && mode[0] == 'r' && mode[1] != '+' ? TEMP_STREAM_READONLY : 0; + ts->mode = mode[0] == 'r' && mode[1] != '+' ? TEMP_STREAM_READONLY : 0; ZVAL_COPY_VALUE(&ts->meta, &meta); } if (base64_comma) { diff --git a/run-tests.php b/run-tests.php index 0d0b683becd79..a112e8f5347f0 100755 --- a/run-tests.php +++ b/run-tests.php @@ -2834,7 +2834,7 @@ function run_test(string $php, $file, array $env): string function error_may_be_retried(string $output): bool { - return preg_match('((timed out)|(connection refused))i', $output) === 1; + return preg_match('((timed out)|(connection refused)|(404: page not found)|(address already in use)|(mailbox already exists))i', $output) === 1; } function expectf_to_regex(?string $wanted): string diff --git a/sapi/cli/ps_title.c b/sapi/cli/ps_title.c index 8ff7ef719e17f..8eb14963c682a 100644 --- a/sapi/cli/ps_title.c +++ b/sapi/cli/ps_title.c @@ -169,19 +169,18 @@ char** save_ps_args(int argc, char** argv) end_of_area = argv[i] + strlen(argv[i]); } + if (!is_contiguous_area) { + goto clobber_error; + } + /* * check for contiguous environ strings following argv */ - for (i = 0; is_contiguous_area && (environ[i] != NULL); i++) + for (i = 0; environ[i] != NULL; i++) { - if (end_of_area + 1 != environ[i]) { - is_contiguous_area = false; + if (end_of_area + 1 == environ[i]) { + end_of_area = environ[i] + strlen(environ[i]); } - end_of_area = environ[i] + strlen(environ[i]); - } - - if (!is_contiguous_area) { - goto clobber_error; } ps_buffer = argv[0]; diff --git a/sapi/phpdbg/tests/print_001.phpt b/sapi/phpdbg/tests/print_001.phpt index c25c5178fef4c..a981cb0001f67 100644 --- a/sapi/phpdbg/tests/print_001.phpt +++ b/sapi/phpdbg/tests/print_001.phpt @@ -29,7 +29,7 @@ Foo\Bar::Foo: ; (lines=5, args=1, vars=1, tmps=1) ; %s:5-7 L0005 0000 CV0($bar) = RECV 1 -L0006 0001 INIT_NS_FCALL_BY_NAME 1 string("Foo\var_dump") +L0006 0001 INIT_NS_FCALL_BY_NAME 1 string("Foo\\var_dump") L0006 0002 SEND_VAR_EX CV0($bar) 1 L0006 0003 DO_FCALL L0007 0004 RETURN null @@ -44,10 +44,10 @@ prompt> [Context %s (9 ops)] $_main: ; (lines=9, args=0, vars=0, tmps=4) ; %s:1-21 -L0018 0000 V0 = NEW 0 string("Foo\Bar") +L0018 0000 V0 = NEW 0 string("Foo\\Bar") L0018 0001 DO_FCALL L0018 0002 INIT_METHOD_CALL 1 V0 string("Foo") -L0018 0003 SEND_VAL_EX string("test") 1 +L0018 0003 SEND_VAL_EX string("test \"quotes\"") 1 L0018 0004 DO_FCALL L0019 0005 INIT_FCALL %d %d string("foo") L0019 0006 SEND_VAL string("test") 1 @@ -72,6 +72,6 @@ namespace { var_dump(strrev($baz)); } - (new \Foo\Bar)->Foo("test"); + (new \Foo\Bar)->Foo('test "quotes"'); foo("test"); } diff --git a/tests/classes/interface_member.phpt b/tests/classes/interface_member.phpt index 04fff625f3aa4..78f329f221502 100644 --- a/tests/classes/interface_member.phpt +++ b/tests/classes/interface_member.phpt @@ -8,4 +8,4 @@ interface if_a { } ?> --EXPECTF-- -Fatal error: Interfaces may not include properties in %s on line %d +Fatal error: Interfaces may only include hooked properties in %s on line %d diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 20fcf2409922f..93f906d9b2fca 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -240,7 +240,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ - zend_enum.c zend_fibers.c zend_atomic.c"); + zend_enum.c zend_fibers.c zend_atomic.c zend_property_hooks.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c"); var PHP_ASSEMBLER = PATH_PROG({