Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,12 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
cache_size += 2 * sizeof(void *);
}
break;
case ZEND_DECLARE_LAMBDA_FUNCTION:
if (opline->extended_value != (uint32_t)-1) {
opline->extended_value = cache_size;
cache_size += sizeof(void *);
}
break;
}
opline++;
}
Expand Down
3 changes: 1 addition & 2 deletions Zend/tests/arrow_functions/005.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ class Test {

?>
--EXPECT--
object(Test)#1 (0) {
}
NULL
object(Test)#1 (0) {
}
object(Test)#1 (0) {
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/bug75079_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ int(123)

Fatal error: Uncaught Error: Cannot access private property Foo::$bar in %s:%d
Stack trace:
#0 %s(%d): A->{closure:%s:%d}()
#0 %s(%d): A::{closure:%s:%d}()
#1 {main}
thrown in %s on line %d
4 changes: 1 addition & 3 deletions Zend/tests/closures/closure_020.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object(foo)#%d (2) {
["test":"foo":private]=>
int(3)
["a"]=>
object(Closure)#%d (5) {
object(Closure)#%d (%d) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
Expand All @@ -40,8 +40,6 @@ object(foo)#%d (2) {
["a"]=>
*RECURSION*
}
["this"]=>
*RECURSION*
}
}
bool(true)
Expand Down
11 changes: 2 additions & 9 deletions Zend/tests/closures/closure_026.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,13 @@ object(foo)#%d (1) {
["a"]=>
array(1) {
[0]=>
object(Closure)#%d (4) {
object(Closure)#%d (%d) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
["this"]=>
*RECURSION*
}
}
}
Expand All @@ -50,18 +48,13 @@ int(1)
string(1) "a"
array(1) {
[0]=>
object(Closure)#%d (4) {
object(Closure)#%d (%d) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
["this"]=>
object(foo)#%d (1) {
["a"]=>
*RECURSION*
}
}
}
int(1)
4 changes: 1 addition & 3 deletions Zend/tests/closures/closure_040.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,5 @@ try {
$cas->bindTo($a, 'A');

?>
--EXPECTF--
--EXPECT--
Closure::bindTo(): Argument #2 ($newScope) must be of type object|string|null, array given

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
4 changes: 0 additions & 4 deletions Zend/tests/closures/closure_041.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,8 @@ Warning: Cannot unbind $this of closure using $this, this will be an error in PH
NULL

After binding, with same-class instance for the bound ones

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
scoped to A: bool(false)
bound: A (should be scoped to dummy class)

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
scoped to A: bool(true)
bound: A
After binding, with different instance for the bound ones
Expand Down
10 changes: 1 addition & 9 deletions Zend/tests/closures/closure_043.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ $d = $staticScoped->bindTo(new A, 'A');

echo "Done.\n";
?>
--EXPECTF--
--EXPECT--
Before binding
bool(false)
bool(false)
Expand All @@ -55,10 +55,6 @@ bool(false)
bool(false)

After binding, null scope, with instance

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
After binding, with scope, no instance
bool(true)
bool(false)
Expand All @@ -67,8 +63,4 @@ bool(true)
bool(false)

After binding, with scope, with instance

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
Done.
4 changes: 2 additions & 2 deletions Zend/tests/closures/closure_061.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ bindTo(null, Cls::class):
Success!

bindTo(new Cls, null):
Cannot bind an instance to a static closure, this will be an error in PHP 9
Cannot rebind scope of closure created from method, this will be an error in PHP 9

bindTo(new Cls, Cls::class):
Cannot bind an instance to a static closure, this will be an error in PHP 9
Success!

bindTo(null, null):
Cannot rebind scope of closure created from method, this will be an error in PHP 9
Expand Down
4 changes: 2 additions & 2 deletions Zend/tests/gc/bug60139.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Foo {
public $x;

public function __construct() {
$this->x = function() {};
$this->x = function () { $this; };
}
}

Expand All @@ -17,7 +17,7 @@ class Bar {

public function __construct() {
$self = $this;
$this->x = function() use ($self) {};
$this->x = function() use ($self) { $this; };
}
}

Expand Down
5 changes: 1 addition & 4 deletions Zend/tests/return_types/012.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $baz = new foo();
var_dump($baz->bar());
?>
--EXPECTF--
object(Closure)#%d (5) {
object(Closure)#%d (4) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
Expand All @@ -27,7 +27,4 @@ object(Closure)#%d (5) {
["test"]=>
string(3) "one"
}
["this"]=>
object(foo)#%d (0) {
}
}
2 changes: 1 addition & 1 deletion Zend/tests/return_types/013.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ var_dump($func=$baz->bar(), $func());
--EXPECTF--
Fatal error: Uncaught TypeError: foo::{closure:%s:%d}(): Return value must be of type array, null returned in %s:%d
Stack trace:
#0 %s(%d): foo->{closure:%s:%d}()
#0 %s(%d): foo::{closure:%s:%d}()
#1 {main}
thrown in %s on line %d
26 changes: 16 additions & 10 deletions Zend/zend_closures.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,19 @@ ZEND_METHOD(Closure, __invoke) /* {{{ */
/* }}} */

static bool zend_valid_closure_binding(
zend_closure *closure, zval *newthis, zend_class_entry *scope) /* {{{ */
zend_closure *closure, zval **newthis_ptr, zend_class_entry *scope) /* {{{ */
{
zval *newthis = *newthis_ptr;
zend_function *func = &closure->func;
bool is_fake_closure = (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0;
if (newthis) {
if (func->common.fn_flags & ZEND_ACC_STATIC) {
zend_error(E_WARNING, "Cannot bind an instance to a static closure, this will be an error in PHP 9");
return 0;
// FIXME: Restrict this workaround to implicitly static closures?
// Will need an additional fn_flag.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a zend_closure flag, passed to zend_create_closure() by ZEND_DECLARE_LAMBDA_FUNCTION. The flag can be stored in OBJ_EXTRA_FLAGS() as there is no other place to store a flag in zend_closure. I do this in the pfa branch: https://github.com/arnaud-lb/php-src/blob/23dce325280c7c6c90043ad3bf830073f6838df3/Zend/zend_types.h#L873 + https://github.com/arnaud-lb/php-src/blob/23dce325280c7c6c90043ad3bf830073f6838df3/Zend/zend_closures.c#L31

zend_object *obj = Z_OBJ_P(newthis);
ZVAL_UNDEF(newthis);
GC_DTOR(obj);
*newthis_ptr = NULL;
}

if (is_fake_closure && func->common.scope &&
Expand Down Expand Up @@ -144,13 +149,14 @@ ZEND_METHOD(Closure, call)

closure = (zend_closure *) Z_OBJ_P(ZEND_THIS);

newobj = Z_OBJ_P(newthis);
newclass = newobj->ce;
newclass = Z_OBJ_P(newthis)->ce;

if (!zend_valid_closure_binding(closure, newthis, newclass)) {
if (!zend_valid_closure_binding(closure, &newthis, newclass)) {
return;
}

newobj = newthis ? Z_OBJ_P(newthis) : NULL;

fci_cache.called_scope = newclass;
fci_cache.object = fci.object = newobj;

Expand Down Expand Up @@ -241,16 +247,16 @@ static void do_closure_bind(zval *return_value, zval *zclosure, zval *newthis, z
ce = NULL;
}

if (!zend_valid_closure_binding(closure, newthis, ce)) {
return;
}

if (newthis) {
called_scope = Z_OBJCE_P(newthis);
} else {
called_scope = ce;
}

if (!zend_valid_closure_binding(closure, &newthis, ce)) {
return;
}

zend_create_closure(return_value, &closure->func, ce, called_scope, newthis);
}

Expand Down
39 changes: 38 additions & 1 deletion Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context, zend_op_arra
CG(context).in_jmp_frameless_branch = false;
CG(context).active_property_info_name = NULL;
CG(context).active_property_hook_kind = (zend_property_hook_kind)-1;
CG(context).closure_may_use_this = false;
}
/* }}} */

Expand Down Expand Up @@ -4022,6 +4023,8 @@ static void zend_compile_dynamic_call(znode *result, znode *name_node, zend_ast
zend_string *method = zend_string_init(colon + 1, ZSTR_LEN(str) - (colon - ZSTR_VAL(str)) - 1, 0);
zend_op *opline = get_next_op();

CG(context).closure_may_use_this = true;

opline->opcode = ZEND_INIT_STATIC_METHOD_CALL;
opline->op1_type = IS_CONST;
opline->op1.constant = zend_add_class_name_literal(class);
Expand Down Expand Up @@ -4731,14 +4734,19 @@ static uint32_t zend_compile_frameless_icall(znode *result, zend_ast_list *args,
static void zend_compile_ns_call(znode *result, znode *name_node, zend_ast *args_ast, uint32_t lineno, uint32_t type) /* {{{ */
{
int name_constants = zend_add_ns_func_name_literal(Z_STR(name_node->u.constant));
zend_string *lc_func_name = Z_STR_P(CT_CONSTANT_EX(CG(active_op_array), name_constants + 2));

if (zend_string_equals_literal(lc_func_name, "call_user_func")
|| zend_string_equals_literal(lc_func_name, "call_user_func_array")) {
CG(context).closure_may_use_this = true;
}

/* Find frameless function with same name. */
zend_function *frameless_function = NULL;
if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT
&& !zend_args_contain_unpack_or_named(zend_ast_get_list(args_ast))
/* Avoid blowing up op count with nested frameless branches. */
&& !CG(context).in_jmp_frameless_branch) {
zend_string *lc_func_name = Z_STR_P(CT_CONSTANT_EX(CG(active_op_array), name_constants + 2));
frameless_function = zend_hash_find_ptr(CG(function_table), lc_func_name);
}

Expand Down Expand Up @@ -5166,6 +5174,7 @@ static void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{
if (name_ast->kind != ZEND_AST_ZVAL || Z_TYPE_P(zend_ast_get_zval(name_ast)) != IS_STRING) {
zend_compile_expr(&name_node, name_ast);
zend_compile_dynamic_call(result, &name_node, args_ast, ast->lineno);
CG(context).closure_may_use_this = true;
return;
}

Expand Down Expand Up @@ -5198,6 +5207,9 @@ static void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{
zend_string_release(lcname);
zval_ptr_dtor(&name_node.u.constant);
return;
} else if (fbc && (zend_string_equals_literal(lcname, "call_user_func")
|| zend_string_equals_literal(lcname, "call_user_func_array"))) {
CG(context).closure_may_use_this = true;
}

if (!fbc
Expand Down Expand Up @@ -5356,6 +5368,8 @@ static void zend_compile_static_call(znode *result, zend_ast *ast, uint32_t type
}
}

CG(context).closure_may_use_this = true;

opline = get_next_op();
opline->opcode = ZEND_INIT_STATIC_METHOD_CALL;

Expand Down Expand Up @@ -8413,6 +8427,7 @@ static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array,
if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
opline = zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, NULL, NULL);
opline->op2.num = func_ref;
opline->extended_value = (uint32_t)-1;
} else {
opline = get_next_op();
opline->opcode = ZEND_DECLARE_FUNCTION;
Expand Down Expand Up @@ -8595,6 +8610,27 @@ static zend_op_array *zend_compile_func_decl_ex(

zend_compile_stmt(stmt_ast);

if (decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) {
/* Attempt to infer static for closures that don't use $this. */
if (!(op_array->fn_flags & ZEND_ACC_USES_THIS)
&& !CG(context).closure_may_use_this
&& !info.varvars_used) {
op_array->fn_flags |= ZEND_ACC_STATIC;
}

zend_op_array *declaring_op_array = orig_oparray_context.op_array;

if ((op_array->fn_flags & ZEND_ACC_STATIC)
&& !op_array->static_variables
&& declaring_op_array->last) {
zend_op *declare_lambda_op = &declaring_op_array->opcodes[declaring_op_array->last - 1];
if (declare_lambda_op->opcode == ZEND_DECLARE_LAMBDA_FUNCTION) {
declare_lambda_op->extended_value = declaring_op_array->cache_size;
declaring_op_array->cache_size += sizeof(void *);
}
}
}

if (is_method) {
CG(zend_lineno) = decl->start_lineno;
zend_check_magic_method_implementation(
Expand Down Expand Up @@ -11941,6 +11977,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */
return;
case ZEND_AST_CLOSURE:
case ZEND_AST_ARROW_FUNC:
CG(context).closure_may_use_this = true;
zend_compile_func_decl(result, ast, FUNC_DECL_LEVEL_NESTED);
return;
case ZEND_AST_THROW:
Expand Down
3 changes: 2 additions & 1 deletion Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ typedef struct _zend_oparray_context {
zend_string *active_property_info_name;
zend_property_hook_kind active_property_hook_kind;
bool in_jmp_frameless_branch;
bool has_assigned_to_http_response_header;
bool has_assigned_to_http_response_header;
bool closure_may_use_this;
} zend_oparray_context;

/* Class, property and method flags class|meth.|prop.|const*/
Expand Down
Loading
Loading