]> granicus.if.org Git - php/commitdiff
Honor strict_types=1 for attributes, improve backtraces
authorNikita Popov <nikita.ppv@gmail.com>
Wed, 23 Sep 2020 11:01:07 +0000 (13:01 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Sun, 27 Sep 2020 08:42:58 +0000 (10:42 +0200)
Make ReflectionAttribute::newInstance() respect the strict_types=1
declaration at the attribute use-site. More generally, pretend that
we are calling the attribute constructor from the place where the
attribute is used, which also means that the attribute location will
show up properly in backtraces and inside "called in" error information.

This requires us to store the attributes strict_types scope (as flags),
as well as the attribute line number. The attribute filename can be
recovered from the symbol it is used on. We might want to expose the
attribute line number via reflection as well.

See also https://externals.io/message/111915.

Closes GH-6201.

Zend/tests/attributes/005_objects.phpt
Zend/tests/attributes/030_strict_types.inc [new file with mode: 0644]
Zend/tests/attributes/030_strict_types.phpt [new file with mode: 0644]
Zend/tests/attributes/031_backtrace.phpt [new file with mode: 0644]
Zend/zend_attributes.c
Zend/zend_attributes.h
Zend/zend_compile.c
ext/reflection/php_reflection.c

index 62b14181efd2e29681bfcd24667aa5b6f40c9122..db0107500c8ca375a9be14f4ca9062035849a10d 100644 (file)
@@ -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 (file)
index 0000000..d9494e4
--- /dev/null
@@ -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 (file)
index 0000000..d46b4d4
--- /dev/null
@@ -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 (file)
index 0000000..b0374e6
--- /dev/null
@@ -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) {
+    }
+  }
+}
index af5baa6ce07d18f835f8a8d1c543f24845f5d744..29a2f4a73254686036a8097ba2f7f117495379af 100644 (file)
@@ -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;
 
index 6a60b06665a8fc4c1f9d4a545ce6779dccc33976..d19b7e470dc1759e05f8fc0112df3d6ad4f5d728 100644 (file)
 #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);
index ba6790a042fc1abe5d4e29f4467531c7a0b6af8e..cc03a60e5ffc7ee1e734b9f98d54bbacada852b2 100644 (file)
@@ -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 */
index 470b52e6c7b5b340f9b5e5297aabbde3a0b1da7f..d4f89be69f52421f07e2f06763eda594a88a002c 100644 (file)
@@ -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();
                }