From b227a722859e83fdba230f746477f6322ae33609 Mon Sep 17 00:00:00 2001 From: Joe Watkins Date: Fri, 21 May 2021 09:28:08 +0200 Subject: [PATCH] ReflectionFunctionAbstract::getClosureUsedVariables Make a distinction at compile time between bind types for static variables getStaticVariables remains unchanged Fixes #80071 --- NEWS | 3 + Zend/Optimizer/dce.c | 22 ++++--- Zend/zend_compile.c | 7 ++- Zend/zend_compile.h | 1 + Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 2 +- ext/reflection/php_reflection.c | 49 +++++++++++++++ ext/reflection/php_reflection.stub.php | 2 + ext/reflection/php_reflection_arginfo.h | 14 +++-- ...ctionFunction_getClosureUsedVariables.phpt | 60 +++++++++++++++++++ 10 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 ext/reflection/tests/ReflectionFunction_getClosureUsedVariables.phpt diff --git a/NEWS b/NEWS index c8acb4be5b..9eb3a6b6c0 100644 --- a/NEWS +++ b/NEWS @@ -112,6 +112,9 @@ PHP NEWS . Convert resource to object \PSpell\Dictionary. (Sara) . Convert resource to object \PSpell\Config. (Sara) +- Reflection: + . Implement ReflectionFunctionAbstract::getClosureUsedVariables (krakjoe) + - Sodium: . Added the XChaCha20 stream cipher functions. (P.I.E. Security Team) . Added the Ristretto255 functions, which are available in libsodium 1.0.18. diff --git a/Zend/Optimizer/dce.c b/Zend/Optimizer/dce.c index d61d367020..4fa69dc7be 100644 --- a/Zend/Optimizer/dce.c +++ b/Zend/Optimizer/dce.c @@ -234,15 +234,23 @@ static inline bool may_have_side_effects( } return 0; case ZEND_BIND_STATIC: - if (op_array->static_variables - && (opline->extended_value & ZEND_BIND_REF) != 0) { - zval *value = - (zval*)((char*)op_array->static_variables->arData + - (opline->extended_value & ~ZEND_BIND_REF)); - if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - /* AST may contain undefined constants */ + if (op_array->static_variables) { + /* Implicit and Explicit bind static is effectively prologue of closure so + report it has side effects like RECV, RECV_INIT; This allows us to + reflect on the closure and discover used variable at runtime */ + if ((opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) { return 1; } + + if ((opline->extended_value & ZEND_BIND_REF) != 0) { + zval *value = + (zval*)((char*)op_array->static_variables->arData + + (opline->extended_value & ~ZEND_BIND_REF)); + if (Z_TYPE_P(value) == IS_CONSTANT_AST) { + /* AST may contain undefined constants */ + return 1; + } + } } return 0; case ZEND_CHECK_VAR: diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 07e8679885..096083435e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6875,6 +6875,7 @@ static void zend_compile_closure_uses(zend_ast *ast) /* {{{ */ uint32_t i; for (i = 0; i < list->children; ++i) { + uint32_t mode = ZEND_BIND_EXPLICIT; zend_ast *var_ast = list->child[i]; zend_string *var_name = zend_ast_get_str(var_ast); zval zv; @@ -6892,7 +6893,11 @@ static void zend_compile_closure_uses(zend_ast *ast) /* {{{ */ CG(zend_lineno) = zend_ast_get_lineno(var_ast); - zend_compile_static_var_common(var_name, &zv, var_ast->attr ? ZEND_BIND_REF : 0); + if (var_ast->attr) { + mode |= ZEND_BIND_REF; + } + + zend_compile_static_var_common(var_name, &zv, mode); } } /* }}} */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 5c60a22086..83a988a1b7 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1057,6 +1057,7 @@ static zend_always_inline bool zend_check_arg_send_type(const zend_function *zf, #define ZEND_BIND_VAL 0 #define ZEND_BIND_REF 1 #define ZEND_BIND_IMPLICIT 2 +#define ZEND_BIND_EXPLICIT 4 #define ZEND_RETURNS_FUNCTION (1<<0) #define ZEND_RETURNS_VALUE (1<<1) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 51130b6b5a..4c71250531 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8698,7 +8698,7 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF) } ZEND_ASSERT(GC_REFCOUNT(ht) == 1); - value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT))); + value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); if (opline->extended_value & ZEND_BIND_REF) { if (Z_TYPE_P(value) == IS_CONSTANT_AST) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index b61509c36a..8c9ddf076a 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -47434,7 +47434,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_UNUSED_HAN } ZEND_ASSERT(GC_REFCOUNT(ht) == 1); - value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT))); + value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); if (opline->extended_value & ZEND_BIND_REF) { if (Z_TYPE_P(value) == IS_CONSTANT_AST) { diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 020c56d6cf..379fde06b6 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1664,6 +1664,55 @@ ZEND_METHOD(ReflectionFunctionAbstract, getClosureScopeClass) } /* }}} */ +/* {{{ Returns an associative array containing the closures lexical scope variables */ +ZEND_METHOD(ReflectionFunctionAbstract, getClosureUsedVariables) +{ + reflection_object *intern; + const zend_function *closure_func; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT(); + + array_init(return_value); + if (!Z_ISUNDEF(intern->obj)) { + closure_func = zend_get_closure_method_def(Z_OBJ(intern->obj)); + if (closure_func == NULL || + closure_func->type != ZEND_USER_FUNCTION || + closure_func->op_array.static_variables == NULL) { + return; + } + + const zend_op_array *ops = &closure_func->op_array; + + HashTable *static_variables = ZEND_MAP_PTR_GET(ops->static_variables_ptr); + + if (!static_variables) { + return; + } + + zend_op *opline = ops->opcodes + ops->num_args; + + for (; opline->opcode == ZEND_BIND_STATIC; opline++) { + if (!(opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) { + continue; + } + + Bucket *bucket = (Bucket*) + (((char*)static_variables->arData) + + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); + + if (Z_ISUNDEF(bucket->val)) { + continue; + } + + zend_hash_add_new(Z_ARRVAL_P(return_value), bucket->key, &bucket->val); + Z_TRY_ADDREF(bucket->val); + } + } +} /* }}} */ + /* {{{ Returns a dynamically created closure for the function */ ZEND_METHOD(ReflectionFunction, getClosure) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 7bd22b6d83..5a8d618b83 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -50,6 +50,8 @@ abstract class ReflectionFunctionAbstract implements Reflector /** @return ReflectionClass|null */ public function getClosureScopeClass() {} + public function getClosureUsedVariables() : array {} + /** @return string|false */ public function getDocComment() {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index fa48db957e..7f01f4eb19 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 0b2b52d4f891a594ccfcbcc0edeec97a9e0f80e6 */ + * Stub hash: 0e99b3dabead95a356ea4ffb16d99a3e939e9869 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -27,6 +27,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionFunctionAbstract_getClosureScopeClass arginfo_class_ReflectionFunctionAbstract_inNamespace +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionFunctionAbstract_getDocComment arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionFunctionAbstract_getEndLine arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -424,8 +427,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionNamedType_isBuiltin arginfo_class_ReflectionFunctionAbstract_inNamespace -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionUnionType_getTypes, 0, 0, IS_ARRAY, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionUnionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables #define arginfo_class_ReflectionExtension___clone arginfo_class_ReflectionFunctionAbstract___clone @@ -489,7 +491,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType -#define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionUnionType_getTypes +#define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_newInstance, 0, 0, IS_OBJECT, 0) ZEND_END_ARG_INFO() @@ -508,7 +510,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnum_getCase, 0, ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionEnum_getCases arginfo_class_ReflectionUnionType_getTypes +#define arginfo_class_ReflectionEnum_getCases arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables #define arginfo_class_ReflectionEnum_isBacked arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType @@ -557,6 +559,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, isGenerator); ZEND_METHOD(ReflectionFunctionAbstract, isVariadic); ZEND_METHOD(ReflectionFunctionAbstract, getClosureThis); ZEND_METHOD(ReflectionFunctionAbstract, getClosureScopeClass); +ZEND_METHOD(ReflectionFunctionAbstract, getClosureUsedVariables); ZEND_METHOD(ReflectionFunctionAbstract, getDocComment); ZEND_METHOD(ReflectionFunctionAbstract, getEndLine); ZEND_METHOD(ReflectionFunctionAbstract, getExtension); @@ -795,6 +798,7 @@ static const zend_function_entry class_ReflectionFunctionAbstract_methods[] = { ZEND_ME(ReflectionFunctionAbstract, isVariadic, arginfo_class_ReflectionFunctionAbstract_isVariadic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getClosureThis, arginfo_class_ReflectionFunctionAbstract_getClosureThis, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getClosureScopeClass, arginfo_class_ReflectionFunctionAbstract_getClosureScopeClass, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionFunctionAbstract, getClosureUsedVariables, arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getDocComment, arginfo_class_ReflectionFunctionAbstract_getDocComment, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getEndLine, arginfo_class_ReflectionFunctionAbstract_getEndLine, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getExtension, arginfo_class_ReflectionFunctionAbstract_getExtension, ZEND_ACC_PUBLIC) diff --git a/ext/reflection/tests/ReflectionFunction_getClosureUsedVariables.phpt b/ext/reflection/tests/ReflectionFunction_getClosureUsedVariables.phpt new file mode 100644 index 0000000000..2ce560f6f4 --- /dev/null +++ b/ext/reflection/tests/ReflectionFunction_getClosureUsedVariables.phpt @@ -0,0 +1,60 @@ +--TEST-- +ReflectionFunctionAbstract::getClosureUsedVariables +--FILE-- +getClosureUsedVariables()); + +$function = function() { + static $one = 1; +}; + +$reflector = new ReflectionFunction($function); + +var_dump($reflector->getClosureUsedVariables()); + +$one = 1; +$two = 2; + +$function = function() use ($one, $two) { + static $three = 3; +}; + +$reflector = new ReflectionFunction($function); + +var_dump($reflector->getClosureUsedVariables()); + +$function = fn() => $three = [$one]; + +$reflector = new ReflectionFunction($function); + +var_dump($reflector->getClosureUsedVariables()); + +$function = function() use (&$one) { + static $three = 3; +}; + +$reflector = new ReflectionFunction($function); + +var_dump($reflector->getClosureUsedVariables()); +?> +--EXPECT-- +array(0) { +} +array(0) { +} +array(2) { + ["one"]=> + int(1) + ["two"]=> + int(2) +} +array(1) { + ["one"]=> + int(1) +} +array(1) { + ["one"]=> + &int(1) +}