summaryrefslogtreecommitdiff
path: root/Zend
diff options
context:
space:
mode:
authorStanislav Malyshev <stas@php.net>2010-04-19 19:45:03 (GMT)
committerStanislav Malyshev <stas@php.net>2010-04-19 19:45:03 (GMT)
commitc93a4f192bb338aa9a22d44276684cf92dfe902d (patch)
tree4e2aa5c989856d9bafd0415f5d7d0ebd4d9c8458 /Zend
parent5a211da7af41f82ec123e63990942eae567dfef2 (diff)
downloadphp-c93a4f192bb338aa9a22d44276684cf92dfe902d.tar.gz
restore $this support for closures to its former glory
Diffstat (limited to 'Zend')
-rw-r--r--Zend/tests/closure_005.phpt74
-rw-r--r--Zend/tests/closure_007.phpt38
-rw-r--r--Zend/tests/closure_020.phpt6
-rw-r--r--Zend/tests/closure_024.phpt26
-rw-r--r--Zend/tests/closure_026.phpt11
-rwxr-xr-xZend/tests/closure_036.phpt33
-rw-r--r--Zend/zend_closures.c123
-rw-r--r--Zend/zend_closures.h3
-rw-r--r--Zend/zend_compile.c5
-rw-r--r--Zend/zend_compile.h2
-rw-r--r--Zend/zend_language_parser.y4
-rw-r--r--Zend/zend_vm_def.h2
-rw-r--r--Zend/zend_vm_execute.h2
13 files changed, 304 insertions, 25 deletions
diff --git a/Zend/tests/closure_005.phpt b/Zend/tests/closure_005.phpt
new file mode 100644
index 0000000..4e32faa
--- /dev/null
+++ b/Zend/tests/closure_005.phpt
@@ -0,0 +1,74 @@
+--TEST--
+Closure 005: Lambda inside class, lifetime of $this
+--FILE--
+<?php
+
+class A {
+ private $x;
+
+ function __construct($x) {
+ $this->x = $x;
+ }
+
+ function __destruct() {
+ echo "Destroyed\n";
+ }
+
+ function getIncer($val) {
+ return function() use ($val) {
+ $this->x += $val;
+ };
+ }
+
+ function getPrinter() {
+ return function() {
+ echo $this->x."\n";
+ };
+ }
+
+ function getError() {
+ return static function() {
+ echo $this->x."\n";
+ };
+ }
+
+ function printX() {
+ echo $this->x."\n";
+ }
+}
+
+$a = new A(3);
+$incer = $a->getIncer(2);
+$printer = $a->getPrinter();
+$error = $a->getError();
+
+$a->printX();
+$printer();
+$incer();
+$a->printX();
+$printer();
+
+unset($a);
+
+$incer();
+$printer();
+
+unset($incer);
+$printer();
+
+unset($printer);
+
+$error();
+
+echo "Done\n";
+?>
+--EXPECTF--
+3
+3
+5
+5
+7
+7
+Destroyed
+
+Fatal error: Using $this when not in object context in %sclosure_005.php on line 28
diff --git a/Zend/tests/closure_007.phpt b/Zend/tests/closure_007.phpt
new file mode 100644
index 0000000..89cd06d
--- /dev/null
+++ b/Zend/tests/closure_007.phpt
@@ -0,0 +1,38 @@
+--TEST--
+Closure 007: Nested lambdas in classes
+--FILE--
+<?php
+
+class A {
+ private $x = 0;
+
+ function getClosureGetter () {
+ return function () {
+ return function () {
+ $this->x++;
+ };
+ };
+ }
+
+ function printX () {
+ echo $this->x."\n";
+ }
+}
+
+$a = new A;
+$a->printX();
+$getClosure = $a->getClosureGetter();
+$a->printX();
+$closure = $getClosure();
+$a->printX();
+$closure();
+$a->printX();
+
+echo "Done\n";
+?>
+--EXPECT--
+0
+0
+0
+1
+Done
diff --git a/Zend/tests/closure_020.phpt b/Zend/tests/closure_020.phpt
index 9d04a9a..bec2bed 100644
--- a/Zend/tests/closure_020.phpt
+++ b/Zend/tests/closure_020.phpt
@@ -23,16 +23,18 @@ var_dump($y()->test);
?>
--EXPECTF--
-object(foo)#%d (%d) {
+object(foo)#%d (2) {
["test":"foo":private]=>
int(3)
["a"]=>
- object(Closure)#%d (1) {
+ object(Closure)#%d (2) {
["static"]=>
array(1) {
["a"]=>
*RECURSION*
}
+ ["this"]=>
+ *RECURSION*
}
}
bool(true)
diff --git a/Zend/tests/closure_024.phpt b/Zend/tests/closure_024.phpt
index 504e81e..74083f7 100644
--- a/Zend/tests/closure_024.phpt
+++ b/Zend/tests/closure_024.phpt
@@ -1,16 +1,26 @@
--TEST--
-Closure 024: Trying to clone the Closure object
+Closure 024: Clone the Closure object
--FILE--
<?php
-$a = function () {
- return clone function () {
- return 1;
- };
-};
+$a = 1;
+$c = function($add) use(&$a) { return $a+$add; };
-$a();
+$cc = clone $c;
+echo $c(10)."\n";
+echo $cc(10)."\n";
+
+$a++;
+
+echo $c(10)."\n";
+echo $cc(10)."\n";
+
+echo "Done.\n";
?>
--EXPECTF--
-Fatal error: Trying to clone an uncloneable object of class Closure in %s on line %d
+11
+11
+12
+12
+Done. \ No newline at end of file
diff --git a/Zend/tests/closure_026.phpt b/Zend/tests/closure_026.phpt
index f9e6bd5..150cc86 100644
--- a/Zend/tests/closure_026.phpt
+++ b/Zend/tests/closure_026.phpt
@@ -32,7 +32,9 @@ object(foo)#%d (1) {
["a"]=>
array(1) {
[0]=>
- object(Closure)#%d (0) {
+ object(Closure)#%d (1) {
+ ["this"]=>
+ *RECURSION*
}
}
}
@@ -41,7 +43,12 @@ int(1)
string(1) "a"
array(1) {
[0]=>
- object(Closure)#%d (0) {
+ object(Closure)#%d (1) {
+ ["this"]=>
+ object(foo)#%d (1) {
+ ["a"]=>
+ *RECURSION*
+ }
}
}
int(1)
diff --git a/Zend/tests/closure_036.phpt b/Zend/tests/closure_036.phpt
new file mode 100755
index 0000000..0f8ccb1
--- /dev/null
+++ b/Zend/tests/closure_036.phpt
@@ -0,0 +1,33 @@
+--TEST--
+Closure 036: Rebinding closures
+--FILE--
+<?php
+
+class A {
+ private $x;
+
+ public function __construct($v) {
+ $this->x = $v;
+ }
+
+ public function getIncrementor() {
+ return function() { return ++$this->x; };
+ }
+}
+
+$a = new A(0);
+$b = new A(10);
+
+$ca = $a->getIncrementor();
+$cb = $ca->bindTo($b);
+$cb2 = Closure::bind($b, $ca);
+
+var_dump($ca());
+var_dump($cb());
+var_dump($cb2());
+
+?>
+--EXPECTF--
+int(1)
+int(11)
+int(12) \ No newline at end of file
diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c
index f9eab38..20632e3 100644
--- a/Zend/zend_closures.c
+++ b/Zend/zend_closures.c
@@ -37,6 +37,7 @@
typedef struct _zend_closure {
zend_object std;
zend_function func;
+ zval *this_ptr;
HashTable *debug_info;
} zend_closure;
@@ -75,6 +76,39 @@ ZEND_METHOD(Closure, __invoke) /* {{{ */
}
/* }}} */
+/* {{{ proto Closure Closure::bindTo(object $to)
+ Bind a closure to another object */
+ZEND_METHOD(Closure, bindTo) /* {{{ */
+{
+ zval *newthis;
+ zend_closure *closure = (zend_closure *)zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!", &newthis) == FAILURE) {
+ RETURN_NULL();
+ }
+
+ zend_create_closure(return_value, &closure->func, newthis?Z_OBJCE_P(newthis):NULL, newthis TSRMLS_CC);
+}
+/* }}} */
+
+/* {{{ proto Closure Closure::bind(object $to, Closure $old)
+ Create a closure to with binding to another object */
+ZEND_METHOD(Closure, bind) /* {{{ */
+{
+ zval *newthis, *zclosure;
+ zend_closure *closure;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!O", &newthis, &zclosure, zend_ce_closure) == FAILURE) {
+ RETURN_NULL();
+ }
+
+ closure = (zend_closure *)zend_object_store_get_object(zclosure TSRMLS_CC);
+
+ zend_create_closure(return_value, &closure->func, newthis?Z_OBJCE_P(newthis):NULL, newthis TSRMLS_CC);
+}
+/* }}} */
+
+
static zend_function *zend_closure_get_constructor(zval *object TSRMLS_DC) /* {{{ */
{
zend_error(E_RECOVERABLE_ERROR, "Instantiation of 'Closure' is not allowed");
@@ -111,6 +145,13 @@ ZEND_API const zend_function *zend_get_closure_method_def(zval *obj TSRMLS_DC) /
}
/* }}} */
+ZEND_API zval* zend_get_closure_this_ptr(zval *obj TSRMLS_DC) /* {{{ */
+{
+ zend_closure *closure = (zend_closure *)zend_object_store_get_object(obj TSRMLS_CC);
+ return closure->this_ptr;
+}
+/* }}} */
+
static zend_function *zend_closure_get_method(zval **object_ptr, char *method_name, int method_len TSRMLS_DC) /* {{{ */
{
char *lc_name;
@@ -125,7 +166,7 @@ static zend_function *zend_closure_get_method(zval **object_ptr, char *method_na
return zend_get_closure_invoke_method(*object_ptr TSRMLS_CC);
}
free_alloca(lc_name, use_heap);
- return NULL;
+ return std_object_handlers.get_method(object_ptr, method_name, method_len TSRMLS_CC);
}
/* }}} */
@@ -187,6 +228,10 @@ static void zend_closure_free_storage(void *object TSRMLS_DC) /* {{{ */
efree(closure->debug_info);
}
+ if (closure->this_ptr) {
+ zval_ptr_dtor(&closure->this_ptr);
+ }
+
efree(closure);
}
/* }}} */
@@ -208,6 +253,17 @@ static zend_object_value zend_closure_new(zend_class_entry *class_type TSRMLS_DC
}
/* }}} */
+static zend_object_value zend_closure_clone(zval *zobject TSRMLS_DC) /* {{{ */
+{
+ zend_closure *closure = (zend_closure *)zend_object_store_get_object(zobject TSRMLS_CC);
+ zval result;
+
+ zend_create_closure(&result, &closure->func, closure->func.common.scope, closure->this_ptr);
+ return Z_OBJVAL(result);
+}
+/* }}} */
+
+
int zend_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC) /* {{{ */
{
zend_closure *closure;
@@ -219,10 +275,17 @@ int zend_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function
closure = (zend_closure *)zend_object_store_get_object(obj TSRMLS_CC);
*fptr_ptr = &closure->func;
- if (zobj_ptr) {
- *zobj_ptr = NULL;
+ if (closure->this_ptr) {
+ if (zobj_ptr) {
+ *zobj_ptr = closure->this_ptr;
+ }
+ *ce_ptr = Z_OBJCE_P(closure->this_ptr);
+ } else {
+ if (zobj_ptr) {
+ *zobj_ptr = NULL;
+ }
+ *ce_ptr = closure->func.common.scope;
}
- *ce_ptr = NULL;
return SUCCESS;
}
/* }}} */
@@ -248,6 +311,11 @@ static HashTable *zend_closure_get_debug_info(zval *object, int *is_temp TSRMLS_
zend_symtable_update(closure->debug_info, "static", sizeof("static"), (void *) &val, sizeof(zval *), NULL);
}
+ if (closure->this_ptr) {
+ Z_ADDREF_P(closure->this_ptr);
+ zend_symtable_update(closure->debug_info, "this", sizeof("this"), (void *) &closure->this_ptr, sizeof(zval *), NULL);
+ }
+
if (arg_info) {
zend_uint i, required = closure->func.common.required_num_args;
@@ -288,8 +356,19 @@ ZEND_METHOD(Closure, __construct)
}
/* }}} */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bindto, 0, 0, 0)
+ ZEND_ARG_INFO(0, newthis)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bind, 0, 0, 0)
+ ZEND_ARG_INFO(0, newthis)
+ ZEND_ARG_INFO(0, closure)
+ZEND_END_ARG_INFO()
+
static const zend_function_entry closure_functions[] = {
ZEND_ME(Closure, __construct, NULL, ZEND_ACC_PRIVATE)
+ ZEND_ME(Closure, bindTo, arginfo_closure_bindto, ZEND_ACC_PUBLIC)
+ ZEND_ME(Closure, bind, arginfo_closure_bind, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
{NULL, NULL, NULL}
};
@@ -313,7 +392,7 @@ void zend_register_closure_ce(TSRMLS_D) /* {{{ */
closure_handlers.has_property = zend_closure_has_property;
closure_handlers.unset_property = zend_closure_unset_property;
closure_handlers.compare_objects = zend_closure_compare_objects;
- closure_handlers.clone_obj = NULL;
+ closure_handlers.clone_obj = zend_closure_clone;
closure_handlers.get_debug_info = zend_closure_get_debug_info;
closure_handlers.get_closure = zend_closure_get_closure;
}
@@ -356,7 +435,7 @@ static int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_list args,
}
/* }}} */
-ZEND_API void zend_create_closure(zval *res, zend_function *func TSRMLS_DC) /* {{{ */
+ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zval *this_ptr TSRMLS_DC) /* {{{ */
{
zend_closure *closure;
@@ -375,9 +454,39 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func TSRMLS_DC) /* {
zend_hash_apply_with_arguments(static_variables TSRMLS_CC, (apply_func_args_t)zval_copy_static_var, 1, closure->func.op_array.static_variables);
}
(*closure->func.op_array.refcount)++;
+ } else {
+ /* verify that we aren't binding internal function to a wrong scope */
+ if(func->common.scope != NULL) {
+ if(scope && !instanceof_function(scope, func->common.scope TSRMLS_CC)) {
+ zend_error(E_WARNING, "Cannot bind function %s::%s to scope class %s", func->common.scope->name, func->common.function_name, scope->name);
+ scope = NULL;
+ }
+ if(scope && this_ptr && (func->common.fn_flags & ZEND_ACC_STATIC) == 0 &&
+ !instanceof_function(Z_OBJCE_P(this_ptr), closure->func.common.scope TSRMLS_CC)) {
+ zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s", func->common.scope->name, func->common.function_name, Z_OBJCE_P(this_ptr)->name);
+ scope = NULL;
+ this_ptr = NULL;
+ }
+ } else {
+ /* if it's a free function, we won't set scope & this since they're meaningless */
+ this_ptr = NULL;
+ scope = NULL;
+ }
}
- closure->func.common.scope = NULL;
+ closure->func.common.scope = scope;
+ if (scope) {
+ closure->func.common.fn_flags |= ZEND_ACC_PUBLIC;
+ if (this_ptr && (closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0) {
+ closure->this_ptr = this_ptr;
+ Z_ADDREF_P(this_ptr);
+ } else {
+ closure->func.common.fn_flags |= ZEND_ACC_STATIC;
+ closure->this_ptr = NULL;
+ }
+ } else {
+ closure->this_ptr = NULL;
+ }
}
/* }}} */
diff --git a/Zend/zend_closures.h b/Zend/zend_closures.h
index e2c12d8..24ecfae 100644
--- a/Zend/zend_closures.h
+++ b/Zend/zend_closures.h
@@ -30,9 +30,10 @@ void zend_register_closure_ce(TSRMLS_D);
extern ZEND_API zend_class_entry *zend_ce_closure;
-ZEND_API void zend_create_closure(zval *res, zend_function *op_array TSRMLS_DC);
+ZEND_API void zend_create_closure(zval *res, zend_function *op_array, zend_class_entry *scope, zval *this_ptr TSRMLS_DC);
ZEND_API zend_function *zend_get_closure_invoke_method(zval *obj TSRMLS_DC);
ZEND_API const zend_function *zend_get_closure_method_def(zval *obj TSRMLS_DC);
+ZEND_API zval* zend_get_closure_this_ptr(zval *obj TSRMLS_DC);
END_EXTERN_C()
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index ddae339..619415d 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -1403,7 +1403,7 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n
}
/* }}} */
-void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference TSRMLS_DC) /* {{{ */
+void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC) /* {{{ */
{
znode function_name;
zend_op_array *current_op_array = CG(active_op_array);
@@ -1423,6 +1423,9 @@ void zend_do_begin_lambda_function_declaration(znode *result, znode *function_to
zval_dtor(&current_op->op2.u.constant);
ZVAL_LONG(&current_op->op2.u.constant, zend_hash_func(Z_STRVAL(current_op->op1.u.constant), Z_STRLEN(current_op->op1.u.constant)));
current_op->result = *result;
+ if (is_static) {
+ CG(active_op_array)->fn_flags |= ZEND_ACC_STATIC;
+ }
CG(active_op_array)->fn_flags |= ZEND_ACC_CLOSURE;
}
/* }}} */
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index faff1ec..845fa66 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -434,7 +434,7 @@ void zend_do_end_function_call(znode *function_name, znode *result, const znode
void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC);
void zend_do_handle_exception(TSRMLS_D);
-void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference TSRMLS_DC);
+void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC);
void zend_do_fetch_lexical_variable(znode *varname, zend_bool is_ref TSRMLS_DC);
void zend_do_try(znode *try_token TSRMLS_DC);
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index 682b594..7738f84 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -647,8 +647,10 @@ expr_without_variable:
| T_ARRAY '(' array_pair_list ')' { $$ = $3; }
| '`' backticks_expr '`' { zend_do_shell_exec(&$$, &$2 TSRMLS_CC); }
| T_PRINT expr { zend_do_print(&$$, &$2 TSRMLS_CC); }
- | function is_reference '(' { zend_do_begin_lambda_function_declaration(&$$, &$1, $2.op_type TSRMLS_CC); }
+ | function is_reference '(' { zend_do_begin_lambda_function_declaration(&$$, &$1, $2.op_type, 0 TSRMLS_CC); }
parameter_list ')' lexical_vars '{' inner_statement_list '}' { zend_do_end_function_declaration(&$1 TSRMLS_CC); $$ = $4; }
+ | T_STATIC function is_reference '(' { zend_do_begin_lambda_function_declaration(&$$, &$2, $3.op_type, 1 TSRMLS_CC); }
+ parameter_list ')' lexical_vars '{' inner_statement_list '}' { zend_do_end_function_declaration(&$2 TSRMLS_CC); $$ = $5; }
;
function:
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index e953d40..fd04ff0 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -4414,7 +4414,7 @@ ZEND_VM_HANDLER(153, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, CONST)
zend_error_noreturn(E_ERROR, "Base lambda function for closure not found");
}
- zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_CC);
+ zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array, EG(scope), EG(This) TSRMLS_CC);
ZEND_VM_NEXT_OPCODE();
}
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 826ba57..a3c3f85 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -2947,7 +2947,7 @@ static int ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_HANDLER(
zend_error_noreturn(E_ERROR, "Base lambda function for closure not found");
}
- zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_CC);
+ zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array, EG(scope), EG(This) TSRMLS_CC);
ZEND_VM_NEXT_OPCODE();
}