summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrea Faulds <ajf@ajf.me>2014-07-30 03:21:44 +0100
committerAndrea Faulds <ajf@ajf.me>2014-07-30 03:21:44 +0100
commit85bf8b4ff19b9ab4f30be0265db9a18805b0ebc0 (patch)
tree08f6fbd6e4046a8e1b2c05ff9b886160d4cec38c
parentf65bdda469dfc30bd28652a6aa94d369eac1b6b2 (diff)
downloadphp-git-85bf8b4ff19b9ab4f30be0265db9a18805b0ebc0.tar.gz
Fixed unbound scoped closure edge cases and added tests for them
-rw-r--r--Zend/tests/closure_bind_unbound_scoped.phpt34
-rw-r--r--Zend/zend_closures.c6
-rw-r--r--Zend/zend_compile.h3
-rw-r--r--Zend/zend_vm_def.h2
-rw-r--r--Zend/zend_vm_execute.h2
5 files changed, 41 insertions, 6 deletions
diff --git a/Zend/tests/closure_bind_unbound_scoped.phpt b/Zend/tests/closure_bind_unbound_scoped.phpt
index a8347f8d90..2975d39ae6 100644
--- a/Zend/tests/closure_bind_unbound_scoped.phpt
+++ b/Zend/tests/closure_bind_unbound_scoped.phpt
@@ -2,6 +2,12 @@
Closure::bind and ::bindTo unbound_scoped parameter
--FILE--
<?php
+
+$foo = function () {};
+$foo = $foo->bindTo(NULL, "StdClass", true);
+// We need to check that you can call an unbound scoped closure without binding it (good idea or no)
+$foo();
+
class FooBar {
private $x = 3;
}
@@ -13,14 +19,34 @@ $foo = function () {
$foo = $foo->bindTo(NULL, "FooBar", true);
// As it is unbound, not static, this will work
-$foo->apply(new FooBar);
+$foo->call(new FooBar);
$foo = function () {
var_dump($this->x);
};
+// Make sure ::bind() works the same
$foo = Closure::bind($foo, NULL, "FooBar", true);
-$foo->apply(new FooBar);
+$foo->call(new FooBar);
+
+// Ensure that binding and having it unscoped will not prevent an E_NOTICE for non-static methods called statically
+class Qux {
+ function notStatic() {}
+}
+$x = new ReflectionMethod("Qux::notStatic");
+$x = $x->getClosure(new Qux);
+$x = $x->bindTo(NULL, "Qux", true);
+$x();
+
+// Ensure that binding and having it unscoped will not prevent fatal error for trying to call an internal class's method with $this as NULL
+$x = new ReflectionMethod("Closure::bindTo");
+$x = $x->getClosure(function () {});
+$x = $x->bindTo(NULL, "Closure", true);
+$x();
?>
---EXPECT--
+--EXPECTF--
+int(3)
int(3)
-int(3) \ No newline at end of file
+
+Strict Standards: Non-static method Qux::notStatic() should not be called statically in %s on line 35
+
+Fatal error: Non-static method Closure::bindTo() cannot be called statically in %s on line 41 \ No newline at end of file
diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c
index 72823a5a0b..a5735f4c79 100644
--- a/Zend/zend_closures.c
+++ b/Zend/zend_closures.c
@@ -563,6 +563,12 @@ ZEND_API void zend_create_closure_ex(zval *res, zend_function *func, zend_class_
If an unbound scoped closure is desired, the parameter must be set to 1*/
} else if (!unbound_scoped) {
closure->func.common.fn_flags |= ZEND_ACC_STATIC;
+ /* Unbound but scoped was explicitly specified and we wish to avoid E_ERROR when calling without object
+ We don't do this if it has implict allowed static (i.e. is a method, should E_STRICT)
+ Nor do we do this if it's an internal function (which would blow up if $this was NULL)
+ (In that case, we're actually creating a closure which can't be called without apply) */
+ } else if ((func->common.fn_flags & ZEND_ACC_ALLOW_STATIC) == 0 && func->type == ZEND_USER_FUNCTION) {
+ closure->func.common.fn_flags |= ZEND_ACC_ALLOW_STATIC_EXPLICIT;
}
}
}
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index f86a41ae2b..1b2bb7309e 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -183,6 +183,9 @@ typedef struct _zend_try_catch_element {
/* method flag (bc only), any method that has this flag can be used statically and non statically. */
#define ZEND_ACC_ALLOW_STATIC 0x10000
+/* method flag (for explicitly unbound scoped closures which shouldn't warn) */
+#define ZEND_ACC_ALLOW_STATIC_EXPLICIT 0x20000000
+
/* shadow of parent's private method/property */
#define ZEND_ACC_SHADOW 0x20000
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index d882086692..5b2af033f8 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -1929,7 +1929,7 @@ ZEND_VM_HELPER(zend_do_fcall_common_helper, ANY, ANY)
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- } else {
+ } else if (!(fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC_EXPLICIT)){
/* FIXME: output identifiers properly */
/* An internal function assumes $this is present and won't check that. So PHP would crash by allowing the call. */
zend_error_noreturn(E_ERROR, "Non-static method %s::%s() cannot be called statically", fbc->common.scope->name, fbc->common.function_name);
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 81aeb43295..27fa59737f 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -509,7 +509,7 @@ static int ZEND_FASTCALL zend_do_fcall_common_helper_SPEC(ZEND_OPCODE_HANDLER_AR
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- } else {
+ } else if (!(fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC_EXPLICIT)){
/* FIXME: output identifiers properly */
/* An internal function assumes $this is present and won't check that. So PHP would crash by allowing the call. */
zend_error_noreturn(E_ERROR, "Non-static method %s::%s() cannot be called statically", fbc->common.scope->name, fbc->common.function_name);