Skip to content

Commit b227a72

Browse files
committed
ReflectionFunctionAbstract::getClosureUsedVariables
Make a distinction at compile time between bind types for static variables getStaticVariables remains unchanged Fixes #80071
1 parent 77ce253 commit b227a72

10 files changed

+147
-15
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ PHP NEWS
112112
. Convert resource<pspell> to object \PSpell\Dictionary. (Sara)
113113
. Convert resource<pspell config> to object \PSpell\Config. (Sara)
114114

115+
- Reflection:
116+
. Implement ReflectionFunctionAbstract::getClosureUsedVariables (krakjoe)
117+
115118
- Sodium:
116119
. Added the XChaCha20 stream cipher functions. (P.I.E. Security Team)
117120
. Added the Ristretto255 functions, which are available in libsodium 1.0.18.

Zend/Optimizer/dce.c

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,23 @@ static inline bool may_have_side_effects(
234234
}
235235
return 0;
236236
case ZEND_BIND_STATIC:
237-
if (op_array->static_variables
238-
&& (opline->extended_value & ZEND_BIND_REF) != 0) {
239-
zval *value =
240-
(zval*)((char*)op_array->static_variables->arData +
241-
(opline->extended_value & ~ZEND_BIND_REF));
242-
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
243-
/* AST may contain undefined constants */
237+
if (op_array->static_variables) {
238+
/* Implicit and Explicit bind static is effectively prologue of closure so
239+
report it has side effects like RECV, RECV_INIT; This allows us to
240+
reflect on the closure and discover used variable at runtime */
241+
if ((opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) {
244242
return 1;
245243
}
244+
245+
if ((opline->extended_value & ZEND_BIND_REF) != 0) {
246+
zval *value =
247+
(zval*)((char*)op_array->static_variables->arData +
248+
(opline->extended_value & ~ZEND_BIND_REF));
249+
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
250+
/* AST may contain undefined constants */
251+
return 1;
252+
}
253+
}
246254
}
247255
return 0;
248256
case ZEND_CHECK_VAR:

Zend/zend_compile.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6875,6 +6875,7 @@ static void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
68756875
uint32_t i;
68766876

68776877
for (i = 0; i < list->children; ++i) {
6878+
uint32_t mode = ZEND_BIND_EXPLICIT;
68786879
zend_ast *var_ast = list->child[i];
68796880
zend_string *var_name = zend_ast_get_str(var_ast);
68806881
zval zv;
@@ -6892,7 +6893,11 @@ static void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
68926893

68936894
CG(zend_lineno) = zend_ast_get_lineno(var_ast);
68946895

6895-
zend_compile_static_var_common(var_name, &zv, var_ast->attr ? ZEND_BIND_REF : 0);
6896+
if (var_ast->attr) {
6897+
mode |= ZEND_BIND_REF;
6898+
}
6899+
6900+
zend_compile_static_var_common(var_name, &zv, mode);
68966901
}
68976902
}
68986903
/* }}} */

Zend/zend_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,7 @@ static zend_always_inline bool zend_check_arg_send_type(const zend_function *zf,
10571057
#define ZEND_BIND_VAL 0
10581058
#define ZEND_BIND_REF 1
10591059
#define ZEND_BIND_IMPLICIT 2
1060+
#define ZEND_BIND_EXPLICIT 4
10601061

10611062
#define ZEND_RETURNS_FUNCTION (1<<0)
10621063
#define ZEND_RETURNS_VALUE (1<<1)

Zend/zend_vm_def.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8698,7 +8698,7 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF)
86988698
}
86998699
ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
87008700

8701-
value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT)));
8701+
value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT)));
87028702

87038703
if (opline->extended_value & ZEND_BIND_REF) {
87048704
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {

Zend/zend_vm_execute.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47434,7 +47434,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_UNUSED_HAN
4743447434
}
4743547435
ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
4743647436

47437-
value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT)));
47437+
value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT)));
4743847438

4743947439
if (opline->extended_value & ZEND_BIND_REF) {
4744047440
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {

ext/reflection/php_reflection.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,55 @@ ZEND_METHOD(ReflectionFunctionAbstract, getClosureScopeClass)
16641664
}
16651665
/* }}} */
16661666

1667+
/* {{{ Returns an associative array containing the closures lexical scope variables */
1668+
ZEND_METHOD(ReflectionFunctionAbstract, getClosureUsedVariables)
1669+
{
1670+
reflection_object *intern;
1671+
const zend_function *closure_func;
1672+
1673+
if (zend_parse_parameters_none() == FAILURE) {
1674+
RETURN_THROWS();
1675+
}
1676+
GET_REFLECTION_OBJECT();
1677+
1678+
array_init(return_value);
1679+
if (!Z_ISUNDEF(intern->obj)) {
1680+
closure_func = zend_get_closure_method_def(Z_OBJ(intern->obj));
1681+
if (closure_func == NULL ||
1682+
closure_func->type != ZEND_USER_FUNCTION ||
1683+
closure_func->op_array.static_variables == NULL) {
1684+
return;
1685+
}
1686+
1687+
const zend_op_array *ops = &closure_func->op_array;
1688+
1689+
HashTable *static_variables = ZEND_MAP_PTR_GET(ops->static_variables_ptr);
1690+
1691+
if (!static_variables) {
1692+
return;
1693+
}
1694+
1695+
zend_op *opline = ops->opcodes + ops->num_args;
1696+
1697+
for (; opline->opcode == ZEND_BIND_STATIC; opline++) {
1698+
if (!(opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) {
1699+
continue;
1700+
}
1701+
1702+
Bucket *bucket = (Bucket*)
1703+
(((char*)static_variables->arData) +
1704+
(opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT)));
1705+
1706+
if (Z_ISUNDEF(bucket->val)) {
1707+
continue;
1708+
}
1709+
1710+
zend_hash_add_new(Z_ARRVAL_P(return_value), bucket->key, &bucket->val);
1711+
Z_TRY_ADDREF(bucket->val);
1712+
}
1713+
}
1714+
} /* }}} */
1715+
16671716
/* {{{ Returns a dynamically created closure for the function */
16681717
ZEND_METHOD(ReflectionFunction, getClosure)
16691718
{

ext/reflection/php_reflection.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public function getClosureThis() {}
5050
/** @return ReflectionClass|null */
5151
public function getClosureScopeClass() {}
5252

53+
public function getClosureUsedVariables() : array {}
54+
5355
/** @return string|false */
5456
public function getDocComment() {}
5557

ext/reflection/php_reflection_arginfo.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 0b2b52d4f891a594ccfcbcc0edeec97a9e0f80e6 */
2+
* Stub hash: 0e99b3dabead95a356ea4ffb16d99a3e939e9869 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1)
55
ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
@@ -27,6 +27,9 @@ ZEND_END_ARG_INFO()
2727

2828
#define arginfo_class_ReflectionFunctionAbstract_getClosureScopeClass arginfo_class_ReflectionFunctionAbstract_inNamespace
2929

30+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables, 0, 0, IS_ARRAY, 0)
31+
ZEND_END_ARG_INFO()
32+
3033
#define arginfo_class_ReflectionFunctionAbstract_getDocComment arginfo_class_ReflectionFunctionAbstract_inNamespace
3134

3235
#define arginfo_class_ReflectionFunctionAbstract_getEndLine arginfo_class_ReflectionFunctionAbstract_inNamespace
@@ -424,8 +427,7 @@ ZEND_END_ARG_INFO()
424427

425428
#define arginfo_class_ReflectionNamedType_isBuiltin arginfo_class_ReflectionFunctionAbstract_inNamespace
426429

427-
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionUnionType_getTypes, 0, 0, IS_ARRAY, 0)
428-
ZEND_END_ARG_INFO()
430+
#define arginfo_class_ReflectionUnionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables
429431

430432
#define arginfo_class_ReflectionExtension___clone arginfo_class_ReflectionFunctionAbstract___clone
431433

@@ -489,7 +491,7 @@ ZEND_END_ARG_INFO()
489491

490492
#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
491493

492-
#define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionUnionType_getTypes
494+
#define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables
493495

494496
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_newInstance, 0, 0, IS_OBJECT, 0)
495497
ZEND_END_ARG_INFO()
@@ -508,7 +510,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnum_getCase, 0,
508510
ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
509511
ZEND_END_ARG_INFO()
510512

511-
#define arginfo_class_ReflectionEnum_getCases arginfo_class_ReflectionUnionType_getTypes
513+
#define arginfo_class_ReflectionEnum_getCases arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables
512514

513515
#define arginfo_class_ReflectionEnum_isBacked arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
514516

@@ -557,6 +559,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, isGenerator);
557559
ZEND_METHOD(ReflectionFunctionAbstract, isVariadic);
558560
ZEND_METHOD(ReflectionFunctionAbstract, getClosureThis);
559561
ZEND_METHOD(ReflectionFunctionAbstract, getClosureScopeClass);
562+
ZEND_METHOD(ReflectionFunctionAbstract, getClosureUsedVariables);
560563
ZEND_METHOD(ReflectionFunctionAbstract, getDocComment);
561564
ZEND_METHOD(ReflectionFunctionAbstract, getEndLine);
562565
ZEND_METHOD(ReflectionFunctionAbstract, getExtension);
@@ -795,6 +798,7 @@ static const zend_function_entry class_ReflectionFunctionAbstract_methods[] = {
795798
ZEND_ME(ReflectionFunctionAbstract, isVariadic, arginfo_class_ReflectionFunctionAbstract_isVariadic, ZEND_ACC_PUBLIC)
796799
ZEND_ME(ReflectionFunctionAbstract, getClosureThis, arginfo_class_ReflectionFunctionAbstract_getClosureThis, ZEND_ACC_PUBLIC)
797800
ZEND_ME(ReflectionFunctionAbstract, getClosureScopeClass, arginfo_class_ReflectionFunctionAbstract_getClosureScopeClass, ZEND_ACC_PUBLIC)
801+
ZEND_ME(ReflectionFunctionAbstract, getClosureUsedVariables, arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables, ZEND_ACC_PUBLIC)
798802
ZEND_ME(ReflectionFunctionAbstract, getDocComment, arginfo_class_ReflectionFunctionAbstract_getDocComment, ZEND_ACC_PUBLIC)
799803
ZEND_ME(ReflectionFunctionAbstract, getEndLine, arginfo_class_ReflectionFunctionAbstract_getEndLine, ZEND_ACC_PUBLIC)
800804
ZEND_ME(ReflectionFunctionAbstract, getExtension, arginfo_class_ReflectionFunctionAbstract_getExtension, ZEND_ACC_PUBLIC)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
ReflectionFunctionAbstract::getClosureUsedVariables
3+
--FILE--
4+
<?php
5+
$reflector = new ReflectionFunction("strlen");
6+
7+
var_dump($reflector->getClosureUsedVariables());
8+
9+
$function = function() {
10+
static $one = 1;
11+
};
12+
13+
$reflector = new ReflectionFunction($function);
14+
15+
var_dump($reflector->getClosureUsedVariables());
16+
17+
$one = 1;
18+
$two = 2;
19+
20+
$function = function() use ($one, $two) {
21+
static $three = 3;
22+
};
23+
24+
$reflector = new ReflectionFunction($function);
25+
26+
var_dump($reflector->getClosureUsedVariables());
27+
28+
$function = fn() => $three = [$one];
29+
30+
$reflector = new ReflectionFunction($function);
31+
32+
var_dump($reflector->getClosureUsedVariables());
33+
34+
$function = function() use (&$one) {
35+
static $three = 3;
36+
};
37+
38+
$reflector = new ReflectionFunction($function);
39+
40+
var_dump($reflector->getClosureUsedVariables());
41+
?>
42+
--EXPECT--
43+
array(0) {
44+
}
45+
array(0) {
46+
}
47+
array(2) {
48+
["one"]=>
49+
int(1)
50+
["two"]=>
51+
int(2)
52+
}
53+
array(1) {
54+
["one"]=>
55+
int(1)
56+
}
57+
array(1) {
58+
["one"]=>
59+
&int(1)
60+
}

0 commit comments

Comments
 (0)