summaryrefslogtreecommitdiff
path: root/ext/reflection
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2018-09-24 20:57:39 +0200
committerNikita Popov <nikita.ppv@gmail.com>2019-02-13 12:21:18 +0100
commit6347f0b937cc3fb3edb38003a41f3f9e8608831d (patch)
tree6896dcf4beff65309a56dd09d0c6a71c32d938ee /ext/reflection
parent34122ede2116777b02473a7d4430a15edcc58063 (diff)
downloadphp-git-6347f0b937cc3fb3edb38003a41f3f9e8608831d.tar.gz
Implement ReflectionReference
RFC: https://wiki.php.net/rfc/reference_reflection
Diffstat (limited to 'ext/reflection')
-rw-r--r--ext/reflection/php_reflection.c124
-rw-r--r--ext/reflection/php_reflection.h1
-rw-r--r--ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt7
-rw-r--r--ext/reflection/tests/ReflectionReference.phpt55
-rw-r--r--ext/reflection/tests/ReflectionReference_errors.phpt52
5 files changed, 237 insertions, 2 deletions
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index b09953b897..1e11b8735a 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -28,6 +28,8 @@
#include "php_ini.h"
#include "php_reflection.h"
#include "ext/standard/info.h"
+#include "ext/standard/sha1.h"
+#include "ext/standard/php_random.h"
#include "zend.h"
#include "zend_API.h"
@@ -42,6 +44,16 @@
#include "zend_builtin_functions.h"
#include "zend_smart_str.h"
+/* Key used to avoid leaking addresses in ReflectionProperty::getId() */
+#define REFLECTION_KEY_LEN 16
+ZEND_BEGIN_MODULE_GLOBALS(reflection)
+ zend_bool key_initialized;
+ unsigned char key[REFLECTION_KEY_LEN];
+ZEND_END_MODULE_GLOBALS(reflection)
+ZEND_DECLARE_MODULE_GLOBALS(reflection)
+
+#define REFLECTION_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(reflection, v)
+
#define reflection_update_property(object, name, value) do { \
zval member; \
ZVAL_STR(&member, name); \
@@ -73,6 +85,7 @@ PHPAPI zend_class_entry *reflection_property_ptr;
PHPAPI zend_class_entry *reflection_class_constant_ptr;
PHPAPI zend_class_entry *reflection_extension_ptr;
PHPAPI zend_class_entry *reflection_zend_extension_ptr;
+PHPAPI zend_class_entry *reflection_reference_ptr;
/* Exception throwing macro */
#define _DO_THROW(msg) \
@@ -6154,6 +6167,89 @@ ZEND_METHOD(reflection_zend_extension, getCopyright)
}
/* }}} */
+/* {{{ proto public ReflectionReference::__construct()
+ * Dummy constructor -- always throws ReflectionExceptions. */
+ZEND_METHOD(reflection_reference, __construct)
+{
+ _DO_THROW(
+ "Cannot directly instantiate ReflectionReference. "
+ "Use ReflectionReference::fromArrayElement() instead"
+ );
+}
+/* }}} */
+
+/* {{{ proto public ReflectionReference|null ReflectionReference::fromArrayElement(array array, mixed key)
+ * Create ReflectionReference for array item. Returns null if not a reference. */
+ZEND_METHOD(reflection_reference, fromArrayElement)
+{
+ HashTable *ht;
+ zval *key, *item;
+ reflection_object *intern;
+
+ if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "hz", &ht, &key) == FAILURE) {
+ return;
+ }
+
+ if (Z_TYPE_P(key) == IS_LONG) {
+ item = zend_hash_index_find(ht, Z_LVAL_P(key));
+ } else if (Z_TYPE_P(key) == IS_STRING) {
+ item = zend_symtable_find(ht, Z_STR_P(key));
+ } else {
+ zend_type_error("Key must be array or string");
+ return;
+ }
+
+ if (!item) {
+ _DO_THROW("Array key not found");
+ }
+
+ if (Z_TYPE_P(item) != IS_REFERENCE) {
+ RETURN_NULL();
+ }
+
+ object_init_ex(return_value, reflection_reference_ptr);
+ intern = Z_REFLECTION_P(return_value);
+ ZVAL_COPY(&intern->obj, item);
+ intern->ref_type = REF_TYPE_OTHER;
+}
+/* }}} */
+
+/* {{{ proto public int|string ReflectionReference::getId()
+ * Returns a unique identifier for the reference.
+ * The format of the return value is unspecified and may change. */
+ZEND_METHOD(reflection_reference, getId)
+{
+ reflection_object *intern;
+ unsigned char digest[20];
+ PHP_SHA1_CTX context;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ intern = Z_REFLECTION_P(getThis());
+ if (Z_TYPE(intern->obj) != IS_REFERENCE) {
+ _DO_THROW("Corrupted ReflectionReference object");
+ }
+
+ if (!REFLECTION_G(key_initialized)) {
+ if (php_random_bytes_throw(&REFLECTION_G(key_initialized), 16) == FAILURE) {
+ return;
+ }
+
+ REFLECTION_G(key_initialized) = 1;
+ }
+
+ /* SHA1(ref || key) to avoid directly exposing memory addresses. */
+ PHP_SHA1Init(&context);
+ PHP_SHA1Update(&context, (unsigned char *) &Z_REF(intern->obj), sizeof(zend_reference *));
+ PHP_SHA1Update(&context, REFLECTION_G(key), REFLECTION_KEY_LEN);
+ PHP_SHA1Final(digest, &context);
+
+ RETURN_STRINGL((char *) digest, sizeof(digest));
+}
+/* }}} */
+
/* {{{ method tables */
static const zend_function_entry reflection_exception_functions[] = {
PHP_FE_END
@@ -6637,6 +6733,21 @@ static const zend_function_entry reflection_zend_extension_functions[] = {
ZEND_ME(reflection_zend_extension, getCopyright, arginfo_reflection__void, 0)
PHP_FE_END
};
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_reflection_reference_fromArrayElement, 0, 0, 2)
+ ZEND_ARG_INFO(0, array)
+ ZEND_ARG_INFO(0, key)
+ZEND_END_ARG_INFO()
+
+static const zend_function_entry reflection_reference_functions[] = {
+ ZEND_ME(reflection_reference, fromArrayElement, arginfo_reflection_reference_fromArrayElement, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ ZEND_ME(reflection_reference, getId, arginfo_reflection__void, ZEND_ACC_PUBLIC)
+
+ /* Always throwing dummy methods */
+ ZEND_ME(reflection, __clone, arginfo_reflection__void, ZEND_ACC_PRIVATE)
+ ZEND_ME(reflection_reference, __construct, arginfo_reflection__void, ZEND_ACC_PRIVATE)
+ PHP_FE_END
+};
/* }}} */
static const zend_function_entry reflection_ext_functions[] = { /* {{{ */
@@ -6777,6 +6888,13 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
zend_class_implements(reflection_zend_extension_ptr, 1, reflector_ptr);
zend_declare_property_string(reflection_zend_extension_ptr, "name", sizeof("name")-1, "", ZEND_ACC_PUBLIC);
+ INIT_CLASS_ENTRY(_reflection_entry, "ReflectionReference", reflection_reference_functions);
+ reflection_init_class_handlers(&_reflection_entry);
+ _reflection_entry.ce_flags |= ZEND_ACC_FINAL;
+ reflection_reference_ptr = zend_register_internal_class(&_reflection_entry);
+
+ REFLECTION_G(key_initialized) = 0;
+
return SUCCESS;
} /* }}} */
@@ -6797,5 +6915,9 @@ zend_module_entry reflection_module_entry = { /* {{{ */
NULL,
PHP_MINFO(reflection),
PHP_REFLECTION_VERSION,
- STANDARD_MODULE_PROPERTIES
+ ZEND_MODULE_GLOBALS(reflection),
+ NULL,
+ NULL,
+ NULL,
+ STANDARD_MODULE_PROPERTIES_EX
}; /* }}} */
diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h
index 82dacb5501..b1d5717e3f 100644
--- a/ext/reflection/php_reflection.h
+++ b/ext/reflection/php_reflection.h
@@ -43,6 +43,7 @@ extern PHPAPI zend_class_entry *reflection_method_ptr;
extern PHPAPI zend_class_entry *reflection_property_ptr;
extern PHPAPI zend_class_entry *reflection_extension_ptr;
extern PHPAPI zend_class_entry *reflection_zend_extension_ptr;
+extern PHPAPI zend_class_entry *reflection_reference_ptr;
PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object);
diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt
index 9b2122d1b9..5877f88e27 100644
--- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt
+++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt
@@ -9,7 +9,7 @@ var_dump($ext->getClasses());
?>
==DONE==
--EXPECT--
-array(16) {
+array(17) {
["ReflectionException"]=>
object(ReflectionClass)#2 (1) {
["name"]=>
@@ -90,5 +90,10 @@ array(16) {
["name"]=>
string(23) "ReflectionZendExtension"
}
+ ["ReflectionReference"]=>
+ object(ReflectionClass)#18 (1) {
+ ["name"]=>
+ string(19) "ReflectionReference"
+ }
}
==DONE==
diff --git a/ext/reflection/tests/ReflectionReference.phpt b/ext/reflection/tests/ReflectionReference.phpt
new file mode 100644
index 0000000000..76d2512123
--- /dev/null
+++ b/ext/reflection/tests/ReflectionReference.phpt
@@ -0,0 +1,55 @@
+--TEST--
+Basic ReflectionReference functionality
+--FILE--
+<?php
+
+$ary = [0, 1, 2];
+$ref1 =& $ary[1];
+unset($ref1);
+$ref2 =& $ary[2];
+
+echo "fromArrayElement():\n";
+$r0 = ReflectionReference::fromArrayElement($ary, 0);
+var_dump($r0 === null);
+$r1 = ReflectionReference::fromArrayElement($ary, 1);
+var_dump($r1 instanceof ReflectionReference);
+$r2 = ReflectionReference::fromArrayElement($ary, 2);
+var_dump($r2 instanceof ReflectionReference);
+
+echo "getId() #1:\n";
+var_dump($r1->getId() === $r1->getId());
+var_dump($r2->getId() === $r2->getId());
+var_dump($r1->getId() !== $r2->getId());
+
+echo "getId() #2:\n";
+$ary2 = [&$ary[1], &$ref2];
+$r1_2 = ReflectionReference::fromArrayElement($ary2, 0);
+$r2_2 = ReflectionReference::fromArrayElement($ary2, 1);
+var_dump($r1->getId() === $r1_2->getId());
+var_dump($r2->getId() === $r2_2->getId());
+
+echo "getId() #3:\n";
+$r1_id = $r1->getId();
+$r2_id = $r2->getId();
+unset($r0, $r1, $r2, $r1_2, $r2_2);
+$r1 = ReflectionReference::fromArrayElement($ary, 1);
+$r2 = ReflectionReference::fromArrayElement($ary, 2);
+var_dump($r1_id === $r1->getId());
+var_dump($r2_id === $r2->getId());
+
+?>
+--EXPECT--
+fromArrayElement():
+bool(true)
+bool(true)
+bool(true)
+getId() #1:
+bool(true)
+bool(true)
+bool(true)
+getId() #2:
+bool(true)
+bool(true)
+getId() #3:
+bool(true)
+bool(true)
diff --git a/ext/reflection/tests/ReflectionReference_errors.phpt b/ext/reflection/tests/ReflectionReference_errors.phpt
new file mode 100644
index 0000000000..8e52a1d223
--- /dev/null
+++ b/ext/reflection/tests/ReflectionReference_errors.phpt
@@ -0,0 +1,52 @@
+--TEST--
+Various error conditions for ReflectionReference
+--FILE--
+<?php
+
+try {
+ new ReflectionReference();
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+try {
+ ReflectionReference::fromArrayElement(new stdClass, "test");
+} catch (TypeError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+try {
+ ReflectionReference::fromArrayElement([], 1.5);
+} catch (TypeError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+try {
+ $ary = [0, 1, 2];
+ ReflectionReference::fromArrayElement($ary, 3);
+} catch (ReflectionException $e) {
+ echo $e->getMessage(), "\n";
+}
+
+try {
+ $ary = [&$ary];
+ $ref = ReflectionReference::fromArrayElement($ary, 0);
+ var_dump(serialize($ref));
+} catch (Exception $e) {
+ echo $e->getMessage(), "\n";
+}
+
+var_dump(unserialize('O:19:"ReflectionReference":0:{}'));
+
+?>
+--EXPECTF--
+Call to private ReflectionReference::__construct() from invalid context
+ReflectionReference::fromArrayElement() expects parameter 1 to be array, object given
+Key must be array or string
+Array key not found
+Serialization of 'ReflectionReference' is not allowed
+
+Warning: Erroneous data format for unserializing 'ReflectionReference' in %s on line %d
+
+Notice: unserialize(): Error at offset 30 of 31 bytes in %s on line %d
+bool(false)