diff options
author | Nikita Popov <nikita.ppv@gmail.com> | 2018-09-24 20:57:39 +0200 |
---|---|---|
committer | Nikita Popov <nikita.ppv@gmail.com> | 2019-02-13 12:21:18 +0100 |
commit | 6347f0b937cc3fb3edb38003a41f3f9e8608831d (patch) | |
tree | 6896dcf4beff65309a56dd09d0c6a71c32d938ee /ext/reflection | |
parent | 34122ede2116777b02473a7d4430a15edcc58063 (diff) | |
download | php-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.c | 124 | ||||
-rw-r--r-- | ext/reflection/php_reflection.h | 1 | ||||
-rw-r--r-- | ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt | 7 | ||||
-rw-r--r-- | ext/reflection/tests/ReflectionReference.phpt | 55 | ||||
-rw-r--r-- | ext/reflection/tests/ReflectionReference_errors.phpt | 52 |
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) |