diff options
author | Benjamin Eberlei <kontakt@beberlei.de> | 2020-05-24 20:57:00 +0200 |
---|---|---|
committer | Benjamin Eberlei <kontakt@beberlei.de> | 2020-06-04 18:19:49 +0200 |
commit | a7908c2d11439a203be92e765b1e000498689e00 (patch) | |
tree | 134edc814397a6ffa5218bf5b2153983c09b152e /ext/reflection/php_reflection.c | |
parent | 970ac28e274b7edcd46bad4588d40d444d4f9074 (diff) | |
download | php-git-a7908c2d11439a203be92e765b1e000498689e00.tar.gz |
Add Attributes
Co-authored-by: Martin Schröder <m.schroeder2007@gmail.com>
Diffstat (limited to 'ext/reflection/php_reflection.c')
-rw-r--r-- | ext/reflection/php_reflection.c | 398 |
1 files changed, 396 insertions, 2 deletions
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 9aac065e38..6d4f257eb4 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -31,6 +31,8 @@ #include "zend.h" #include "zend_API.h" +#include "zend_ast.h" +#include "zend_attributes.h" #include "zend_exceptions.h" #include "zend_operators.h" #include "zend_constants.h" @@ -84,6 +86,7 @@ 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; +PHPAPI zend_class_entry *reflection_attribute_ptr; /* Exception throwing macro */ #define _DO_THROW(msg) \ @@ -109,6 +112,8 @@ PHPAPI zend_class_entry *reflection_reference_ptr; #define REGISTER_REFLECTION_CLASS_CONST_LONG(class_name, const_name, value) \ zend_declare_class_constant_long(reflection_ ## class_name ## _ptr, const_name, sizeof(const_name)-1, (zend_long)value); +#define REFLECTION_ATTRIBUTE_IS_INSTANCEOF (1 << 1) + /* {{{ Object structure */ /* Struct for properties */ @@ -132,6 +137,12 @@ typedef struct _type_reference { zend_bool legacy_behavior; } type_reference; +/* Struct for attributes */ +typedef struct _attribute_reference { + zend_attribute *data; + zend_class_entry *scope; +} attribute_reference; + typedef enum { REF_TYPE_OTHER, /* Must be 0 */ REF_TYPE_FUNCTION, @@ -139,7 +150,8 @@ typedef enum { REF_TYPE_PARAMETER, REF_TYPE_TYPE, REF_TYPE_PROPERTY, - REF_TYPE_CLASS_CONSTANT + REF_TYPE_CLASS_CONSTANT, + REF_TYPE_ATTRIBUTE } reflection_type_t; /* Struct for reflection objects */ @@ -238,6 +250,9 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */ zend_string_release_ex(prop_reference->unmangled_name, 0); efree(intern->ptr); break; + case REF_TYPE_ATTRIBUTE: + efree(intern->ptr); + break; case REF_TYPE_GENERATOR: case REF_TYPE_CLASS_CONSTANT: case REF_TYPE_OTHER: @@ -1058,6 +1073,116 @@ static void _extension_string(smart_str *str, zend_module_entry *module, char *i } /* }}} */ +/* {{{ reflection_attribute_factory */ +static void reflection_attribute_factory(zval *object, zend_attribute *data, zend_class_entry *scope) +{ + reflection_object *intern; + attribute_reference *reference; + + reflection_instantiate(reflection_attribute_ptr, object); + intern = Z_REFLECTION_P(object); + reference = (attribute_reference*) emalloc(sizeof(attribute_reference)); + reference->data = data; + reference->scope = scope; + intern->ptr = reference; + intern->ref_type = REF_TYPE_ATTRIBUTE; +} +/* }}} */ + +static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope, + uint32_t offset, zend_string *name, zend_class_entry *base) /* {{{ */ +{ + ZEND_ASSERT(attributes != NULL); + + zend_attribute *attr; + zval tmp; + + if (name) { + // Name based filtering using lowercased key. + zend_string *filter = zend_string_tolower(name); + + ZEND_HASH_FOREACH_PTR(attributes, attr) { + if (attr->offset == offset && zend_string_equals(attr->lcname, filter)) { + reflection_attribute_factory(&tmp, attr, scope); + add_next_index_zval(ret, &tmp); + } + } ZEND_HASH_FOREACH_END(); + + zend_string_release(filter); + return SUCCESS; + } + + ZEND_HASH_FOREACH_PTR(attributes, attr) { + if (attr->offset != offset) { + continue; + } + + if (base) { + // Base type filtering. + zend_class_entry *ce = zend_lookup_class_ex(attr->name, attr->lcname, 0); + + if (ce == NULL) { + // Bailout on error, otherwise ignore unavailable class. + if (EG(exception)) { + return FAILURE; + } + + continue; + } + + if (!instanceof_function(ce, base)) { + continue; + } + } + + reflection_attribute_factory(&tmp, attr, scope); + add_next_index_zval(ret, &tmp); + } ZEND_HASH_FOREACH_END(); + + return SUCCESS; +} +/* }}} */ + +static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attributes, + uint32_t offset, zend_class_entry *scope) /* {{{ */ +{ + zend_string *name = NULL; + zend_long flags = 0; + zend_class_entry *base = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S!l", &name, &flags) == FAILURE) { + RETURN_THROWS(); + } + + if (flags & ~REFLECTION_ATTRIBUTE_IS_INSTANCEOF) { + zend_argument_value_error(2, "must be a valid attribute filter flag"); + RETURN_THROWS(); + } + + if (name && (flags & REFLECTION_ATTRIBUTE_IS_INSTANCEOF)) { + if (NULL == (base = zend_lookup_class(name))) { + if (!EG(exception)) { + zend_throw_error(NULL, "Class '%s' not found", ZSTR_VAL(name)); + } + + RETURN_THROWS(); + } + + name = NULL; + } + + if (!attributes) { + RETURN_EMPTY_ARRAY(); + } + + array_init(return_value); + + if (FAILURE == read_attributes(return_value, attributes, scope, offset, name, base)) { + RETURN_THROWS(); + } +} +/* }}} */ + static void _zend_extension_string(smart_str *str, zend_extension *extension, char *indent) /* {{{ */ { smart_str_append_printf(str, "%sZend Extension [ %s ", indent, extension->name); @@ -1630,6 +1755,19 @@ ZEND_METHOD(ReflectionFunctionAbstract, getDocComment) } /* }}} */ +/* {{{ proto public array ReflectionFunction::getAttributes([ string name, int flags ]) + Returns the attributes of this function */ +ZEND_METHOD(ReflectionFunctionAbstract, getAttributes) +{ + reflection_object *intern; + zend_function *fptr; + + GET_REFLECTION_OBJECT_PTR(fptr); + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, fptr->common.attributes, 0, fptr->common.scope); +} +/* }}} */ + /* {{{ proto public array ReflectionFunction::getStaticVariables() Returns an associative array containing this function's static variables and their values */ ZEND_METHOD(ReflectionFunctionAbstract, getStaticVariables) @@ -2556,7 +2694,22 @@ ZEND_METHOD(ReflectionParameter, canBePassedByValue) } /* }}} */ -/* {{{ proto public bool ReflectionParameter::getPosition() +/* {{{ proto public array ReflectionParameter::getAttributes([ string name, int flags ]) + Get parameter attributes. */ +ZEND_METHOD(ReflectionParameter, getAttributes) +{ + reflection_object *intern; + parameter_reference *param; + + GET_REFLECTION_OBJECT_PTR(param); + + HashTable *attributes = param->fptr->common.attributes; + zend_class_entry *scope = param->fptr->common.scope; + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, attributes, param->offset + 1, scope); +} + +/* {{{ proto public int ReflectionParameter::getPosition() Returns whether this parameter is an optional parameter */ ZEND_METHOD(ReflectionParameter, getPosition) { @@ -3612,6 +3765,19 @@ ZEND_METHOD(ReflectionClassConstant, getDocComment) } /* }}} */ +/* {{{ proto public array ReflectionClassConstant::getAttributes([ string name, int flags ]) + Returns the attributes of this constant */ +ZEND_METHOD(ReflectionClassConstant, getAttributes) +{ + reflection_object *intern; + zend_class_constant *ref; + + GET_REFLECTION_OBJECT_PTR(ref); + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->attributes, 0, ref->ce); +} +/* }}} */ + /* {{{ reflection_class_object_ctor */ static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_object) { @@ -3979,6 +4145,19 @@ ZEND_METHOD(ReflectionClass, getDocComment) } /* }}} */ +/* {{{ proto public array ReflectionClass::getAttributes([ string name, int flags ]) + Returns the attributes for this class */ +ZEND_METHOD(ReflectionClass, getAttributes) +{ + reflection_object *intern; + zend_class_entry *ce; + + GET_REFLECTION_OBJECT_PTR(ce); + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ce->attributes, 0, ce); +} +/* }}} */ + /* {{{ proto public ReflectionMethod ReflectionClass::getConstructor() Returns the class' constructor if there is one, NULL otherwise */ ZEND_METHOD(ReflectionClass, getConstructor) @@ -5479,6 +5658,19 @@ ZEND_METHOD(ReflectionProperty, getDocComment) } /* }}} */ +/* {{{ proto public array ReflectionProperty::getAttributes([ string name, int flags ]) + Returns the attributes of this property */ +ZEND_METHOD(ReflectionProperty, getAttributes) +{ + reflection_object *intern; + property_reference *ref; + + GET_REFLECTION_OBJECT_PTR(ref); + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->prop->attributes, 0, ref->prop->ce); +} +/* }}} */ + /* {{{ proto public int ReflectionProperty::setAccessible(bool visible) Sets whether non-public properties can be requested */ ZEND_METHOD(ReflectionProperty, setAccessible) @@ -6190,6 +6382,201 @@ ZEND_METHOD(ReflectionReference, getId) } /* }}} */ +ZEND_METHOD(ReflectionAttribute, __construct) +{ +} + +ZEND_METHOD(ReflectionAttribute, __clone) +{ + /* Should never be executable */ + _DO_THROW("Cannot clone object using __clone()"); +} + +/* {{{ proto public string ReflectionAttribute::getName() + * Returns the name of the attribute */ +ZEND_METHOD(ReflectionAttribute, getName) +{ + reflection_object *intern; + attribute_reference *attr; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(attr); + + RETURN_STR_COPY(attr->data->name); +} +/* }}} */ + +static zend_always_inline int import_attribute_value(zval *ret, zval *val, zend_class_entry *scope) /* {{{ */ +{ + ZVAL_COPY_OR_DUP(ret, val); + + if (Z_TYPE_P(val) == IS_CONSTANT_AST) { + if (SUCCESS != zval_update_constant_ex(ret, scope)) { + zval_ptr_dtor(ret); + return FAILURE; + } + } + + return SUCCESS; +} +/* }}} */ + +/* {{{ proto public array ReflectionAttribute::getArguments() + * Returns the arguments passed to the attribute */ +ZEND_METHOD(ReflectionAttribute, getArguments) +{ + reflection_object *intern; + attribute_reference *attr; + + zval tmp; + uint32_t i; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(attr); + + array_init(return_value); + + for (i = 0; i < attr->data->argc; i++) { + if (FAILURE == import_attribute_value(&tmp, &attr->data->argv[i], attr->scope)) { + RETURN_THROWS(); + } + + add_next_index_zval(return_value, &tmp); + } +} +/* }}} */ + +static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zval *args, uint32_t argc) /* {{{ */ +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + zend_function *ctor; + zval retval; + int ret; + + ctor = ce->constructor; + + ZEND_ASSERT(ctor != NULL); + + if (!(ctor->common.fn_flags & ZEND_ACC_PUBLIC)) { + zend_throw_error(NULL, "Attribute constructor of class '%s' must be public", ZSTR_VAL(ce->name)); + return FAILURE; + } + + fci.size = sizeof(fci); + ZVAL_UNDEF(&fci.function_name); + fci.object = obj; + fci.retval = &retval; + fci.params = args; + fci.param_count = argc; + fci.no_separation = 1; + + fcc.function_handler = ctor; + fcc.called_scope = ce; + fcc.object = obj; + + ret = zend_call_function(&fci, &fcc); + + if (EG(exception)) { + zend_object_store_ctor_failed(obj); + } + + zval_ptr_dtor(&retval); + + if (ret != SUCCESS) { + zend_throw_error(NULL, "Failed to invoke constructor of attribute class '%s'", ZSTR_VAL(ce->name)); + } + + return EG(exception) ? FAILURE : SUCCESS; +} +/* }}} */ + +static void attribute_ctor_cleanup(zval *obj, zval *args, uint32_t argc) /* {{{ */ +{ + if (obj) { + zval_ptr_dtor(obj); + } + + if (args) { + uint32_t i; + + for (i = 0; i < argc; i++) { + zval_ptr_dtor(&args[i]); + } + + efree(args); + } +} +/* }}} */ + +/* {{{ proto public object ReflectionAttribute::newInstance() + * Returns the attribute as an object */ +ZEND_METHOD(ReflectionAttribute, newInstance) +{ + reflection_object *intern; + attribute_reference *attr; + + zend_class_entry *ce; + zval obj; + + zval *args = NULL; + uint32_t count; + uint32_t argc = 0; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(attr); + + if (NULL == (ce = zend_lookup_class(attr->data->name))) { + zend_throw_error(NULL, "Attribute class '%s' not found", ZSTR_VAL(attr->data->name)); + RETURN_THROWS(); + } + + if (!zend_get_attribute_str(ce->attributes, ZEND_STRL("phpattribute"))) { + zend_throw_error(NULL, "Attempting to use class '%s' as attribute that does not have <<PhpAttribute>>.", ZSTR_VAL(attr->data->name)); + RETURN_THROWS(); + } + + if (SUCCESS != object_init_ex(&obj, ce)) { + RETURN_THROWS(); + } + + count = attr->data->argc; + + if (count) { + args = emalloc(count * sizeof(zval)); + + for (argc = 0; argc < attr->data->argc; argc++) { + if (FAILURE == import_attribute_value(&args[argc], &attr->data->argv[argc], attr->scope)) { + attribute_ctor_cleanup(&obj, args, argc); + RETURN_THROWS(); + } + } + } + + if (ce->constructor) { + if (FAILURE == call_attribute_constructor(ce, Z_OBJ(obj), args, argc)) { + attribute_ctor_cleanup(&obj, args, argc); + RETURN_THROWS(); + } + } else if (argc) { + attribute_ctor_cleanup(&obj, args, argc); + zend_throw_error(NULL, "Attribute class '%s' does not have a constructor, cannot pass arguments", ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } + + attribute_ctor_cleanup(NULL, args, argc); + + RETURN_COPY_VALUE(&obj); +} +/* }}} */ static const zend_function_entry reflection_ext_functions[] = { /* {{{ */ PHP_FE_END }; /* }}} */ @@ -6340,6 +6727,13 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ _reflection_entry.ce_flags |= ZEND_ACC_FINAL; reflection_reference_ptr = zend_register_internal_class(&_reflection_entry); + INIT_CLASS_ENTRY(_reflection_entry, "ReflectionAttribute", class_ReflectionAttribute_methods); + reflection_init_class_handlers(&_reflection_entry); + _reflection_entry.ce_flags |= ZEND_ACC_FINAL; + reflection_attribute_ptr = zend_register_internal_class(&_reflection_entry); + + REGISTER_REFLECTION_CLASS_CONST_LONG(attribute, "IS_INSTANCEOF", REFLECTION_ATTRIBUTE_IS_INSTANCEOF); + REFLECTION_G(key_initialized) = 0; return SUCCESS; |