Skip to content

Commit 8eb5cb3

Browse files
committed
Attribute support
1 parent a935292 commit 8eb5cb3

File tree

7 files changed

+223
-34
lines changed

7 files changed

+223
-34
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
--TEST--
2+
Partial application copies attributes
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class Test {}
8+
9+
#[NoDiscard]
10+
function f($a, #[SensitiveParameter] $b, #[Test] ...$c) {
11+
}
12+
13+
function dump_attributes($function) {
14+
echo "Function attributes:\n";
15+
$r = new ReflectionFunction($function);
16+
var_dump($r->getAttributes());
17+
18+
foreach ($r->getParameters() as $i => $p) {
19+
echo "Parameter $i:\n";
20+
var_dump($p->getAttributes());
21+
}
22+
}
23+
24+
dump_attributes('f');
25+
26+
$f = f(1, ?, ?, ...);
27+
28+
dump_attributes($f);
29+
30+
?>
31+
--EXPECTF--
32+
Function attributes:
33+
array(1) {
34+
[0]=>
35+
object(ReflectionAttribute)#%d (1) {
36+
["name"]=>
37+
string(9) "NoDiscard"
38+
}
39+
}
40+
Parameter 0:
41+
array(0) {
42+
}
43+
Parameter 1:
44+
array(1) {
45+
[0]=>
46+
object(ReflectionAttribute)#%d (1) {
47+
["name"]=>
48+
string(18) "SensitiveParameter"
49+
}
50+
}
51+
Parameter 2:
52+
array(1) {
53+
[0]=>
54+
object(ReflectionAttribute)#%d (1) {
55+
["name"]=>
56+
string(4) "Test"
57+
}
58+
}
59+
Function attributes:
60+
array(1) {
61+
[0]=>
62+
object(ReflectionAttribute)#%d (1) {
63+
["name"]=>
64+
string(9) "NoDiscard"
65+
}
66+
}
67+
Parameter 0:
68+
array(1) {
69+
[0]=>
70+
object(ReflectionAttribute)#%d (1) {
71+
["name"]=>
72+
string(18) "SensitiveParameter"
73+
}
74+
}
75+
Parameter 1:
76+
array(1) {
77+
[0]=>
78+
object(ReflectionAttribute)#%d (1) {
79+
["name"]=>
80+
string(4) "Test"
81+
}
82+
}
83+
Parameter 2:
84+
array(1) {
85+
[0]=>
86+
object(ReflectionAttribute)#%d (1) {
87+
["name"]=>
88+
string(4) "Test"
89+
}
90+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
Partial application preserves #[SensitiveParameter]
3+
--FILE--
4+
<?php
5+
6+
function f($a, #[SensitiveParameter] $b, $c) {
7+
throw new Exception();
8+
}
9+
10+
echo "# During partial application:\n";
11+
12+
try {
13+
$f = f(1, 'sensitive');
14+
} catch (Error $e) {
15+
echo $e, "\n\n";
16+
}
17+
18+
echo "# In trampoline:\n";
19+
20+
try {
21+
$f = f(1, ?, ?)('sensitive');
22+
} catch (Error $e) {
23+
echo $e, "\n\n";
24+
}
25+
26+
echo "# In execution:\n";
27+
28+
try {
29+
$f = f(1, ?, ?)('sensitive', 3);
30+
} catch (Exception $e) {
31+
echo $e, "\n";
32+
}
33+
34+
?>
35+
--EXPECTF--
36+
# During partial application:
37+
ArgumentCountError: Too few arguments to function f(), 2 passed in %s on line %d and exactly 3 expected in %s:%d
38+
Stack trace:
39+
#0 %s(%d): f(1, Object(SensitiveParameterValue))
40+
#1 {main}
41+
42+
# In trampoline:
43+
Error: not enough arguments for application of f, 1 given and exactly 2 expected, declared in %s on line %d in %s:%d
44+
Stack trace:
45+
#0 %s(%d): Closure->f(Object(SensitiveParameterValue))
46+
#1 {main}
47+
48+
# In execution:
49+
Exception in %s:%d
50+
Stack trace:
51+
#0 %s(%d): f(1, Object(SensitiveParameterValue), 3)
52+
#1 {main}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Partial application preserves #[NoDiscard]
3+
--FILE--
4+
<?php
5+
6+
#[NoDiscard] function f($a) {
7+
}
8+
9+
$f = f(?);
10+
$f(1);
11+
12+
(void) $f(1);
13+
14+
?>
15+
--EXPECTF--
16+
Warning: The return value of function f() should either be used or intentionally ignored by casting it as (void) in %s on line 7
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Partial application preserves #[Deprecated]
3+
--FILE--
4+
<?php
5+
6+
#[Deprecated] function f($a) {
7+
}
8+
9+
$f = f(?);
10+
$f(1);
11+
12+
?>
13+
--EXPECTF--
14+
Deprecated: Function f() is deprecated in %s on line 7

Zend/zend_partial.c

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "zend_types.h"
2727
#include "zend_vm.h"
2828
#include "zend_observer.h"
29+
#include "zend_attributes.h"
2930

3031
typedef struct _zend_partial {
3132
/* Common zend_closure fields */
@@ -98,6 +99,23 @@ static zend_always_inline uint32_t zend_partial_signature_size(zend_partial *par
9899
return count * sizeof(zend_arg_info);
99100
}
100101

102+
static void zend_partial_signature_copy_attributes(zend_array *dest, zend_array *src, uint32_t dest_offset, uint32_t src_offset) {
103+
zend_attribute *attr;
104+
ZEND_HASH_PACKED_FOREACH_PTR(src, attr) {
105+
if (attr->offset == src_offset) {
106+
if (src_offset != 0) {
107+
zend_attribute *copy = emalloc(sizeof(zend_attribute));
108+
memcpy(copy, attr, sizeof(zend_attribute));
109+
copy->offset = dest_offset;
110+
attr = copy;
111+
}
112+
zend_hash_next_index_insert_ptr(dest, attr);
113+
} else if (attr->offset > src_offset) {
114+
break;
115+
}
116+
} ZEND_HASH_FOREACH_END();
117+
}
118+
101119
static zend_always_inline void zend_partial_signature_create(zend_partial *partial) {
102120
zend_arg_info *signature = emalloc(zend_partial_signature_size(partial)), *info = signature;
103121

@@ -113,6 +131,16 @@ static zend_always_inline void zend_partial_signature_create(zend_partial *parti
113131
memset(partial->trampoline.op_array.arg_flags, 0,
114132
sizeof(partial->trampoline.op_array.arg_flags));
115133

134+
zend_array *attributes;
135+
if (zend_hash_num_elements(partial->func.common.attributes)) {
136+
attributes = emalloc(sizeof(zend_array));
137+
zend_hash_init(attributes, 0, NULL, NULL, false);
138+
zend_partial_signature_copy_attributes(attributes, partial->func.common.attributes, 0, 0);
139+
partial->trampoline.common.attributes = attributes;
140+
} else {
141+
attributes = NULL;
142+
}
143+
116144
while (offset < limit) {
117145
zval *arg = &partial->argv[offset];
118146

@@ -132,6 +160,9 @@ static zend_always_inline void zend_partial_signature_create(zend_partial *parti
132160
byref = true;
133161
}
134162
}
163+
if (UNEXPECTED(attributes)) {
164+
zend_partial_signature_copy_attributes(attributes, partial->func.common.attributes, num, offset + 1);
165+
}
135166
info++;
136167
} else {
137168
ZEND_ASSERT(ZEND_PARTIAL_FUNC_FLAG(&partial->func, ZEND_ACC_VARIADIC));
@@ -154,6 +185,9 @@ static zend_always_inline void zend_partial_signature_create(zend_partial *parti
154185
byref = true;
155186
}
156187
}
188+
if (UNEXPECTED(attributes)) {
189+
zend_partial_signature_copy_attributes(attributes, partial->func.common.attributes, num, partial->func.common.num_args + 1);
190+
}
157191
info++;
158192
}
159193
} else if (Z_ISUNDEF_P(arg)) {
@@ -188,6 +222,9 @@ static zend_always_inline void zend_partial_signature_create(zend_partial *parti
188222
}
189223
byref = true;
190224
}
225+
if (UNEXPECTED(attributes)) {
226+
zend_partial_signature_copy_attributes(attributes, partial->func.common.attributes, num + 1, partial->func.common.num_args + 1);
227+
}
191228
info++;
192229
}
193230

@@ -267,7 +304,7 @@ static zend_always_inline void zend_partial_trampoline_create(zend_partial *part
267304
static const void *dummy = (void*)(intptr_t)2;
268305

269306
const uint32_t keep_flags =
270-
ZEND_ACC_RETURN_REFERENCE | ZEND_ACC_HAS_RETURN_TYPE | ZEND_ACC_STRICT_TYPES;
307+
ZEND_ACC_RETURN_REFERENCE | ZEND_ACC_HAS_RETURN_TYPE | ZEND_ACC_STRICT_TYPES | ZEND_ACC_NODISCARD | ZEND_ACC_DEPRECATED;
271308

272309
trampoline->common = partial->func.common;
273310
trampoline->type = ZEND_USER_FUNCTION;
@@ -500,6 +537,19 @@ static void zend_partial_free(zend_object *object) {
500537
destroy_op_array(&partial->func.op_array);
501538
}
502539

540+
/* When partial->func.common.attributes has elements,
541+
* partial->trampoline.common.attributes is a copy that must be destroyed */
542+
if (zend_hash_num_elements(partial->func.common.attributes)) {
543+
zend_attribute *attr;
544+
ZEND_HASH_PACKED_FOREACH_PTR(partial->trampoline.common.attributes, attr) {
545+
if (attr->offset > 0) {
546+
efree(attr);
547+
}
548+
} ZEND_HASH_FOREACH_END();
549+
zend_hash_destroy(partial->trampoline.common.attributes);
550+
efree(partial->trampoline.common.attributes);
551+
}
552+
503553
if (Z_TYPE(partial->This) == IS_OBJECT) {
504554
zval_ptr_dtor(&partial->This);
505555
}

Zend/zend_vm_def.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9261,17 +9261,6 @@ ZEND_VM_HANDLER(214, ZEND_CALL_PARTIAL, ANY, ANY, SPEC(OBSERVER))
92619261

92629262
fbc = call->func;
92639263

9264-
// TODO: deprecated check
9265-
if (UNEXPECTED(!RETURN_VALUE_USED(opline) && (fbc->common.fn_flags & ZEND_ACC_NODISCARD))) {
9266-
if ((fbc->common.fn_flags & ZEND_ACC_NODISCARD) && EG(exception) == NULL) {
9267-
zend_nodiscard_function(fbc);
9268-
}
9269-
if (UNEXPECTED(EG(exception) != NULL)) {
9270-
UNDEF_RESULT();
9271-
HANDLE_EXCEPTION();
9272-
}
9273-
}
9274-
92759264
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)) {
92769265
execute_data = call;
92779266
i_init_func_execute_data_ex(&fbc->op_array, ret, true, true, true EXECUTE_DATA_CC);

Zend/zend_vm_execute.h

Lines changed: 0 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)