Skip to content

Commit 5686c16

Browse files
committed
Honor strict_types=1 for attributes, improve backtraces
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.
1 parent 43ce784 commit 5686c16

8 files changed

+231
-41
lines changed

Zend/tests/attributes/005_objects.phpt

+3-3
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,16 @@ try {
9696
}
9797

9898
?>
99-
--EXPECT--
99+
--EXPECTF--
100100
string(2) "A1"
101101
string(4) "test"
102102
int(50)
103103

104104
string(7) "ERROR 1"
105-
string(81) "Too few arguments to function A1::__construct(), 0 passed and at least 1 expected"
105+
string(%d) "Too few arguments to function A1::__construct(), 0 passed in %s005_objects.php on line 26 and at least 1 expected"
106106

107107
string(7) "ERROR 2"
108-
string(74) "A1::__construct(): Argument #1 ($name) must be of type string, array given"
108+
string(%d) "A1::__construct(): Argument #1 ($name) must be of type string, array given, called in %s005_objects.php on line 36"
109109

110110
string(7) "ERROR 3"
111111
string(30) "Attribute class "A2" not found"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
#[MyAttribute("42")]
5+
class TestStrict {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
strict_types=1 of the attribute use-site is respected
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class MyAttribute {
8+
public function __construct(public int $value) {}
9+
}
10+
11+
#[MyAttribute("42")]
12+
class TestWeak {}
13+
14+
require __DIR__ . '/030_strict_types.inc';
15+
16+
var_dump((new ReflectionClass(TestWeak::class))->getAttributes()[0]->newInstance());
17+
var_dump((new ReflectionClass(TestStrict::class))->getAttributes()[0]->newInstance());
18+
19+
?>
20+
--EXPECTF--
21+
object(MyAttribute)#1 (1) {
22+
["value"]=>
23+
int(42)
24+
}
25+
26+
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
27+
Stack trace:
28+
#0 %s030_strict_types.inc(4): MyAttribute->__construct('42')
29+
#1 %s(%d): ReflectionAttribute->newInstance()
30+
#2 {main}
31+
thrown in %s on line %d
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
--TEST--
2+
Backtrace during attribute instance creation
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class MyAttribute {
8+
public function __construct() {
9+
debug_print_backtrace();
10+
var_dump(debug_backtrace());
11+
var_dump((new Exception)->getTrace());
12+
}
13+
}
14+
15+
#[MyAttribute]
16+
class Test {}
17+
18+
(new ReflectionClass(Test::class))->getAttributes()[0]->newInstance();
19+
20+
?>
21+
--EXPECTF--
22+
#0 MyAttribute->__construct() called at [%s031_backtrace.php:12]
23+
#1 ReflectionAttribute->newInstance() called at [%s:%d]
24+
array(2) {
25+
[0]=>
26+
array(7) {
27+
["file"]=>
28+
string(%d) "%s031_backtrace.php"
29+
["line"]=>
30+
int(12)
31+
["function"]=>
32+
string(11) "__construct"
33+
["class"]=>
34+
string(11) "MyAttribute"
35+
["object"]=>
36+
object(MyAttribute)#1 (0) {
37+
}
38+
["type"]=>
39+
string(2) "->"
40+
["args"]=>
41+
array(0) {
42+
}
43+
}
44+
[1]=>
45+
array(7) {
46+
["file"]=>
47+
string(%d) "%s"
48+
["line"]=>
49+
int(%d)
50+
["function"]=>
51+
string(11) "newInstance"
52+
["class"]=>
53+
string(19) "ReflectionAttribute"
54+
["object"]=>
55+
object(ReflectionAttribute)#2 (0) {
56+
}
57+
["type"]=>
58+
string(2) "->"
59+
["args"]=>
60+
array(0) {
61+
}
62+
}
63+
}
64+
array(2) {
65+
[0]=>
66+
array(6) {
67+
["file"]=>
68+
string(%d) "%s031_backtrace.php"
69+
["line"]=>
70+
int(12)
71+
["function"]=>
72+
string(11) "__construct"
73+
["class"]=>
74+
string(11) "MyAttribute"
75+
["type"]=>
76+
string(2) "->"
77+
["args"]=>
78+
array(0) {
79+
}
80+
}
81+
[1]=>
82+
array(6) {
83+
["file"]=>
84+
string(%d) "%s"
85+
["line"]=>
86+
int(%d)
87+
["function"]=>
88+
string(11) "newInstance"
89+
["class"]=>
90+
string(19) "ReflectionAttribute"
91+
["type"]=>
92+
string(2) "->"
93+
["args"]=>
94+
array(0) {
95+
}
96+
}
97+
}

Zend/zend_attributes.c

+9-16
Original file line numberDiff line numberDiff line change
@@ -175,38 +175,29 @@ ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attrib
175175
return 0;
176176
}
177177

178-
static zend_always_inline void free_attribute(zend_attribute *attr, bool persistent)
178+
static void attr_free(zval *v)
179179
{
180-
uint32_t i;
180+
zend_attribute *attr = Z_PTR_P(v);
181181

182182
zend_string_release(attr->name);
183183
zend_string_release(attr->lcname);
184184

185-
for (i = 0; i < attr->argc; i++) {
185+
for (uint32_t i = 0; i < attr->argc; i++) {
186186
if (attr->args[i].name) {
187187
zend_string_release(attr->args[i].name);
188188
}
189189
zval_ptr_dtor(&attr->args[i].value);
190190
}
191191

192-
pefree(attr, persistent);
193-
}
194-
195-
static void attr_free(zval *v)
196-
{
197-
free_attribute((zend_attribute *) Z_PTR_P(v), 0);
192+
pefree(attr, attr->flags & ZEND_ATTRIBUTE_PERSISTENT);
198193
}
199194

200-
static void attr_pfree(zval *v)
201-
{
202-
free_attribute((zend_attribute *) Z_PTR_P(v), 1);
203-
}
204-
205-
ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool persistent, uint32_t offset, zend_string *name, uint32_t argc)
195+
ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_string *name, uint32_t argc, uint32_t flags, uint32_t offset, uint32_t lineno)
206196
{
197+
bool persistent = flags & ZEND_ATTRIBUTE_PERSISTENT;
207198
if (*attributes == NULL) {
208199
*attributes = pemalloc(sizeof(HashTable), persistent);
209-
zend_hash_init(*attributes, 8, NULL, persistent ? attr_pfree : attr_free, persistent);
200+
zend_hash_init(*attributes, 8, NULL, attr_free, persistent);
210201
}
211202

212203
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
218209
}
219210

220211
attr->lcname = zend_string_tolower_ex(attr->name, persistent);
212+
attr->flags = flags;
213+
attr->lineno = lineno;
221214
attr->offset = offset;
222215
attr->argc = argc;
223216

Zend/zend_attributes.h

+19-6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<6)
3131
#define ZEND_ATTRIBUTE_FLAGS ((1<<7) - 1)
3232

33+
/* Flags for zend_attribute.flags */
34+
#define ZEND_ATTRIBUTE_PERSISTENT (1<<0)
35+
#define ZEND_ATTRIBUTE_STRICT_TYPES (1<<1)
36+
3337
#define ZEND_ATTRIBUTE_SIZE(argc) \
3438
(sizeof(zend_attribute) + sizeof(zend_attribute_arg) * (argc) - sizeof(zend_attribute_arg))
3539

@@ -45,6 +49,8 @@ typedef struct {
4549
typedef struct _zend_attribute {
4650
zend_string *name;
4751
zend_string *lcname;
52+
uint32_t flags;
53+
uint32_t lineno;
4854
/* Parameter offsets start at 1, everything else uses 0. */
4955
uint32_t offset;
5056
uint32_t argc;
@@ -71,33 +77,40 @@ ZEND_API zend_bool zend_is_attribute_repeated(HashTable *attributes, zend_attrib
7177
ZEND_API zend_internal_attribute *zend_internal_attribute_register(zend_class_entry *ce, uint32_t flags);
7278
ZEND_API zend_internal_attribute *zend_internal_attribute_get(zend_string *lcname);
7379

74-
ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool persistent, uint32_t offset, zend_string *name, uint32_t argc);
80+
ZEND_API zend_attribute *zend_add_attribute(
81+
HashTable **attributes, zend_string *name, uint32_t argc,
82+
uint32_t flags, uint32_t offset, uint32_t lineno);
7583

7684
END_EXTERN_C()
7785

7886
static zend_always_inline zend_attribute *zend_add_class_attribute(zend_class_entry *ce, zend_string *name, uint32_t argc)
7987
{
80-
return zend_add_attribute(&ce->attributes, ce->type != ZEND_USER_CLASS, 0, name, argc);
88+
uint32_t flags = ce->type != ZEND_USER_CLASS ? ZEND_ATTRIBUTE_PERSISTENT : 0;
89+
return zend_add_attribute(&ce->attributes, name, argc, flags, 0, 0);
8190
}
8291

8392
static zend_always_inline zend_attribute *zend_add_function_attribute(zend_function *func, zend_string *name, uint32_t argc)
8493
{
85-
return zend_add_attribute(&func->common.attributes, func->common.type != ZEND_USER_FUNCTION, 0, name, argc);
94+
uint32_t flags = func->common.type != ZEND_USER_FUNCTION ? ZEND_ATTRIBUTE_PERSISTENT : 0;
95+
return zend_add_attribute(&func->common.attributes, name, argc, flags, 0, 0);
8696
}
8797

8898
static zend_always_inline zend_attribute *zend_add_parameter_attribute(zend_function *func, uint32_t offset, zend_string *name, uint32_t argc)
8999
{
90-
return zend_add_attribute(&func->common.attributes, func->common.type != ZEND_USER_FUNCTION, offset + 1, name, argc);
100+
uint32_t flags = func->common.type != ZEND_USER_FUNCTION ? ZEND_ATTRIBUTE_PERSISTENT : 0;
101+
return zend_add_attribute(&func->common.attributes, name, argc, flags, offset + 1, 0);
91102
}
92103

93104
static zend_always_inline zend_attribute *zend_add_property_attribute(zend_class_entry *ce, zend_property_info *info, zend_string *name, uint32_t argc)
94105
{
95-
return zend_add_attribute(&info->attributes, ce->type != ZEND_USER_CLASS, 0, name, argc);
106+
uint32_t flags = ce->type != ZEND_USER_CLASS ? ZEND_ATTRIBUTE_PERSISTENT : 0;
107+
return zend_add_attribute(&info->attributes, name, argc, flags, 0, 0);
96108
}
97109

98110
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)
99111
{
100-
return zend_add_attribute(&c->attributes, ce->type != ZEND_USER_CLASS, 0, name, argc);
112+
uint32_t flags = ce->type != ZEND_USER_CLASS ? ZEND_ATTRIBUTE_PERSISTENT : 0;
113+
return zend_add_attribute(&c->attributes, name, argc, flags, 0, 0);
101114
}
102115

103116
void zend_register_attribute_ce(void);

Zend/zend_compile.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -6221,7 +6221,10 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3
62216221
zend_string *name = zend_resolve_class_name_ast(el->child[0]);
62226222
zend_ast_list *args = el->child[1] ? zend_ast_get_list(el->child[1]) : NULL;
62236223

6224-
attr = zend_add_attribute(attributes, 0, offset, name, args ? args->children : 0);
6224+
uint32_t flags = (CG(active_op_array)->fn_flags & ZEND_ACC_STRICT_TYPES)
6225+
? ZEND_ATTRIBUTE_STRICT_TYPES : 0;
6226+
attr = zend_add_attribute(
6227+
attributes, name, args ? args->children : 0, flags, offset, el->lineno);
62256228
zend_string_release(name);
62266229

62276230
/* Populate arguments */

0 commit comments

Comments
 (0)