Skip to content

Commit 6347f0b

Browse files
committed
Implement ReflectionReference
RFC: https://wiki.php.net/rfc/reference_reflection
1 parent 34122ed commit 6347f0b

6 files changed

Lines changed: 242 additions & 2 deletions

File tree

UPGRADING

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ PHP 7.4 UPGRADE NOTES
202202
7. New Classes and Interfaces
203203
========================================
204204

205+
- Reflection:
206+
. A new ReflectionReference class has been added, which allows detecting
207+
references and comparing them for identity. For more details see the RFC:
208+
https://wiki.php.net/rfc/reference_reflection
209+
205210
========================================
206211
8. Removed Extensions and SAPIs
207212
========================================

ext/reflection/php_reflection.c

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include "php_ini.h"
2929
#include "php_reflection.h"
3030
#include "ext/standard/info.h"
31+
#include "ext/standard/sha1.h"
32+
#include "ext/standard/php_random.h"
3133

3234
#include "zend.h"
3335
#include "zend_API.h"
@@ -42,6 +44,16 @@
4244
#include "zend_builtin_functions.h"
4345
#include "zend_smart_str.h"
4446

47+
/* Key used to avoid leaking addresses in ReflectionProperty::getId() */
48+
#define REFLECTION_KEY_LEN 16
49+
ZEND_BEGIN_MODULE_GLOBALS(reflection)
50+
zend_bool key_initialized;
51+
unsigned char key[REFLECTION_KEY_LEN];
52+
ZEND_END_MODULE_GLOBALS(reflection)
53+
ZEND_DECLARE_MODULE_GLOBALS(reflection)
54+
55+
#define REFLECTION_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(reflection, v)
56+
4557
#define reflection_update_property(object, name, value) do { \
4658
zval member; \
4759
ZVAL_STR(&member, name); \
@@ -73,6 +85,7 @@ PHPAPI zend_class_entry *reflection_property_ptr;
7385
PHPAPI zend_class_entry *reflection_class_constant_ptr;
7486
PHPAPI zend_class_entry *reflection_extension_ptr;
7587
PHPAPI zend_class_entry *reflection_zend_extension_ptr;
88+
PHPAPI zend_class_entry *reflection_reference_ptr;
7689

7790
/* Exception throwing macro */
7891
#define _DO_THROW(msg) \
@@ -6154,6 +6167,89 @@ ZEND_METHOD(reflection_zend_extension, getCopyright)
61546167
}
61556168
/* }}} */
61566169

6170+
/* {{{ proto public ReflectionReference::__construct()
6171+
* Dummy constructor -- always throws ReflectionExceptions. */
6172+
ZEND_METHOD(reflection_reference, __construct)
6173+
{
6174+
_DO_THROW(
6175+
"Cannot directly instantiate ReflectionReference. "
6176+
"Use ReflectionReference::fromArrayElement() instead"
6177+
);
6178+
}
6179+
/* }}} */
6180+
6181+
/* {{{ proto public ReflectionReference|null ReflectionReference::fromArrayElement(array array, mixed key)
6182+
* Create ReflectionReference for array item. Returns null if not a reference. */
6183+
ZEND_METHOD(reflection_reference, fromArrayElement)
6184+
{
6185+
HashTable *ht;
6186+
zval *key, *item;
6187+
reflection_object *intern;
6188+
6189+
if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "hz", &ht, &key) == FAILURE) {
6190+
return;
6191+
}
6192+
6193+
if (Z_TYPE_P(key) == IS_LONG) {
6194+
item = zend_hash_index_find(ht, Z_LVAL_P(key));
6195+
} else if (Z_TYPE_P(key) == IS_STRING) {
6196+
item = zend_symtable_find(ht, Z_STR_P(key));
6197+
} else {
6198+
zend_type_error("Key must be array or string");
6199+
return;
6200+
}
6201+
6202+
if (!item) {
6203+
_DO_THROW("Array key not found");
6204+
}
6205+
6206+
if (Z_TYPE_P(item) != IS_REFERENCE) {
6207+
RETURN_NULL();
6208+
}
6209+
6210+
object_init_ex(return_value, reflection_reference_ptr);
6211+
intern = Z_REFLECTION_P(return_value);
6212+
ZVAL_COPY(&intern->obj, item);
6213+
intern->ref_type = REF_TYPE_OTHER;
6214+
}
6215+
/* }}} */
6216+
6217+
/* {{{ proto public int|string ReflectionReference::getId()
6218+
* Returns a unique identifier for the reference.
6219+
* The format of the return value is unspecified and may change. */
6220+
ZEND_METHOD(reflection_reference, getId)
6221+
{
6222+
reflection_object *intern;
6223+
unsigned char digest[20];
6224+
PHP_SHA1_CTX context;
6225+
6226+
if (zend_parse_parameters_none() == FAILURE) {
6227+
return;
6228+
}
6229+
6230+
intern = Z_REFLECTION_P(getThis());
6231+
if (Z_TYPE(intern->obj) != IS_REFERENCE) {
6232+
_DO_THROW("Corrupted ReflectionReference object");
6233+
}
6234+
6235+
if (!REFLECTION_G(key_initialized)) {
6236+
if (php_random_bytes_throw(&REFLECTION_G(key_initialized), 16) == FAILURE) {
6237+
return;
6238+
}
6239+
6240+
REFLECTION_G(key_initialized) = 1;
6241+
}
6242+
6243+
/* SHA1(ref || key) to avoid directly exposing memory addresses. */
6244+
PHP_SHA1Init(&context);
6245+
PHP_SHA1Update(&context, (unsigned char *) &Z_REF(intern->obj), sizeof(zend_reference *));
6246+
PHP_SHA1Update(&context, REFLECTION_G(key), REFLECTION_KEY_LEN);
6247+
PHP_SHA1Final(digest, &context);
6248+
6249+
RETURN_STRINGL((char *) digest, sizeof(digest));
6250+
}
6251+
/* }}} */
6252+
61576253
/* {{{ method tables */
61586254
static const zend_function_entry reflection_exception_functions[] = {
61596255
PHP_FE_END
@@ -6637,6 +6733,21 @@ static const zend_function_entry reflection_zend_extension_functions[] = {
66376733
ZEND_ME(reflection_zend_extension, getCopyright, arginfo_reflection__void, 0)
66386734
PHP_FE_END
66396735
};
6736+
6737+
ZEND_BEGIN_ARG_INFO_EX(arginfo_reflection_reference_fromArrayElement, 0, 0, 2)
6738+
ZEND_ARG_INFO(0, array)
6739+
ZEND_ARG_INFO(0, key)
6740+
ZEND_END_ARG_INFO()
6741+
6742+
static const zend_function_entry reflection_reference_functions[] = {
6743+
ZEND_ME(reflection_reference, fromArrayElement, arginfo_reflection_reference_fromArrayElement, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
6744+
ZEND_ME(reflection_reference, getId, arginfo_reflection__void, ZEND_ACC_PUBLIC)
6745+
6746+
/* Always throwing dummy methods */
6747+
ZEND_ME(reflection, __clone, arginfo_reflection__void, ZEND_ACC_PRIVATE)
6748+
ZEND_ME(reflection_reference, __construct, arginfo_reflection__void, ZEND_ACC_PRIVATE)
6749+
PHP_FE_END
6750+
};
66406751
/* }}} */
66416752

66426753
static const zend_function_entry reflection_ext_functions[] = { /* {{{ */
@@ -6777,6 +6888,13 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
67776888
zend_class_implements(reflection_zend_extension_ptr, 1, reflector_ptr);
67786889
zend_declare_property_string(reflection_zend_extension_ptr, "name", sizeof("name")-1, "", ZEND_ACC_PUBLIC);
67796890

6891+
INIT_CLASS_ENTRY(_reflection_entry, "ReflectionReference", reflection_reference_functions);
6892+
reflection_init_class_handlers(&_reflection_entry);
6893+
_reflection_entry.ce_flags |= ZEND_ACC_FINAL;
6894+
reflection_reference_ptr = zend_register_internal_class(&_reflection_entry);
6895+
6896+
REFLECTION_G(key_initialized) = 0;
6897+
67806898
return SUCCESS;
67816899
} /* }}} */
67826900

@@ -6797,5 +6915,9 @@ zend_module_entry reflection_module_entry = { /* {{{ */
67976915
NULL,
67986916
PHP_MINFO(reflection),
67996917
PHP_REFLECTION_VERSION,
6800-
STANDARD_MODULE_PROPERTIES
6918+
ZEND_MODULE_GLOBALS(reflection),
6919+
NULL,
6920+
NULL,
6921+
NULL,
6922+
STANDARD_MODULE_PROPERTIES_EX
68016923
}; /* }}} */

ext/reflection/php_reflection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ extern PHPAPI zend_class_entry *reflection_method_ptr;
4343
extern PHPAPI zend_class_entry *reflection_property_ptr;
4444
extern PHPAPI zend_class_entry *reflection_extension_ptr;
4545
extern PHPAPI zend_class_entry *reflection_zend_extension_ptr;
46+
extern PHPAPI zend_class_entry *reflection_reference_ptr;
4647

4748
PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object);
4849

ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ var_dump($ext->getClasses());
99
?>
1010
==DONE==
1111
--EXPECT--
12-
array(16) {
12+
array(17) {
1313
["ReflectionException"]=>
1414
object(ReflectionClass)#2 (1) {
1515
["name"]=>
@@ -90,5 +90,10 @@ array(16) {
9090
["name"]=>
9191
string(23) "ReflectionZendExtension"
9292
}
93+
["ReflectionReference"]=>
94+
object(ReflectionClass)#18 (1) {
95+
["name"]=>
96+
string(19) "ReflectionReference"
97+
}
9398
}
9499
==DONE==
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
--TEST--
2+
Basic ReflectionReference functionality
3+
--FILE--
4+
<?php
5+
6+
$ary = [0, 1, 2];
7+
$ref1 =& $ary[1];
8+
unset($ref1);
9+
$ref2 =& $ary[2];
10+
11+
echo "fromArrayElement():\n";
12+
$r0 = ReflectionReference::fromArrayElement($ary, 0);
13+
var_dump($r0 === null);
14+
$r1 = ReflectionReference::fromArrayElement($ary, 1);
15+
var_dump($r1 instanceof ReflectionReference);
16+
$r2 = ReflectionReference::fromArrayElement($ary, 2);
17+
var_dump($r2 instanceof ReflectionReference);
18+
19+
echo "getId() #1:\n";
20+
var_dump($r1->getId() === $r1->getId());
21+
var_dump($r2->getId() === $r2->getId());
22+
var_dump($r1->getId() !== $r2->getId());
23+
24+
echo "getId() #2:\n";
25+
$ary2 = [&$ary[1], &$ref2];
26+
$r1_2 = ReflectionReference::fromArrayElement($ary2, 0);
27+
$r2_2 = ReflectionReference::fromArrayElement($ary2, 1);
28+
var_dump($r1->getId() === $r1_2->getId());
29+
var_dump($r2->getId() === $r2_2->getId());
30+
31+
echo "getId() #3:\n";
32+
$r1_id = $r1->getId();
33+
$r2_id = $r2->getId();
34+
unset($r0, $r1, $r2, $r1_2, $r2_2);
35+
$r1 = ReflectionReference::fromArrayElement($ary, 1);
36+
$r2 = ReflectionReference::fromArrayElement($ary, 2);
37+
var_dump($r1_id === $r1->getId());
38+
var_dump($r2_id === $r2->getId());
39+
40+
?>
41+
--EXPECT--
42+
fromArrayElement():
43+
bool(true)
44+
bool(true)
45+
bool(true)
46+
getId() #1:
47+
bool(true)
48+
bool(true)
49+
bool(true)
50+
getId() #2:
51+
bool(true)
52+
bool(true)
53+
getId() #3:
54+
bool(true)
55+
bool(true)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
Various error conditions for ReflectionReference
3+
--FILE--
4+
<?php
5+
6+
try {
7+
new ReflectionReference();
8+
} catch (Error $e) {
9+
echo $e->getMessage(), "\n";
10+
}
11+
12+
try {
13+
ReflectionReference::fromArrayElement(new stdClass, "test");
14+
} catch (TypeError $e) {
15+
echo $e->getMessage(), "\n";
16+
}
17+
18+
try {
19+
ReflectionReference::fromArrayElement([], 1.5);
20+
} catch (TypeError $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
24+
try {
25+
$ary = [0, 1, 2];
26+
ReflectionReference::fromArrayElement($ary, 3);
27+
} catch (ReflectionException $e) {
28+
echo $e->getMessage(), "\n";
29+
}
30+
31+
try {
32+
$ary = [&$ary];
33+
$ref = ReflectionReference::fromArrayElement($ary, 0);
34+
var_dump(serialize($ref));
35+
} catch (Exception $e) {
36+
echo $e->getMessage(), "\n";
37+
}
38+
39+
var_dump(unserialize('O:19:"ReflectionReference":0:{}'));
40+
41+
?>
42+
--EXPECTF--
43+
Call to private ReflectionReference::__construct() from invalid context
44+
ReflectionReference::fromArrayElement() expects parameter 1 to be array, object given
45+
Key must be array or string
46+
Array key not found
47+
Serialization of 'ReflectionReference' is not allowed
48+
49+
Warning: Erroneous data format for unserializing 'ReflectionReference' in %s on line %d
50+
51+
Notice: unserialize(): Error at offset 30 of 31 bytes in %s on line %d
52+
bool(false)

0 commit comments

Comments
 (0)