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