Skip to content

Commit d409119

Browse files
Validators return error messages
1 parent e3f6c2f commit d409119

File tree

6 files changed

+42
-58
lines changed

6 files changed

+42
-58
lines changed

UPGRADING.INTERNALS

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,16 @@ PHP 8.5 INTERNALS UPGRADE NOTES
2828
extra layer of indirection can be removed. In other cases a zval can
2929
be heap-allocated and stored in the pointer as a minimal change to keep
3030
compatibility.
31+
* The validator callbacks for internal attribute now return `zend_string *`
32+
rather than `void`; instead of emitting an error when an attribute is
33+
applied incorrectly, the error message should be returned as a zend_string
34+
pointer.
3135
. The `target` parameter for validator callbacks for internal attributes
32-
can now include in the bitmask the new flags
33-
ZEND_ATTRIBUTE_NO_TARGET_VALIDATION and
34-
ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION. The first indicates that, if the
35-
attribute is being applied to an invalid target, no error should be thrown
36-
at compile time. The second indicates that validation is being performed
37-
at runtime and that if an attribute is being applied to an invalid target,
38-
a catchable error should be thrown. Target validation is different from
39-
the actual functionality of the attribute; e.g. the #[\Override] attribute
40-
will trigger an error if the function does not override a method from the
41-
parent class or from an interface; that error does not get delayed.
36+
can now include in the bitmask the new flag
37+
ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION. The flag indicates that
38+
validation is being performed at runtime and that other than returning an
39+
error message, the validator should not do anything, most notably including
40+
modification of any class properties.
4241
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
4342

4443
- Hash

Zend/zend_attributes.c

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,10 @@ uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_ent
7070
return ZEND_ATTRIBUTE_TARGET_ALL;
7171
}
7272

73-
static void validate_allow_dynamic_properties(
73+
static zend_string *validate_allow_dynamic_properties(
7474
zend_attribute *attr, uint32_t target, zend_class_entry *scope)
7575
{
76-
if (scope == NULL) {
77-
/* Only reachable when validator is run but the attribute isn't applied
78-
* to a class; in the case of delayed target validation reflection will
79-
* complain about the target before running the validator. */
80-
ZEND_ASSERT(target & ZEND_ATTRIBUTE_NO_TARGET_VALIDATION);
81-
return;
82-
}
76+
ZEND_ASSERT(scope != NULL);
8377
const char *msg = NULL;
8478
if (scope->ce_flags & ZEND_ACC_TRAIT) {
8579
msg = "Cannot apply #[\\AllowDynamicProperties] to trait %s";
@@ -91,27 +85,20 @@ static void validate_allow_dynamic_properties(
9185
msg = "Cannot apply #[\\AllowDynamicProperties] to enum %s";
9286
}
9387
if (msg != NULL) {
94-
if (target & ZEND_ATTRIBUTE_NO_TARGET_VALIDATION) {
95-
return;
96-
}
97-
if (target & ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION) {
98-
// Should not have passed the first time
99-
ZEND_ASSERT((scope->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES) == 0);
100-
// Throw a catchable error at runtime
101-
zend_throw_error(NULL, msg, ZSTR_VAL(scope->name));
102-
return;
103-
}
104-
zend_error_noreturn(E_ERROR, msg, ZSTR_VAL(scope->name) );
88+
smart_str str = {0};
89+
smart_str_append_printf(&str, msg, ZSTR_VAL(scope->name));
90+
return smart_str_extract(&str);
10591
}
10692
if (target & ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION) {
10793
// Should have passed the first time
10894
ZEND_ASSERT((scope->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES) != 0);
109-
return;
95+
return NULL;
11096
}
11197
scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES;
98+
return NULL;
11299
}
113100

114-
static void validate_attribute(
101+
static zend_string *validate_attribute(
115102
zend_attribute *attr, uint32_t target, zend_class_entry *scope)
116103
{
117104
const char *msg = NULL;
@@ -125,15 +112,11 @@ static void validate_attribute(
125112
msg = "Cannot apply #[\\Attribute] to abstract class %s";
126113
}
127114
if (msg != NULL) {
128-
if (target & ZEND_ATTRIBUTE_NO_TARGET_VALIDATION) {
129-
return;
130-
}
131-
if (target & ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION) {
132-
zend_throw_error(NULL, msg, ZSTR_VAL(scope->name));
133-
return;
134-
}
135-
zend_error_noreturn(E_ERROR, msg, ZSTR_VAL(scope->name));
115+
smart_str str = {0};
116+
smart_str_append_printf(&str, msg, ZSTR_VAL(scope->name));
117+
return smart_str_extract(&str);
136118
}
119+
return NULL;
137120
}
138121

139122
ZEND_METHOD(Attribute, __construct)
@@ -235,7 +218,7 @@ ZEND_METHOD(Deprecated, __construct)
235218
}
236219
}
237220

238-
static void validate_nodiscard(
221+
static zend_string *validate_nodiscard(
239222
zend_attribute *attr, uint32_t target, zend_class_entry *scope)
240223
{
241224
/* There isn't an easy way to identify the *method* that the attribute is
@@ -247,16 +230,14 @@ static void validate_nodiscard(
247230
const zend_string *prop_info_name = CG(context).active_property_info_name;
248231
if (prop_info_name == NULL) {
249232
op_array->fn_flags |= ZEND_ACC_NODISCARD;
250-
return;
233+
return NULL;
251234
}
252235
// Applied to a hook, either throw or ignore
253-
if (target & ZEND_ATTRIBUTE_NO_TARGET_VALIDATION) {
254-
return;
255-
}
256-
zend_error_noreturn(E_COMPILE_ERROR, "#[\\NoDiscard] is not supported for property hooks");
236+
return ZSTR_INIT_LITERAL("#[\\NoDiscard] is not supported for property hooks", 0);
257237
}
258238
/* At runtime, no way to identify the target method; Reflection will handle
259239
* throwing the error if needed. */
240+
return NULL;
260241
}
261242

262243
ZEND_METHOD(NoDiscard, __construct)

Zend/zend_attributes.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,9 @@
3434
#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<7)
3535
#define ZEND_ATTRIBUTE_FLAGS ((1<<8) - 1)
3636

37-
/* Not a real flag, just passed to validators when target validation is
38-
* suppressed; must not conflict with any of the real flags above. */
39-
#define ZEND_ATTRIBUTE_NO_TARGET_VALIDATION (1<<8)
40-
4137
/* Not a real flag, just passed to validators when target validation is
4238
* being run at runtime; must not conflict with any of the real flags above. */
43-
#define ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION (1<<9)
39+
#define ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION (1<<8)
4440

4541
/* Flags for zend_attribute.flags */
4642
#define ZEND_ATTRIBUTE_PERSISTENT (1<<0)
@@ -79,7 +75,7 @@ typedef struct _zend_attribute {
7975
typedef struct _zend_internal_attribute {
8076
zend_class_entry *ce;
8177
uint32_t flags;
82-
void (*validator)(zend_attribute *attr, uint32_t target, zend_class_entry *scope);
78+
zend_string* (*validator)(zend_attribute *attr, uint32_t target, zend_class_entry *scope);
8379
} zend_internal_attribute;
8480

8581
ZEND_API zend_attribute *zend_get_attribute(HashTable *attributes, zend_string *lcname);

Zend/zend_compile.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7575,7 +7575,6 @@ static void zend_compile_attributes(
75757575
strlen("delayedtargetvalidation")
75767576
);
75777577
}
7578-
uint32_t extra_flags = delayed_target_validation ? ZEND_ATTRIBUTE_NO_TARGET_VALIDATION : 0;
75797578
/* Validate attributes in a secondary loop (needed to detect repeated attributes). */
75807579
ZEND_HASH_PACKED_FOREACH_PTR(*attributes, attr) {
75817580
if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) {
@@ -7603,7 +7602,13 @@ static void zend_compile_attributes(
76037602

76047603
// Validators are not run if the target is already invalid
76057604
if (run_validator && config->validator != NULL) {
7606-
config->validator(attr, target | extra_flags, CG(active_class_entry));
7605+
zend_string *error = config->validator(attr, target, CG(active_class_entry));
7606+
if (error != NULL) {
7607+
if (delayed_target_validation == false) {
7608+
zend_error_noreturn(E_COMPILE_ERROR, ZSTR_VAL(error));
7609+
}
7610+
zend_string_efree(error);
7611+
}
76077612
}
76087613
} ZEND_HASH_FOREACH_END();
76097614
}

ext/reflection/php_reflection.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7375,12 +7375,14 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
73757375
if (delayed_target_validation && ce->type == ZEND_INTERNAL_CLASS) {
73767376
zend_internal_attribute *config = zend_internal_attribute_get(attr->data->lcname);
73777377
if (config != NULL && config->validator != NULL) {
7378-
config->validator(
7378+
zend_string *error = config->validator(
73797379
attr->data,
73807380
attr->target | ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION,
73817381
attr->scope
73827382
);
7383-
if (EG(exception)) {
7383+
if (error != NULL) {
7384+
zend_throw_exception(zend_ce_error, ZSTR_VAL(error), 0);
7385+
zend_string_efree(error);
73847386
RETURN_THROWS();
73857387
}
73867388
}
@@ -7389,7 +7391,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
73897391
* attribute is applied to, attr->scope is just the overall class entry
73907392
* that the method is a part of. */
73917393
if (ce == zend_ce_nodiscard && attr->on_property_hook) {
7392-
zend_throw_error(NULL, "#[\\NoDiscard] is not supported for property hooks");;
7394+
zend_throw_error(NULL, "#[\\NoDiscard] is not supported for property hooks");
73937395
RETURN_THROWS();
73947396
}
73957397
}

ext/zend_test/test.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -958,11 +958,12 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze
958958
return zend_std_get_static_method(ce, name, NULL);
959959
}
960960

961-
void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
961+
zend_string *zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
962962
{
963963
if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
964-
zend_error(E_COMPILE_ERROR, "Only classes can be marked with #[ZendTestAttribute]");
964+
return ZSTR_INIT_LITERAL("Only classes can be marked with #[ZendTestAttribute]", 0);
965965
}
966+
return NULL;
966967
}
967968

968969
static ZEND_METHOD(_ZendTestClass, __toString)

0 commit comments

Comments
 (0)