From 20233469737ba007a1bdd38b7acd5512a2e7d534 Mon Sep 17 00:00:00 2001
From: Nikita Popov <nikita.ppv@gmail.com>
Date: Mon, 15 Jan 2018 12:13:55 +0100
Subject: [PATCH] Fixed bug #75079

---
 NEWS                       |  2 ++
 Zend/tests/bug75079.phpt   | 39 ++++++++++++++++++++++++++++++++++++++
 Zend/tests/bug75079_2.phpt | 36 +++++++++++++++++++++++++++++++++++
 Zend/zend_closures.c       | 32 ++++++++++++++++++-------------
 4 files changed, 96 insertions(+), 13 deletions(-)
 create mode 100644 Zend/tests/bug75079.phpt
 create mode 100644 Zend/tests/bug75079_2.phpt

diff --git a/NEWS b/NEWS
index 2caa0d8f47..83bbd1162d 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,8 @@ PHP                                                                        NEWS
   . Fixed bug #75799 (arg of get_defined_functions is optional). (carusogabriel)
   . Fixed bug #75396 (Exit inside generator finally results in fatal error).
     (Nikita)
+  . Fixed bug #75079 (self keyword leads to incorrectly generated TypeError when
+    in closure in trait). (Nikita)
 
 - IMAP:
   . Fixed bug #75774 (imap_append HeapCorruction). (Anatol)
diff --git a/Zend/tests/bug75079.phpt b/Zend/tests/bug75079.phpt
new file mode 100644
index 0000000000..9bf9c1ddb8
--- /dev/null
+++ b/Zend/tests/bug75079.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Bug #75079: self keyword leads to incorrectly generated TypeError when in closure in trait
+--FILE--
+<?php
+
+trait Foo
+{
+    public function selfDo(self ...$Selfs)
+    {
+        array_map(
+            function (self $Self) : self
+            {
+                return $Self;
+            },
+            $Selfs
+        );
+    }
+}
+
+class Bar
+{
+    use Foo;
+}
+
+class Baz
+{
+    use Foo;
+}
+
+$Bar = new Bar;
+$Baz = new Baz;
+
+$Bar->selfDo($Bar, $Bar);
+$Baz->selfDo($Baz, $Baz);
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
diff --git a/Zend/tests/bug75079_2.phpt b/Zend/tests/bug75079_2.phpt
new file mode 100644
index 0000000000..6e0b1cd27f
--- /dev/null
+++ b/Zend/tests/bug75079_2.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Bug #75079 variation without traits
+--FILE--
+<?php
+
+class Foo
+{
+    private static $bar = 123;
+    
+    static function test(){
+        return function(){
+            return function(){
+                return Foo::$bar;
+            };
+        };
+    }
+}
+
+
+$f = Foo::test();
+
+var_dump($f()());
+
+class A{}
+$a = new A; 
+var_dump($f->bindTo($a, A::CLASS)()());
+
+?>
+--EXPECTF--
+int(123)
+
+Fatal error: Uncaught Error: Cannot access private property Foo::$bar in %s:%d
+Stack trace:
+#0 %s(%d): A->{closure}()
+#1 {main}
+  thrown in %s on line %d
diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c
index 799531b6bf..01aae545d4 100644
--- a/Zend/zend_closures.c
+++ b/Zend/zend_closures.c
@@ -186,7 +186,7 @@ ZEND_METHOD(Closure, call)
 ZEND_METHOD(Closure, bind)
 {
 	zval *newthis, *zclosure, *scope_arg = NULL;
-	zend_closure *closure, *new_closure;
+	zend_closure *closure;
 	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) {
@@ -226,15 +226,6 @@ ZEND_METHOD(Closure, bind)
 	}
 
 	zend_create_closure(return_value, &closure->func, ce, called_scope, newthis);
-	new_closure = (zend_closure *) Z_OBJ_P(return_value);
-
-	/* Runtime cache relies on bound scope to be immutable, hence we need a separate rt cache in case scope changed */
-	if (ZEND_USER_CODE(closure->func.type) && (closure->func.common.scope != new_closure->func.common.scope || (closure->func.op_array.fn_flags & ZEND_ACC_NO_RT_ARENA))) {
-		new_closure->func.op_array.run_time_cache = emalloc(new_closure->func.op_array.cache_size);
-		memset(new_closure->func.op_array.run_time_cache, 0, new_closure->func.op_array.cache_size);
-
-		new_closure->func.op_array.fn_flags |= ZEND_ACC_NO_RT_ARENA;
-	}
 }
 /* }}} */
 
@@ -669,9 +660,24 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
 			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);
-			memset(func->op_array.run_time_cache, 0, func->op_array.cache_size);
+
+		/* Runtime cache is scope-dependent, so we cannot reuse it if the scope changed */
+		if (!closure->func.op_array.run_time_cache
+			|| func->common.scope != scope
+			|| (func->common.fn_flags & ZEND_ACC_NO_RT_ARENA)
+		) {
+			if (!func->op_array.run_time_cache && (func->common.fn_flags & ZEND_ACC_CLOSURE)) {
+				/* If a real closure is used for the first time, we create a shared runtime cache
+				 * and remember which scope it is for. */
+				func->common.scope = scope;
+				func->op_array.run_time_cache = zend_arena_alloc(&CG(arena), func->op_array.cache_size);
+				closure->func.op_array.run_time_cache = func->op_array.run_time_cache;
+			} else {
+				/* Otherwise, we use a non-shared runtime cache */
+				closure->func.op_array.run_time_cache = emalloc(func->op_array.cache_size);
+				closure->func.op_array.fn_flags |= ZEND_ACC_NO_RT_ARENA;
+			}
+			memset(closure->func.op_array.run_time_cache, 0, func->op_array.cache_size);
 		}
 		if (closure->func.op_array.refcount) {
 			(*closure->func.op_array.refcount)++;
-- 
2.40.0