diff options
-rw-r--r-- | Zend/tests/attributes/005_objects.phpt | 6 | ||||
-rw-r--r-- | Zend/tests/attributes/030_strict_types.inc | 5 | ||||
-rw-r--r-- | Zend/tests/attributes/030_strict_types.phpt | 31 | ||||
-rw-r--r-- | Zend/tests/attributes/031_backtrace.phpt | 97 | ||||
-rw-r--r-- | Zend/zend_attributes.c | 25 | ||||
-rw-r--r-- | Zend/zend_attributes.h | 25 | ||||
-rw-r--r-- | Zend/zend_compile.c | 5 | ||||
-rw-r--r-- | ext/reflection/php_reflection.c | 78 |
8 files changed, 231 insertions, 41 deletions
diff --git a/Zend/tests/attributes/005_objects.phpt b/Zend/tests/attributes/005_objects.phpt index 62b14181ef..db0107500c 100644 --- a/Zend/tests/attributes/005_objects.phpt +++ b/Zend/tests/attributes/005_objects.phpt @@ -96,16 +96,16 @@ try { } ?> ---EXPECT-- +--EXPECTF-- string(2) "A1" string(4) "test" int(50) string(7) "ERROR 1" -string(81) "Too few arguments to function A1::__construct(), 0 passed and at least 1 expected" +string(%d) "Too few arguments to function A1::__construct(), 0 passed in %s005_objects.php on line 26 and at least 1 expected" string(7) "ERROR 2" -string(74) "A1::__construct(): Argument #1 ($name) must be of type string, array given" +string(%d) "A1::__construct(): Argument #1 ($name) must be of type string, array given, called in %s005_objects.php on line 36" string(7) "ERROR 3" string(30) "Attribute class "A2" not found" diff --git a/Zend/tests/attributes/030_strict_types.inc b/Zend/tests/attributes/030_strict_types.inc new file mode 100644 index 0000000000..d9494e4fce --- /dev/null +++ b/Zend/tests/attributes/030_strict_types.inc @@ -0,0 +1,5 @@ +<?php +declare(strict_types=1); + +#[MyAttribute("42")] +class TestStrict {} diff --git a/Zend/tests/attributes/030_strict_types.phpt b/Zend/tests/attributes/030_strict_types.phpt new file mode 100644 index 0000000000..d46b4d4a32 --- /dev/null +++ b/Zend/tests/attributes/030_strict_types.phpt @@ -0,0 +1,31 @@ +--TEST-- +strict_types=1 of the attribute use-site is respected +--FILE-- +<?php + +#[Attribute] +class MyAttribute { + public function __construct(public int $value) {} +} + +#[MyAttribute("42")] +class TestWeak {} + +require __DIR__ . '/030_strict_types.inc'; + +var_dump((new ReflectionClass(TestWeak::class))->getAttributes()[0]->newInstance()); +var_dump((new ReflectionClass(TestStrict::class))->getAttributes()[0]->newInstance()); + +?> +--EXPECTF-- +object(MyAttribute)#1 (1) { + ["value"]=> + int(42) +} + +Fatal error: Uncaught TypeError: MyAttribute::__construct(): Argument #1 ($value) must be of type int, string given, called in %s030_strict_types.inc on line 4 and defined in %s030_strict_types.php:5 +Stack trace: +#0 %s030_strict_types.inc(4): MyAttribute->__construct('42') +#1 %s(%d): ReflectionAttribute->newInstance() +#2 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/031_backtrace.phpt b/Zend/tests/attributes/031_backtrace.phpt new file mode 100644 index 0000000000..b0374e67e2 --- /dev/null +++ b/Zend/tests/attributes/031_backtrace.phpt @@ -0,0 +1,97 @@ +--TEST-- +Backtrace during attribute instance creation +--FILE-- +<?php + +#[Attribute] +class MyAttribute { + public function __construct() { + debug_print_backtrace(); + var_dump(debug_backtrace()); + var_dump((new Exception)->getTrace()); + } +} + +#[MyAttribute] +class Test {} + +(new ReflectionClass(Test::class))->getAttributes()[0]->newInstance(); + +?> +--EXPECTF-- +#0 MyAttribute->__construct() called at [%s031_backtrace.php:12] +#1 ReflectionAttribute->newInstance() called at [%s:%d] +array(2) { + [0]=> + array(7) { + ["file"]=> + string(%d) "%s031_backtrace.php" + ["line"]=> + int(12) + ["function"]=> + string(11) "__construct" + ["class"]=> + string(11) "MyAttribute" + ["object"]=> + object(MyAttribute)#1 (0) { + } + ["type"]=> + string(2) "->" + ["args"]=> + array(0) { + } + } + [1]=> + array(7) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["function"]=> + string(11) "newInstance" + ["class"]=> + string(19) "ReflectionAttribute" + ["object"]=> + object(ReflectionAttribute)#2 (0) { + } + ["type"]=> + string(2) "->" + ["args"]=> + array(0) { + } + } +} +array(2) { + [0]=> + array(6) { + ["file"]=> + string(%d) "%s031_backtrace.php" + ["line"]=> + int(12) + ["function"]=> + string(11) "__construct" + ["class"]=> + string(11) "MyAttribute" + ["type"]=> + string(2) "->" + ["args"]=> + array(0) { + } + } + [1]=> + array(6) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["function"]=> + string(11) "newInstance" + ["class"]=> + string(19) "ReflectionAttribute" + ["type"]=> + string(2) "->" + ["args"]=> + array(0) { + } + } +} diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index af5baa6ce0..29a2f4a732 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -175,38 +175,29 @@ ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attrib return 0; } -static zend_always_inline void free_attribute(zend_attribute *attr, bool persistent) +static void attr_free(zval *v) { - uint32_t i; + zend_attribute *attr = Z_PTR_P(v); zend_string_release(attr->name); zend_string_release(attr->lcname); - for (i = 0; i < attr->argc; i++) { + for (uint32_t i = 0; i < attr->argc; i++) { if (attr->args[i].name) { zend_string_release(attr->args[i].name); } zval_ptr_dtor(&attr->args[i].value); } - pefree(attr, persistent); -} - -static void attr_free(zval *v) -{ - free_attribute((zend_attribute *) Z_PTR_P(v), 0); + pefree(attr, attr->flags & ZEND_ATTRIBUTE_PERSISTENT); } -static void attr_pfree(zval *v) -{ - free_attribute((zend_attribute *) Z_PTR_P(v), 1); -} - -ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool persistent, uint32_t offset, zend_string *name, uint32_t argc) +ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_string *name, uint32_t argc, uint32_t flags, uint32_t offset, uint32_t lineno) { + bool persistent = flags & ZEND_ATTRIBUTE_PERSISTENT; if (*attributes == NULL) { *attributes = pemalloc(sizeof(HashTable), persistent); - zend_hash_init(*attributes, 8, NULL, persistent ? attr_pfree : attr_free, persistent); + zend_hash_init(*attributes, 8, NULL, attr_free, persistent); } zend_attribute *attr = pemalloc(ZEND_ATTRIBUTE_SIZE(argc), persistent); @@ -218,6 +209,8 @@ ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool pe } attr->lcname = zend_string_tolower_ex(attr->name, persistent); + attr->flags = flags; + attr->lineno = lineno; attr->offset = offset; attr->argc = argc; diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index 6a60b06665..d19b7e470d 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -30,6 +30,10 @@ #define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<6) #define ZEND_ATTRIBUTE_FLAGS ((1<<7) - 1) +/* Flags for zend_attribute.flags */ +#define ZEND_ATTRIBUTE_PERSISTENT (1<<0) +#define ZEND_ATTRIBUTE_STRICT_TYPES (1<<1) + #define ZEND_ATTRIBUTE_SIZE(argc) \ (sizeof(zend_attribute) + sizeof(zend_attribute_arg) * (argc) - sizeof(zend_attribute_arg)) @@ -45,6 +49,8 @@ typedef struct { typedef struct _zend_attribute { zend_string *name; zend_string *lcname; + uint32_t flags; + uint32_t lineno; /* Parameter offsets start at 1, everything else uses 0. */ uint32_t offset; uint32_t argc; @@ -71,33 +77,40 @@ ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attrib ZEND_API zend_internal_attribute *zend_internal_attribute_register(zend_class_entry *ce, uint32_t flags); ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname); -ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool persistent, uint32_t offset, zend_string *name, uint32_t argc); +ZEND_API zend_attribute *zend_add_attribute( + HashTable **attributes, zend_string *name, uint32_t argc, + uint32_t flags, uint32_t offset, uint32_t lineno); END_EXTERN_C() static zend_always_inline zend_attribute *zend_add_class_attribute(zend_class_entry *ce, zend_string *name, uint32_t argc) { - return zend_add_attribute(&ce->attributes, ce->type != ZEND_USER_CLASS, 0, name, argc); + uint32_t flags = ce->type != ZEND_USER_CLASS ? ZEND_ATTRIBUTE_PERSISTENT : 0; + return zend_add_attribute(&ce->attributes, name, argc, flags, 0, 0); } static zend_always_inline zend_attribute *zend_add_function_attribute(zend_function *func, zend_string *name, uint32_t argc) { - return zend_add_attribute(&func->common.attributes, func->common.type != ZEND_USER_FUNCTION, 0, name, argc); + uint32_t flags = func->common.type != ZEND_USER_FUNCTION ? ZEND_ATTRIBUTE_PERSISTENT : 0; + return zend_add_attribute(&func->common.attributes, name, argc, flags, 0, 0); } static zend_always_inline zend_attribute *zend_add_parameter_attribute(zend_function *func, uint32_t offset, zend_string *name, uint32_t argc) { - return zend_add_attribute(&func->common.attributes, func->common.type != ZEND_USER_FUNCTION, offset + 1, name, argc); + uint32_t flags = func->common.type != ZEND_USER_FUNCTION ? ZEND_ATTRIBUTE_PERSISTENT : 0; + return zend_add_attribute(&func->common.attributes, name, argc, flags, offset + 1, 0); } static zend_always_inline zend_attribute *zend_add_property_attribute(zend_class_entry *ce, zend_property_info *info, zend_string *name, uint32_t argc) { - return zend_add_attribute(&info->attributes, ce->type != ZEND_USER_CLASS, 0, name, argc); + uint32_t flags = ce->type != ZEND_USER_CLASS ? ZEND_ATTRIBUTE_PERSISTENT : 0; + return zend_add_attribute(&info->attributes, name, argc, flags, 0, 0); } static zend_always_inline zend_attribute *zend_add_class_constant_attribute(zend_class_entry *ce, zend_class_constant *c, zend_string *name, uint32_t argc) { - return zend_add_attribute(&c->attributes, ce->type != ZEND_USER_CLASS, 0, name, argc); + uint32_t flags = ce->type != ZEND_USER_CLASS ? ZEND_ATTRIBUTE_PERSISTENT : 0; + return zend_add_attribute(&c->attributes, name, argc, flags, 0, 0); } void zend_register_attribute_ce(void); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ba6790a042..cc03a60e5f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6221,7 +6221,10 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3 zend_string *name = zend_resolve_class_name_ast(el->child[0]); zend_ast_list *args = el->child[1] ? zend_ast_get_list(el->child[1]) : NULL; - attr = zend_add_attribute(attributes, 0, offset, name, args ? args->children : 0); + uint32_t flags = (CG(active_op_array)->fn_flags & ZEND_ACC_STRICT_TYPES) + ? ZEND_ATTRIBUTE_STRICT_TYPES : 0; + attr = zend_add_attribute( + attributes, name, args ? args->children : 0, flags, offset, el->lineno); zend_string_release(name); /* Populate arguments */ diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 470b52e6c7..d4f89be69f 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -142,6 +142,7 @@ typedef struct _attribute_reference { HashTable *attributes; zend_attribute *data; zend_class_entry *scope; + zend_string *filename; uint32_t target; } attribute_reference; @@ -252,9 +253,14 @@ 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: + case REF_TYPE_ATTRIBUTE: { + attribute_reference *attr_ref = intern->ptr; + if (attr_ref->filename) { + zend_string_release(attr_ref->filename); + } efree(intern->ptr); break; + } case REF_TYPE_GENERATOR: case REF_TYPE_CLASS_CONSTANT: case REF_TYPE_OTHER: @@ -1084,7 +1090,7 @@ static void _extension_string(smart_str *str, zend_module_entry *module, char *i /* {{{ reflection_attribute_factory */ static void reflection_attribute_factory(zval *object, HashTable *attributes, zend_attribute *data, - zend_class_entry *scope, uint32_t target) + zend_class_entry *scope, uint32_t target, zend_string *filename) { reflection_object *intern; attribute_reference *reference; @@ -1095,6 +1101,7 @@ static void reflection_attribute_factory(zval *object, HashTable *attributes, ze reference->attributes = attributes; reference->data = data; reference->scope = scope; + reference->filename = filename ? zend_string_copy(filename) : NULL; reference->target = target; intern->ptr = reference; intern->ref_type = REF_TYPE_ATTRIBUTE; @@ -1102,7 +1109,7 @@ static void reflection_attribute_factory(zval *object, HashTable *attributes, ze /* }}} */ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope, - uint32_t offset, uint32_t target, zend_string *name, zend_class_entry *base) /* {{{ */ + uint32_t offset, uint32_t target, zend_string *name, zend_class_entry *base, zend_string *filename) /* {{{ */ { ZEND_ASSERT(attributes != NULL); @@ -1115,7 +1122,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s ZEND_HASH_FOREACH_PTR(attributes, attr) { if (attr->offset == offset && zend_string_equals(attr->lcname, filter)) { - reflection_attribute_factory(&tmp, attributes, attr, scope, target); + reflection_attribute_factory(&tmp, attributes, attr, scope, target, filename); add_next_index_zval(ret, &tmp); } } ZEND_HASH_FOREACH_END(); @@ -1147,7 +1154,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s } } - reflection_attribute_factory(&tmp, attributes, attr, scope, target); + reflection_attribute_factory(&tmp, attributes, attr, scope, target, filename); add_next_index_zval(ret, &tmp); } ZEND_HASH_FOREACH_END(); @@ -1156,7 +1163,7 @@ static int read_attributes(zval *ret, HashTable *attributes, zend_class_entry *s /* }}} */ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attributes, - uint32_t offset, zend_class_entry *scope, uint32_t target) /* {{{ */ + uint32_t offset, zend_class_entry *scope, uint32_t target, zend_string *filename) /* {{{ */ { zend_string *name = NULL; zend_long flags = 0; @@ -1189,7 +1196,7 @@ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attribut array_init(return_value); - if (FAILURE == read_attributes(return_value, attributes, scope, offset, target, name, base)) { + if (FAILURE == read_attributes(return_value, attributes, scope, offset, target, name, base, filename)) { RETURN_THROWS(); } } @@ -1760,7 +1767,9 @@ ZEND_METHOD(ReflectionFunctionAbstract, getAttributes) target = ZEND_ATTRIBUTE_TARGET_FUNCTION; } - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, fptr->common.attributes, 0, fptr->common.scope, target); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, + fptr->common.attributes, 0, fptr->common.scope, target, + fptr->type == ZEND_USER_FUNCTION ? fptr->op_array.filename : NULL); } /* }}} */ @@ -2660,7 +2669,9 @@ ZEND_METHOD(ReflectionParameter, getAttributes) 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, ZEND_ATTRIBUTE_TARGET_PARAMETER); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, + attributes, param->offset + 1, scope, ZEND_ATTRIBUTE_TARGET_PARAMETER, + param->fptr->type == ZEND_USER_FUNCTION ? param->fptr->op_array.filename : NULL); } /* {{{ Returns whether this parameter is an optional parameter */ @@ -3679,7 +3690,9 @@ ZEND_METHOD(ReflectionClassConstant, getAttributes) GET_REFLECTION_OBJECT_PTR(ref); - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->attributes, 0, ref->ce, ZEND_ATTRIBUTE_TARGET_CLASS_CONST); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, + ref->attributes, 0, ref->ce, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, + ref->ce->type == ZEND_USER_CLASS ? ref->ce->info.user.filename : NULL); } /* }}} */ @@ -4076,7 +4089,9 @@ ZEND_METHOD(ReflectionClass, getAttributes) GET_REFLECTION_OBJECT_PTR(ce); - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ce->attributes, 0, ce, ZEND_ATTRIBUTE_TARGET_CLASS); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, + ce->attributes, 0, ce, ZEND_ATTRIBUTE_TARGET_CLASS, + ce->type == ZEND_USER_CLASS ? ce->info.user.filename : NULL); } /* }}} */ @@ -5465,7 +5480,9 @@ ZEND_METHOD(ReflectionProperty, getAttributes) GET_REFLECTION_OBJECT_PTR(ref); - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, ref->prop->attributes, 0, ref->prop->ce, ZEND_ATTRIBUTE_TARGET_PROPERTY); + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, + ref->prop->attributes, 0, ref->prop->ce, ZEND_ATTRIBUTE_TARGET_PROPERTY, + ref->prop->ce->type == ZEND_USER_CLASS ? ref->prop->ce->info.user.filename : NULL); } /* }}} */ @@ -6238,9 +6255,14 @@ ZEND_METHOD(ReflectionAttribute, getArguments) } /* }}} */ -static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zval *args, uint32_t argc, HashTable *named_params) /* {{{ */ +static int call_attribute_constructor( + zend_attribute *attr, zend_class_entry *ce, zend_object *obj, + zval *args, uint32_t argc, HashTable *named_params, zend_string *filename) { zend_function *ctor = ce->constructor; + zend_execute_data *prev_execute_data, dummy_frame; + zend_function dummy_func; + zend_op dummy_opline; ZEND_ASSERT(ctor != NULL); if (!(ctor->common.fn_flags & ZEND_ACC_PUBLIC)) { @@ -6248,8 +6270,35 @@ static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zv return FAILURE; } + if (filename) { + /* Set up dummy call frame that makes it look like the attribute was invoked + * from where it occurs in the code. */ + memset(&dummy_frame, 0, sizeof(zend_execute_data)); + memset(&dummy_func, 0, sizeof(zend_function)); + memset(&dummy_opline, 0, sizeof(zend_op)); + + prev_execute_data = EG(current_execute_data); + dummy_frame.prev_execute_data = prev_execute_data; + dummy_frame.func = &dummy_func; + dummy_frame.opline = &dummy_opline; + + dummy_func.type = ZEND_USER_FUNCTION; + dummy_func.common.fn_flags = + attr->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0; + dummy_func.op_array.filename = filename; + + dummy_opline.opcode = ZEND_DO_FCALL; + dummy_opline.lineno = attr->lineno; + + EG(current_execute_data) = &dummy_frame; + } + zend_call_known_function(ctor, obj, obj->ce, NULL, argc, args, named_params); + if (filename) { + EG(current_execute_data) = prev_execute_data; + } + if (EG(exception)) { zend_object_store_ctor_failed(obj); return FAILURE; @@ -6257,7 +6306,6 @@ static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zv return SUCCESS; } -/* }}} */ static void attribute_ctor_cleanup( zval *obj, zval *args, uint32_t argc, HashTable *named_params) /* {{{ */ @@ -6373,7 +6421,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance) } if (ce->constructor) { - if (FAILURE == call_attribute_constructor(ce, Z_OBJ(obj), args, argc, named_params)) { + if (FAILURE == call_attribute_constructor(attr->data, ce, Z_OBJ(obj), args, argc, named_params, attr->filename)) { attribute_ctor_cleanup(&obj, args, argc, named_params); RETURN_THROWS(); } |