mirror of
https://github.com/php/php-src.git
synced 2024-11-23 18:04:36 +08:00
Always memoize calls in lhs of coalesce assignment
We don't want to invoke calls twice, even if they are considered "variables", i.e. might be writable if returning a reference. Function calls behave the same in all BP contexts so they don't need to be invoked twice. The singular exception to this is nullsafe coalesce in isset/empty, because it needs to return false/true respectively when short-circuited. However, since nullsafe calls are not allwed in write context we may ignore this problem. Closes GH-11592
This commit is contained in:
parent
c0ce3e7efa
commit
1057cce1c0
64
Zend/tests/assign_coalesce_008.phpt
Normal file
64
Zend/tests/assign_coalesce_008.phpt
Normal file
@ -0,0 +1,64 @@
|
||||
--TEST--
|
||||
Assign coalesce: All calls should be memoized
|
||||
--FILE--
|
||||
<?php
|
||||
class Foo {
|
||||
public $prop;
|
||||
|
||||
public function foo() {
|
||||
echo __METHOD__, "\n";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function bar() {
|
||||
echo __METHOD__, "\n";
|
||||
return 'prop';
|
||||
}
|
||||
|
||||
public function __isset($name) {
|
||||
echo __METHOD__, "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
public function __set($name, $value) {
|
||||
echo __METHOD__, "\n";
|
||||
var_dump($value);
|
||||
}
|
||||
}
|
||||
|
||||
function &foo() {
|
||||
global $foo;
|
||||
echo __FUNCTION__, "\n";
|
||||
return $foo;
|
||||
}
|
||||
function bar() {
|
||||
echo __FUNCTION__, "\n";
|
||||
}
|
||||
|
||||
foo(bar())['bar'] ??= 42;
|
||||
var_dump($foo);
|
||||
|
||||
$foo = new Foo();
|
||||
$foo->foo()->foo()->{$foo->bar()} ??= 42;
|
||||
var_dump($foo);
|
||||
$foo->foo()->baz ??= 42;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
bar
|
||||
foo
|
||||
array(1) {
|
||||
["bar"]=>
|
||||
int(42)
|
||||
}
|
||||
Foo::foo
|
||||
Foo::foo
|
||||
Foo::bar
|
||||
object(Foo)#1 (1) {
|
||||
["prop"]=>
|
||||
int(42)
|
||||
}
|
||||
Foo::foo
|
||||
Foo::__isset
|
||||
Foo::__set
|
||||
int(42)
|
@ -4589,14 +4589,7 @@ static void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{
|
||||
if (runtime_resolution) {
|
||||
if (zend_string_equals_literal_ci(zend_ast_get_str(name_ast), "assert")
|
||||
&& !is_callable_convert) {
|
||||
if (CG(memoize_mode) == ZEND_MEMOIZE_NONE) {
|
||||
zend_compile_assert(result, zend_ast_get_list(args_ast), Z_STR(name_node.u.constant), NULL, ast->lineno);
|
||||
} else {
|
||||
/* We want to always memoize assert calls, even if they are positioned in
|
||||
* write-context. This prevents memoizing their arguments that might not be
|
||||
* evaluated if assertions are disabled, using a TMPVAR that wasn't initialized. */
|
||||
zend_compile_memoized_expr(result, ast);
|
||||
}
|
||||
zend_compile_assert(result, zend_ast_get_list(args_ast), Z_STR(name_node.u.constant), NULL, ast->lineno);
|
||||
} else {
|
||||
zend_compile_ns_call(result, &name_node, args_ast, ast->lineno);
|
||||
}
|
||||
@ -4615,14 +4608,7 @@ static void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{
|
||||
|
||||
/* Special assert() handling should apply independently of compiler flags. */
|
||||
if (fbc && zend_string_equals_literal(lcname, "assert") && !is_callable_convert) {
|
||||
if (CG(memoize_mode) == ZEND_MEMOIZE_NONE) {
|
||||
zend_compile_assert(result, zend_ast_get_list(args_ast), lcname, fbc, ast->lineno);
|
||||
} else {
|
||||
/* We want to always memoize assert calls, even if they are positioned in
|
||||
* write-context. This prevents memoizing their arguments that might not be
|
||||
* evaluated if assertions are disabled, using a TMPVAR that wasn't initialized. */
|
||||
zend_compile_memoized_expr(result, ast);
|
||||
}
|
||||
zend_compile_assert(result, zend_ast_get_list(args_ast), lcname, fbc, ast->lineno);
|
||||
zend_string_release(lcname);
|
||||
zval_ptr_dtor(&name_node.u.constant);
|
||||
return;
|
||||
@ -10591,6 +10577,17 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty
|
||||
{
|
||||
CG(zend_lineno) = zend_ast_get_lineno(ast);
|
||||
|
||||
if (CG(memoize_mode) != ZEND_MEMOIZE_NONE) {
|
||||
switch (ast->kind) {
|
||||
case ZEND_AST_CALL:
|
||||
case ZEND_AST_METHOD_CALL:
|
||||
case ZEND_AST_NULLSAFE_METHOD_CALL:
|
||||
case ZEND_AST_STATIC_CALL:
|
||||
zend_compile_memoized_expr(result, ast);
|
||||
return &CG(active_op_array)->opcodes[CG(active_op_array)->last - 1];
|
||||
}
|
||||
}
|
||||
|
||||
switch (ast->kind) {
|
||||
case ZEND_AST_VAR:
|
||||
return zend_compile_simple_var(result, ast, type, 0);
|
||||
|
Loading…
Reference in New Issue
Block a user