Revert "Allow random $this on non-internal Closures again"

This reverts commit 35d0405c47.
This commit is contained in:
Dmitry Stogov 2015-10-06 23:48:10 +03:00
parent 3c0348056a
commit 524d00e005
11 changed files with 95 additions and 86 deletions

View File

@ -4,7 +4,7 @@ Bug #70630 (Closure::call/bind() crash with ReflectionFunction->getClosure())
<?php
class a {}
function foo() { print "ok\n"; }
function foo() {}
foreach (["substr", "foo"] as $fn) {
$x = (new ReflectionFunction($fn))->getClosure();
@ -15,9 +15,10 @@ foreach (["substr", "foo"] as $fn) {
?>
--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

View File

@ -53,9 +53,9 @@ $d = $nonstaticScoped->bindTo(null); $d(); echo "\n";
$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";
@ -64,7 +64,6 @@ $d = $nonstaticScoped->bindTo(new B); $d(); echo "\n";
echo "Done.\n";
?>
--EXPECTF--
Before binding
scoped to A: bool(false)
@ -87,12 +86,14 @@ bound: no
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

View File

@ -26,20 +26,19 @@ $d = $staticUnscoped->bindTo(null, null); $d(); echo "\n";
$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)
@ -58,9 +57,13 @@ 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)
@ -72,8 +75,12 @@ bool(false)
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.

View File

@ -1,5 +1,5 @@
--TEST--
Closure::call() or Closure::bind() to independent class
Closure::call() or Closure::bind() to independent class must fail
--FILE--
<?php
@ -53,4 +53,11 @@ var_dump($baz->getVar());
--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

View File

@ -1,9 +1,9 @@
--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);
@ -26,17 +26,14 @@ class d { function foo() { print "Success\n"; yield; } }
$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

View File

@ -3,18 +3,17 @@ Force failure with Closure binding to unknown scopes/$this with Closure::bind()
--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);
@ -25,12 +24,6 @@ $x->bindTo(new b, "c");
$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);
@ -41,19 +34,17 @@ $x->bindTo(new e, "e");
?>
--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

View File

@ -36,6 +36,7 @@ $elePHPant->x = 7;
// Try on a StdClass
var_dump($bar->call($elePHPant));
$beta = function ($z) {
return $this->x * $z;
};
@ -59,6 +60,8 @@ $foo->call(new FooBar);
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)

View File

@ -94,11 +94,22 @@ ZEND_METHOD(Closure, call)
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;
}
@ -127,7 +138,7 @@ ZEND_METHOD(Closure, call)
}
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 */
@ -166,7 +177,6 @@ ZEND_METHOD(Closure, bind)
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 */
@ -188,7 +198,7 @@ ZEND_METHOD(Closure, bind)
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;
@ -520,37 +530,32 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
{
zend_closure *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 */
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));
}
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));
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));
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;
}
if (func->type != ZEND_USER_FUNCTION || (func->common.fn_flags & ZEND_ACC_REAL_CLOSURE) == 0) {
/* verify that we aren't binding internal function to a wrong scope */
if (func->common.scope != NULL) {
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 (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 an object or class", ZSTR_VAL(func->common.function_name));
ZVAL_NULL(res);
return;
}
}
object_init_ex(res, zend_ce_closure);
closure = (zend_closure *) Z_OBJ_P(res);

View File

@ -851,9 +851,6 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
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);
}

View File

@ -2418,6 +2418,9 @@ ZEND_VM_HELPER(zend_leave_helper, ANY, ANY)
}
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);
@ -3858,9 +3861,6 @@ ZEND_VM_C_LABEL(fcall_end_change_scope):
}
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):

View File

@ -534,6 +534,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper_SPEC(ZEND_OPCODE_
}
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);
@ -916,9 +919,6 @@ fcall_end_change_scope:
}
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: