<?php
class a {}
-function foo() { print "ok\n"; }
+function foo() {}
foreach (["substr", "foo"] as $fn) {
$x = (new ReflectionFunction($fn))->getClosure();
?>
--EXPECTF--
-Warning: substr() expects at least 2 parameters, 0 given in %s on line %d
+Warning: Cannot bind function substr to an object in %s on line %d
-Warning: Cannot bind function substr to a class scope in %s on line %d
-ok
+Warning: Cannot bind function substr to an object or class in %s on line %d
-Warning: Cannot bind function foo to a class scope in %s on line %d
+Warning: Cannot bind function foo to an object in %s on line %d
+
+Warning: Cannot bind function foo to an object or class in %s on line %d
$d->bindTo($d);
echo "After binding, with same-class instance for the bound ones", "\n";
-$d = $staticUnscoped->bindTo(new A); /* $d(); */ echo "\n";
+$d = $staticUnscoped->bindTo(new A); $d(); echo "\n";
$d = $nonstaticUnscoped->bindTo(new A); $d(); echo " (should be scoped to dummy class)\n";
-$d = $staticScoped->bindTo(new A); /* $d(); */ echo "\n";
+$d = $staticScoped->bindTo(new A); $d(); echo "\n";
$d = $nonstaticScoped->bindTo(new A); $d(); echo "\n";
echo "After binding, with different instance for the bound ones", "\n";
echo "Done.\n";
-?>
--EXPECTF--
Before binding
scoped to A: bool(false)
After binding, with same-class instance for the bound ones
Warning: Cannot bind an instance to a static closure in %s on line %d
-
+scoped to A: bool(false)
+bound: no
scoped to A: bool(false)
bound: A (should be scoped to dummy class)
Warning: Cannot bind an instance to a static closure in %s on line %d
-
+scoped to A: bool(true)
+bound: no
scoped to A: bool(true)
bound: A
After binding, with different instance for the bound ones
$d = $staticScoped->bindTo(null, null); $d(); echo "\n";
echo "After binding, null scope, with instance", "\n";
-$d = $staticUnscoped->bindTo(new A, null); /* $d(); */ echo "\n";
-$d = $staticScoped->bindTo(new A, null); /* $d();n*/ echo "\n";
+$d = $staticUnscoped->bindTo(new A, null); $d(); echo "\n";
+$d = $staticScoped->bindTo(new A, null); $d(); echo "\n";
echo "After binding, with scope, no instance", "\n";
$d = $staticUnscoped->bindTo(null, 'A'); $d(); echo "\n";
$d = $staticScoped->bindTo(null, 'A'); $d(); echo "\n";
echo "After binding, with scope, with instance", "\n";
-$d = $staticUnscoped->bindTo(new A, 'A'); /* $d(); */ echo "\n";
-$d = $staticScoped->bindTo(new A, 'A'); /* $d(); */ echo "\n";
+$d = $staticUnscoped->bindTo(new A, 'A'); $d(); echo "\n";
+$d = $staticScoped->bindTo(new A, 'A'); $d(); echo "\n";
echo "Done.\n";
-?>
--EXPECTF--
Before binding
bool(false)
After binding, null scope, with instance
Warning: Cannot bind an instance to a static closure in %s on line %d
+bool(false)
+bool(false)
Warning: Cannot bind an instance to a static closure in %s on line %d
+bool(false)
+bool(false)
After binding, with scope, no instance
bool(true)
After binding, with scope, with instance
Warning: Cannot bind an instance to a static closure in %s on line %d
+bool(true)
+bool(false)
Warning: Cannot bind an instance to a static closure in %s on line %d
+bool(true)
+bool(false)
Done.
--TEST--
-Closure::call() or Closure::bind() to independent class
+Closure::call() or Closure::bind() to independent class must fail
--FILE--
<?php
--EXPECTF--
string(3) "baz"
string(3) "bar"
-string(3) "foo"
+
+Warning: Cannot bind function foo::initClass to object of class baz in %s on line %d
+
+Fatal error: Uncaught Error: Function name must be a string in %s:%d
+Stack trace:
+#0 %s(%d): callMethodOn('foo', 'initClass', Object(baz))
+#1 {main}
+ thrown in %s on line %d
--TEST--
-Test Closure binding to unknown scopes with Closure::call()
+Force failure with Closure binding to unknown scopes/$this with Closure::call()
--FILE--
<?php
-function foo() { echo get_class($this), "\n"; }
+function foo() { print "FAIL\n"; }
$x = (new ReflectionFunction("foo"))->getClosure();
$x->call(new stdClass);
$x = (new ReflectionMethod("d", "foo"))->getClosure(new d);
$x->call(new d)->current();
-// internal functions with unknown scope must fail
-$x = (new ReflectionMethod("Closure", "bindTo"))->getClosure(function() {});
-$x->call(new a);
-
?>
--EXPECTF--
-stdClass
-stdClass
+
+Warning: Cannot bind function foo to an object in %s on line %d
+
+Warning: Cannot bind function a::foo to object of class stdClass in %s on line %d
b
-stdClass
+
+Warning: Cannot bind function c::foo to object of class stdClass in %s on line %d
a
Success
-
-Warning: Cannot bind closure of internal method Closure::bindTo to unrelated object of class a in %s on line %d
--FILE--
<?php
-function foo() { echo get_class($this), "\n"; }
+function foo() { print "FAIL\n"; }
$x = (new ReflectionFunction("foo"))->getClosure();
-$x->bindTo(new stdClass)();
-$x->bindTo(new stdClass, "stdClass");
+$x->bindTo(new stdClass);
class a { function foo() { echo get_class($this), "\n"; } }
$x = (new ReflectionMethod("a", "foo"))->getClosure(new a);
-$x->bindTo(new stdClass)();
+$x->bindTo(new stdClass);
class c extends stdClass { function foo() { echo get_class($this), "\n"; } }
$x = (new ReflectionMethod("c", "foo"))->getClosure(new c);
-$x->bindTo(new stdClass)();
+$x->bindTo(new stdClass);
class b extends a {}
$x = (new ReflectionMethod("a", "foo"))->getClosure(new a);
$x = (new ReflectionMethod("a", "foo"))->getClosure(new a);
$x->bindTo(new a)();
-class z extends SplStack {}
-$x = (new ReflectionMethod("SplStack", "pop"))->getClosure(new SplStack);
-$z = new z; $z->push(20);
-var_dump($x->bindTo($z)());
-$x->bindTo($z, "z");
-
class d { function foo() { print "Success\n"; yield; } }
class e extends d {}
$x = (new ReflectionMethod("d", "foo"))->getClosure(new d);
?>
--EXPECTF--
-stdClass
-Warning: Cannot bind closure to scope of internal class stdClass in %s on line %d
-stdClass
-stdClass
+Warning: Cannot bind function foo to an object or class in %s on line %d
+
+Warning: Cannot bind function a::foo to object of class stdClass in %s on line %d
+
+Warning: Cannot bind function c::foo to object of class stdClass in %s on line %d
b
b
Warning: Cannot bind function a::foo to scope class c in %s on line %d
a
-int(20)
-
-Warning: Cannot bind function SplDoublyLinkedList::pop to scope class z in %s on line %d
Success
Success
Success
// Try on a StdClass
var_dump($bar->call($elePHPant));
+
$beta = function ($z) {
return $this->x * $z;
};
int(0)
int(0)
int(3)
-int(7)
+
+Warning: Cannot bind closure to object of internal class stdClass in %s line %d
+NULL
int(21)
int(3)
return;
}
+ if (closure->func.type != ZEND_USER_FUNCTION || (closure->func.common.fn_flags & ZEND_ACC_REAL_CLOSURE) == 0) {
+ /* verify that we aren't binding methods to a wrong object */
+ if (closure->func.common.scope == NULL) {
+ zend_error(E_WARNING, "Cannot bind function %s to an object", ZSTR_VAL(closure->func.common.function_name));
+ return;
+ } else if (!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;
+ }
+ }
+
newobj = Z_OBJ_P(newthis);
- if (closure->func.type == ZEND_INTERNAL_FUNCTION && closure->func.common.scope != NULL && !instanceof_function(newobj->ce, closure->func.common.scope)) {
+ 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 of internal method %s::%s to unrelated object of class %s", ZSTR_VAL(closure->func.common.scope->name), ZSTR_VAL(closure->func.common.function_name), ZSTR_VAL(newobj->ce->name));
+ zend_error(E_WARNING, "Cannot bind closure to object of internal class %s", ZSTR_VAL(newobj->ce->name));
return;
}
}
if (closure->func.type == ZEND_USER_FUNCTION && (closure->func.common.fn_flags & ZEND_ACC_REAL_CLOSURE)) {
- /* use scope of passed object; we must not change scope of functions and methods, only true Closures */
+ /* use scope of passed object */
fci_cache.function_handler->common.scope = Z_OBJCE_P(newthis);
/* Runtime cache relies on bound scope to be immutable, hence we need a separate rt cache in case scope changed */
if ((newthis != NULL) && (closure->func.common.fn_flags & ZEND_ACC_STATIC)) {
zend_error(E_WARNING, "Cannot bind an instance to a static closure");
- RETURN_NULL();
}
if (scope_arg != NULL) { /* scope argument was given */
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_NULL();
+ return;
}
} else { /* scope argument not given; do not change the scope by default */
ce = closure->func.common.scope;
{
zend_closure *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 (func->type != ZEND_USER_FUNCTION || (func->common.fn_flags & ZEND_ACC_REAL_CLOSURE) == 0) {
- /* verify that we aren't binding a internal function to a wrong scope */
+ /* verify that we aren't binding internal function to a wrong scope */
if (func->common.scope != NULL) {
- if (scope != func->common.scope) {
- if (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));
- } else {
- zend_error(E_WARNING, "Cannot unbind function %s::%s from its scope", ZSTR_VAL(func->common.scope->name), ZSTR_VAL(func->common.function_name));
- }
+ if (scope && 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));
ZVAL_NULL(res);
return;
}
- if (func->type == ZEND_INTERNAL_FUNCTION && this_ptr && (Z_TYPE_P(this_ptr) != IS_UNDEF) && !instanceof_function(Z_OBJCE_P(this_ptr), func->common.scope)) {
- /* rebinding to internal class is not allowed */
- zend_error(E_WARNING, "Cannot bind closure of internal method %s::%s to unrelated object of class %s", ZSTR_VAL(func->common.scope->name), ZSTR_VAL(func->common.function_name), ZSTR_VAL(Z_OBJCE_P(this_ptr)->name));
+ if (scope && this_ptr && (func->common.fn_flags & ZEND_ACC_STATIC) == 0 && !instanceof_function(Z_OBJCE_P(this_ptr), 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));
ZVAL_NULL(res);
return;
}
} else if (scope) {
- zend_error(E_WARNING, "Cannot bind function %s to a class scope", ZSTR_VAL(func->common.function_name));
+ zend_error(E_WARNING, "Cannot bind function %s to an object or class", ZSTR_VAL(func->common.function_name));
ZVAL_NULL(res);
return;
}
}
- 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;
- }
-
object_init_ex(res, zend_ce_closure);
closure = (zend_closure *) Z_OBJ_P(res);
if (EXPECTED((func->op_array.fn_flags & ZEND_ACC_GENERATOR) == 0)) {
zend_init_execute_data(call, &func->op_array, fci->retval);
zend_execute_ex(call);
- if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) {
- OBJ_RELEASE((zend_object*)func->op_array.prototype);
- }
} else {
zend_generator_create_zval(call, &func->op_array, fci->retval);
}
}
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
EG(current_execute_data) = EX(prev_execute_data);
+ if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) {
+ OBJ_RELEASE((zend_object*)EX(func)->op_array.prototype);
+ }
} else /* if (call_kind == ZEND_CALL_TOP_CODE) */ {
zend_array *symbol_table = EX(symbol_table);
}
OBJ_RELEASE(object);
}
- if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) && (fbc->common.fn_flags & ZEND_ACC_GENERATOR) == 0)) {
- OBJ_RELEASE((zend_object*)fbc->op_array.prototype);
- }
EG(scope) = EX(func)->op_array.scope;
ZEND_VM_C_LABEL(fcall_end):
}
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
EG(current_execute_data) = EX(prev_execute_data);
+ if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) {
+ OBJ_RELEASE((zend_object*)EX(func)->op_array.prototype);
+ }
} else /* if (call_kind == ZEND_CALL_TOP_CODE) */ {
zend_array *symbol_table = EX(symbol_table);
}
OBJ_RELEASE(object);
}
- if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) && (fbc->common.fn_flags & ZEND_ACC_GENERATOR) == 0)) {
- OBJ_RELEASE((zend_object*)fbc->op_array.prototype);
- }
EG(scope) = EX(func)->op_array.scope;
fcall_end: