summaryrefslogtreecommitdiff
path: root/Zend/zend_closures.c
diff options
context:
space:
mode:
Diffstat (limited to 'Zend/zend_closures.c')
-rw-r--r--Zend/zend_closures.c237
1 files changed, 140 insertions, 97 deletions
diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c
index 7d2025c56f..810ac6a66c 100644
--- a/Zend/zend_closures.c
+++ b/Zend/zend_closures.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) 1998-2015 Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright (c) 1998-2016 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 |
@@ -32,13 +32,17 @@
#define ZEND_CLOSURE_PRINT_NAME "Closure object"
#define ZEND_CLOSURE_PROPERTY_ERROR() \
- zend_throw_error(zend_ce_error, "Closure object cannot have properties")
+ zend_throw_error(NULL, "Closure object cannot have properties")
+
+/* reuse bit to mark "fake" closures (it wasn't used for functions before) */
+#define ZEND_ACC_FAKE_CLOSURE ZEND_ACC_INTERFACE
typedef struct _zend_closure {
zend_object std;
zend_function func;
zval this_ptr;
zend_class_entry *called_scope;
+ void (*orig_internal_handler)(INTERNAL_FUNCTION_PARAMETERS);
} zend_closure;
/* non-static since it needs to be referenced */
@@ -48,17 +52,11 @@ static zend_object_handlers closure_handlers;
ZEND_METHOD(Closure, __invoke) /* {{{ */
{
zend_function *func = EX(func);
- zval *arguments;
+ zval *arguments = ZEND_CALL_ARG(execute_data, 1);
- arguments = emalloc(sizeof(zval) * ZEND_NUM_ARGS());
- if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), arguments) == FAILURE) {
- efree(arguments);
- zend_throw_error(zend_ce_error, "Cannot get arguments for calling closure");
- RETVAL_FALSE;
- } else if (call_user_function_ex(CG(function_table), NULL, getThis(), return_value, ZEND_NUM_ARGS(), arguments, 1, NULL) == FAILURE) {
+ if (call_user_function_ex(CG(function_table), NULL, getThis(), return_value, ZEND_NUM_ARGS(), arguments, 1, NULL) == FAILURE) {
RETVAL_FALSE;
}
- efree(arguments);
/* destruct the function also, then - we have allocated it in get_method */
zend_string_release(func->internal_function.function_name);
@@ -69,6 +67,48 @@ ZEND_METHOD(Closure, __invoke) /* {{{ */
}
/* }}} */
+static zend_bool zend_valid_closure_binding(
+ zend_closure *closure, zval *newthis, zend_class_entry *scope) /* {{{ */
+{
+ zend_function *func = &closure->func;
+ zend_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");
+ return 0;
+ }
+
+ if (is_fake_closure && func->common.scope &&
+ !instanceof_function(Z_OBJCE_P(newthis), func->common.scope)) {
+ /* Binding incompatible $this to an internal method is not supported. */
+ zend_error(E_WARNING, "Cannot bind method %s::%s() to object of class %s",
+ ZSTR_VAL(func->common.scope->name),
+ ZSTR_VAL(func->common.function_name),
+ ZSTR_VAL(Z_OBJCE_P(newthis)->name));
+ return 0;
+ }
+ } else if (!(func->common.fn_flags & ZEND_ACC_STATIC) && func->common.scope
+ && func->type == ZEND_INTERNAL_FUNCTION) {
+ zend_error(E_WARNING, "Cannot unbind $this of internal method");
+ return 0;
+ }
+
+ if (scope && scope != func->common.scope && scope->type == ZEND_INTERNAL_CLASS) {
+ /* rebinding to internal class is not allowed */
+ zend_error(E_WARNING, "Cannot bind closure to scope of internal class %s",
+ ZSTR_VAL(scope->name));
+ return 0;
+ }
+
+ if (is_fake_closure && scope != func->common.scope) {
+ zend_error(E_WARNING, "Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure()");
+ return 0;
+ }
+
+ return 1;
+}
+/* }}} */
+
/* {{{ proto mixed Closure::call(object to [, mixed parameter] [, mixed ...] )
Call closure, binding to a given object with its class as the scope */
ZEND_METHOD(Closure, call)
@@ -87,27 +127,11 @@ ZEND_METHOD(Closure, call)
}
zclosure = getThis();
- closure = (zend_closure *)Z_OBJ_P(zclosure);
-
- if (closure->func.common.fn_flags & ZEND_ACC_STATIC) {
- zend_error(E_WARNING, "Cannot bind an instance to a static closure");
- return;
- }
-
- if (closure->func.type == ZEND_INTERNAL_FUNCTION) {
- /* verify that we aren't binding internal function to a wrong object */
- if ((closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0 &&
- !instanceof_function(Z_OBJCE_P(newthis), closure->func.common.scope)) {
- zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s", ZSTR_VAL(closure->func.common.scope->name), ZSTR_VAL(closure->func.common.function_name), ZSTR_VAL(Z_OBJCE_P(newthis)->name));
- return;
- }
- }
+ closure = (zend_closure *) Z_OBJ_P(zclosure);
newobj = Z_OBJ_P(newthis);
- if (newobj->ce != closure->func.common.scope && newobj->ce->type == ZEND_INTERNAL_CLASS) {
- /* rebinding to internal class is not allowed */
- zend_error(E_WARNING, "Cannot bind closure to object of internal class %s", ZSTR_VAL(newobj->ce->name));
+ if (!zend_valid_closure_binding(closure, newthis, Z_OBJCE_P(newthis))) {
return;
}
@@ -121,23 +145,34 @@ ZEND_METHOD(Closure, call)
fci.param_count = my_param_count;
fci.object = fci_cache.object = newobj;
fci_cache.initialized = 1;
+ fci_cache.called_scope = Z_OBJCE_P(newthis);
- my_function = *fci_cache.function_handler;
- /* use scope of passed object */
- my_function.common.scope = Z_OBJCE_P(newthis);
- fci_cache.function_handler = &my_function;
-
- /* Runtime cache relies on bound scope to be immutable, hence we need a separate rt cache in case scope changed */
- if (ZEND_USER_CODE(my_function.type) && closure->func.common.scope != Z_OBJCE_P(newthis)) {
- my_function.op_array.run_time_cache = emalloc(my_function.op_array.cache_size);
- memset(my_function.op_array.run_time_cache, 0, my_function.op_array.cache_size);
+ if (fci_cache.function_handler->common.fn_flags & ZEND_ACC_GENERATOR) {
+ zval new_closure;
+ zend_create_closure(&new_closure, fci_cache.function_handler, Z_OBJCE_P(newthis), closure->called_scope, newthis);
+ closure = (zend_closure *) Z_OBJ(new_closure);
+ fci_cache.function_handler = &closure->func;
+ } else {
+ memcpy(&my_function, fci_cache.function_handler, fci_cache.function_handler->type == ZEND_USER_FUNCTION ? sizeof(zend_op_array) : sizeof(zend_internal_function));
+ /* use scope of passed object */
+ my_function.common.scope = Z_OBJCE_P(newthis);
+ fci_cache.function_handler = &my_function;
+
+ /* Runtime cache relies on bound scope to be immutable, hence we need a separate rt cache in case scope changed */
+ if (ZEND_USER_CODE(my_function.type) && closure->func.common.scope != Z_OBJCE_P(newthis)) {
+ my_function.op_array.run_time_cache = emalloc(my_function.op_array.cache_size);
+ memset(my_function.op_array.run_time_cache, 0, my_function.op_array.cache_size);
+ }
}
if (zend_call_function(&fci, &fci_cache) == SUCCESS && Z_TYPE(closure_result) != IS_UNDEF) {
ZVAL_COPY_VALUE(return_value, &closure_result);
}
- if (ZEND_USER_CODE(my_function.type) && closure->func.common.scope != Z_OBJCE_P(newthis)) {
+ if (fci_cache.function_handler->common.fn_flags & ZEND_ACC_GENERATOR) {
+ /* copied upon generator creation */
+ --GC_REFCOUNT(&closure->std);
+ } else if (ZEND_USER_CODE(my_function.type) && closure->func.common.scope != Z_OBJCE_P(newthis)) {
efree(my_function.op_array.run_time_cache);
}
}
@@ -152,15 +187,11 @@ ZEND_METHOD(Closure, bind)
zend_class_entry *ce, *called_scope;
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Oo!|z", &zclosure, zend_ce_closure, &newthis, &scope_arg) == FAILURE) {
- RETURN_NULL();
+ return;
}
closure = (zend_closure *)Z_OBJ_P(zclosure);
- if ((newthis != NULL) && (closure->func.common.fn_flags & ZEND_ACC_STATIC)) {
- zend_error(E_WARNING, "Cannot bind an instance to a static closure");
- }
-
if (scope_arg != NULL) { /* scope argument was given */
if (Z_TYPE_P(scope_arg) == IS_OBJECT) {
ce = Z_OBJCE_P(scope_arg);
@@ -177,15 +208,14 @@ ZEND_METHOD(Closure, bind)
}
zend_string_release(class_name);
}
- if(ce && ce != closure->func.common.scope && ce->type == ZEND_INTERNAL_CLASS) {
- /* rebinding to internal class is not allowed */
- zend_error(E_WARNING, "Cannot bind closure to scope of internal class %s", ZSTR_VAL(ce->name));
- return;
- }
} else { /* scope argument not given; do not change the scope by default */
ce = closure->func.common.scope;
}
+ if (!zend_valid_closure_binding(closure, newthis, ce)) {
+ return;
+ }
+
if (newthis) {
called_scope = Z_OBJCE_P(newthis);
} else {
@@ -205,9 +235,9 @@ ZEND_METHOD(Closure, bind)
}
/* }}} */
-static zend_function *zend_closure_get_constructor(zend_object *object) /* {{{ */
+static ZEND_COLD zend_function *zend_closure_get_constructor(zend_object *object) /* {{{ */
{
- zend_throw_error(zend_ce_error, "Instantiation of 'Closure' is not allowed");
+ zend_throw_error(NULL, "Instantiation of 'Closure' is not allowed");
return NULL;
}
/* }}} */
@@ -241,7 +271,7 @@ ZEND_API zend_function *zend_get_closure_invoke_method(zend_object *object) /* {
invoke->internal_function.handler = ZEND_MN(Closure___invoke);
invoke->internal_function.module = 0;
invoke->internal_function.scope = zend_ce_closure;
- invoke->internal_function.function_name = zend_string_init(ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME)-1, 0);
+ invoke->internal_function.function_name = CG(known_strings)[ZEND_STR_MAGIC_INVOKE];
return invoke;
}
/* }}} */
@@ -262,14 +292,10 @@ ZEND_API zval* zend_get_closure_this_ptr(zval *obj) /* {{{ */
static zend_function *zend_closure_get_method(zend_object **object, zend_string *method, const zval *key) /* {{{ */
{
- zend_string *lc_name;
-
- lc_name = zend_string_tolower(method);
- if (zend_string_equals_literal(method, ZEND_INVOKE_FUNC_NAME)) {
- zend_string_release(lc_name);
+ if (zend_string_equals_literal_ci(method, ZEND_INVOKE_FUNC_NAME)) {
return zend_get_closure_invoke_method(*object);
}
- zend_string_release(lc_name);
+
return std_object_handlers.get_method(object, method, key);
}
/* }}} */
@@ -356,23 +382,16 @@ static zend_object *zend_closure_clone(zval *zobject) /* {{{ */
int zend_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zend_object **obj_ptr) /* {{{ */
{
- zend_closure *closure;
-
- if (Z_TYPE_P(obj) != IS_OBJECT) {
- return FAILURE;
- }
-
- closure = (zend_closure *)Z_OBJ_P(obj);
+ zend_closure *closure = (zend_closure *)Z_OBJ_P(obj);
*fptr_ptr = &closure->func;
*ce_ptr = closure->called_scope;
- if (obj_ptr) {
- if (Z_TYPE(closure->this_ptr) != IS_UNDEF) {
- *obj_ptr = Z_OBJ(closure->this_ptr);
- } else {
- *obj_ptr = NULL;
- }
+ if (Z_TYPE(closure->this_ptr) != IS_UNDEF) {
+ *obj_ptr = Z_OBJ(closure->this_ptr);
+ } else {
+ *obj_ptr = NULL;
}
+
return SUCCESS;
}
/* }}} */
@@ -392,12 +411,12 @@ static HashTable *zend_closure_get_debug_info(zval *object, int *is_temp) /* {{{
if (closure->func.type == ZEND_USER_FUNCTION && closure->func.op_array.static_variables) {
HashTable *static_variables = closure->func.op_array.static_variables;
ZVAL_ARR(&val, zend_array_dup(static_variables));
- zend_hash_str_update(debug_info, "static", sizeof("static")-1, &val);
+ zend_hash_update(debug_info, CG(known_strings)[ZEND_STR_STATIC], &val);
}
if (Z_TYPE(closure->this_ptr) != IS_UNDEF) {
Z_ADDREF(closure->this_ptr);
- zend_hash_str_update(debug_info, "this", sizeof("this")-1, &closure->this_ptr);
+ zend_hash_update(debug_info, CG(known_strings)[ZEND_STR_THIS], &closure->this_ptr);
}
if (arg_info &&
@@ -448,9 +467,9 @@ static HashTable *zend_closure_get_gc(zval *obj, zval **table, int *n) /* {{{ */
/* {{{ proto Closure::__construct()
Private constructor preventing instantiation */
-ZEND_METHOD(Closure, __construct)
+ZEND_COLD ZEND_METHOD(Closure, __construct)
{
- zend_throw_error(zend_ce_error, "Instantiation of 'Closure' is not allowed");
+ zend_throw_error(NULL, "Instantiation of 'Closure' is not allowed");
}
/* }}} */
@@ -491,7 +510,6 @@ void zend_register_closure_ce(void) /* {{{ */
memcpy(&closure_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
closure_handlers.free_obj = zend_closure_free_storage;
- closure_handlers.clone_obj = NULL;
closure_handlers.get_constructor = zend_closure_get_constructor;
closure_handlers.get_method = zend_closure_get_method;
closure_handlers.write_property = zend_closure_write_property;
@@ -507,6 +525,15 @@ void zend_register_closure_ce(void) /* {{{ */
}
/* }}} */
+static void zend_closure_internal_handler(INTERNAL_FUNCTION_PARAMETERS) /* {{{ */
+{
+ zend_closure *closure = (zend_closure*)EX(func)->common.prototype;
+ closure->orig_internal_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+ OBJ_RELEASE((zend_object*)closure);
+ EX(func) = NULL;
+}
+/* }}} */
+
ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */
{
zend_closure *closure;
@@ -515,23 +542,19 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
closure = (zend_closure *)Z_OBJ_P(res);
- memcpy(&closure->func, func, func->type == ZEND_USER_FUNCTION ? sizeof(zend_op_array) : sizeof(zend_internal_function));
- closure->func.common.prototype = (zend_function*)closure;
- closure->func.common.fn_flags |= ZEND_ACC_CLOSURE;
-
if ((scope == NULL) && this_ptr && (Z_TYPE_P(this_ptr) != IS_UNDEF)) {
/* use dummy scope if we're binding an object without specifying a scope */
/* maybe it would be better to create one for this purpose */
scope = zend_ce_closure;
}
- if (closure->func.type == ZEND_USER_FUNCTION) {
+ if (func->type == ZEND_USER_FUNCTION) {
+ memcpy(&closure->func, func, sizeof(zend_op_array));
+ closure->func.common.prototype = (zend_function*)closure;
+ closure->func.common.fn_flags |= ZEND_ACC_CLOSURE;
if (closure->func.op_array.static_variables) {
- HashTable *static_variables = closure->func.op_array.static_variables;
-
- ALLOC_HASHTABLE(closure->func.op_array.static_variables);
- zend_hash_init(closure->func.op_array.static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0);
- zend_hash_apply_with_arguments(static_variables, zval_copy_static_var, 1, closure->func.op_array.static_variables);
+ closure->func.op_array.static_variables =
+ zend_array_dup(closure->func.op_array.static_variables);
}
if (UNEXPECTED(!closure->func.op_array.run_time_cache)) {
closure->func.op_array.run_time_cache = func->op_array.run_time_cache = zend_arena_alloc(&CG(arena), func->op_array.cache_size);
@@ -541,19 +564,20 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
(*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)) {
- zend_error(E_WARNING, "Cannot bind function %s::%s to scope class %s", ZSTR_VAL(func->common.scope->name), ZSTR_VAL(func->common.function_name), ZSTR_VAL(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)) {
- zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s", ZSTR_VAL(func->common.scope->name), ZSTR_VAL(func->common.function_name), ZSTR_VAL(Z_OBJCE_P(this_ptr)->name));
- scope = NULL;
- this_ptr = NULL;
- }
+ memcpy(&closure->func, func, sizeof(zend_internal_function));
+ closure->func.common.prototype = (zend_function*)closure;
+ closure->func.common.fn_flags |= ZEND_ACC_CLOSURE;
+ /* wrap internal function handler to avoid memory leak */
+ if (UNEXPECTED(closure->func.internal_function.handler == zend_closure_internal_handler)) {
+ /* avoid infinity recursion, by taking handler from nested closure */
+ zend_closure *nested = (zend_closure*)((char*)func - XtOffsetOf(zend_closure, func));
+ ZEND_ASSERT(nested->std.ce == zend_ce_closure);
+ closure->orig_internal_handler = nested->orig_internal_handler;
} else {
+ closure->orig_internal_handler = closure->func.internal_function.handler;
+ }
+ closure->func.internal_function.handler = zend_closure_internal_handler;
+ if (!func->common.scope) {
/* if it's a free function, we won't set scope & this since they're meaningless */
this_ptr = NULL;
scope = NULL;
@@ -574,6 +598,25 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
}
/* }}} */
+ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */
+{
+ zend_closure *closure;
+
+ zend_create_closure(res, func, scope, called_scope, this_ptr);
+
+ closure = (zend_closure *)Z_OBJ_P(res);
+ closure->func.common.fn_flags |= ZEND_ACC_FAKE_CLOSURE;
+}
+/* }}} */
+
+void zend_closure_bind_var(zval *closure_zv, zend_string *var_name, zval *var) /* {{{ */
+{
+ zend_closure *closure = (zend_closure *) Z_OBJ_P(closure_zv);
+ HashTable *static_variables = closure->func.op_array.static_variables;
+ zend_hash_update(static_variables, var_name, var);
+}
+/* }}} */
+
/*
* Local variables:
* tab-width: 4