From 2d6b28a882476e546ff1baea72d12207050384c6 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 12 Aug 2025 15:20:42 +0200 Subject: [PATCH 1/2] Use attribute validator for assigning flags --- Zend/zend_attributes.c | 54 +++++++++++++++++++++-- Zend/zend_attributes.h | 3 +- Zend/zend_compile.c | 97 +++++++++--------------------------------- Zend/zend_constants.c | 19 +++------ 4 files changed, 80 insertions(+), 93 deletions(-) diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 3256e220d8f3a..dccea97a30fef 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -70,7 +70,7 @@ uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_ent } static void validate_allow_dynamic_properties( - zend_attribute *attr, uint32_t target, zend_class_entry *scope) + zend_attribute *attr, uint32_t target_type, zend_class_entry *scope, void *target, uint32_t offset) { if (scope->ce_flags & ZEND_ACC_TRAIT) { zend_error_noreturn(E_ERROR, "Cannot apply #[\\AllowDynamicProperties] to trait %s", @@ -96,7 +96,7 @@ static void validate_allow_dynamic_properties( } static void validate_attribute( - zend_attribute *attr, uint32_t target, zend_class_entry *scope) + zend_attribute *attr, uint32_t target_type, zend_class_entry *scope, void *target, uint32_t offset) { const char *msg = NULL; if (scope->ce_flags & ZEND_ACC_TRAIT) { @@ -113,6 +113,51 @@ static void validate_attribute( } } +static void validate_override( + zend_attribute *attr, uint32_t target_type, zend_class_entry *scope, void *target, uint32_t offset) +{ + if (target_type & ZEND_ATTRIBUTE_TARGET_METHOD) { + zend_op_array *op_array = target; + op_array->fn_flags |= ZEND_ACC_OVERRIDE; + } else { + ZEND_ASSERT(target_type & ZEND_ATTRIBUTE_TARGET_PROPERTY); + zend_property_info *prop_info = target; + prop_info->flags |= ZEND_ACC_OVERRIDE; + } +} + +static void validate_deprecated( + zend_attribute *attr, uint32_t target_type, zend_class_entry *scope, void *target, uint32_t offset) +{ + if (target_type & (ZEND_ATTRIBUTE_TARGET_FUNCTION|ZEND_ATTRIBUTE_TARGET_METHOD)) { + zend_op_array *op_array = target; + op_array->fn_flags |= ZEND_ACC_DEPRECATED; + } else if (target_type & (ZEND_ATTRIBUTE_TARGET_CLASS_CONST)) { + zend_class_constant *c = target; + ZEND_CLASS_CONST_FLAGS(c) |= ZEND_ACC_DEPRECATED; + /* For deprecated constants, we need to flag the zval for recursion + * detection. Make sure the zval is separated out of shm. */ + scope->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; + scope->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + } else { + ZEND_ASSERT(target_type & ZEND_ATTRIBUTE_TARGET_CONST); + zend_constant *c = target; + ZEND_CONSTANT_SET_FLAGS( + c, + ZEND_CONSTANT_FLAGS(c) | CONST_DEPRECATED, + ZEND_CONSTANT_MODULE_NUMBER(c) + ); + } +} + +static void validate_no_discard( + zend_attribute *attr, uint32_t target_type, zend_class_entry *scope, void *target, uint32_t offset) +{ + ZEND_ASSERT(target_type & (ZEND_ATTRIBUTE_TARGET_FUNCTION|ZEND_ATTRIBUTE_TARGET_METHOD)); + zend_op_array *op_array = target; + op_array->fn_flags |= ZEND_ACC_NODISCARD; +} + ZEND_METHOD(Attribute, __construct) { zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL; @@ -560,13 +605,16 @@ void zend_register_attribute_ce(void) zend_ce_sensitive_parameter_value->default_object_handlers = &attributes_object_handlers_sensitive_parameter_value; zend_ce_override = register_class_Override(); - zend_mark_internal_attribute(zend_ce_override); + attr = zend_mark_internal_attribute(zend_ce_override); + attr->validator = validate_override; zend_ce_deprecated = register_class_Deprecated(); attr = zend_mark_internal_attribute(zend_ce_deprecated); + attr->validator = validate_deprecated; zend_ce_nodiscard = register_class_NoDiscard(); attr = zend_mark_internal_attribute(zend_ce_nodiscard); + attr->validator = validate_no_discard; } void zend_attributes_shutdown(void) diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index a4d6b28c0094a..30c1a3a76a775 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -70,7 +70,8 @@ typedef struct _zend_attribute { typedef struct _zend_internal_attribute { zend_class_entry *ce; uint32_t flags; - void (*validator)(zend_attribute *attr, uint32_t target, zend_class_entry *scope); + /* Parameter offsets start at 1, everything else uses 0. */ + void (*validator)(zend_attribute *attr, uint32_t target_type, zend_class_entry *scope, void *target, uint32_t offset); } zend_internal_attribute; ZEND_API zend_attribute *zend_get_attribute(HashTable *attributes, zend_string *lcname); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d8fb26e17768d..ec89aec68afa8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7468,7 +7468,7 @@ static bool zend_is_valid_default_value(zend_type type, zval *value) } static void zend_compile_attributes( - HashTable **attributes, zend_ast *ast, uint32_t offset, uint32_t target, uint32_t promoted + HashTable **attributes, zend_ast *ast, uint32_t offset, uint32_t target_type, uint32_t promoted, void *target ) /* {{{ */ { zend_attribute *attr; zend_internal_attribute *config; @@ -7502,7 +7502,7 @@ static void zend_compile_attributes( zend_string_release(lcname); /* Exclude internal attributes that do not match on promoted properties. */ - if (config && !(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { + if (config && !(target_type & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { if (promoted & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL)) { zend_string_release(name); continue; @@ -7560,8 +7560,8 @@ static void zend_compile_attributes( continue; } - if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { - zend_string *location = zend_get_attribute_target_names(target); + if (!(target_type & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { + zend_string *location = zend_get_attribute_target_names(target_type); zend_string *allowed = zend_get_attribute_target_names(config->flags); zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", @@ -7575,8 +7575,9 @@ static void zend_compile_attributes( } } - if (config->validator != NULL) { - config->validator(attr, target, CG(active_class_entry)); + /* target is NULL for global constants at compile-time. Validator will be called at runtime. */ + if (config->validator != NULL && target) { + config->validator(attr, target_type, CG(active_class_entry), target, offset); } } ZEND_HASH_FOREACH_END(); } @@ -7773,13 +7774,6 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 arg_info->name = zend_string_copy(name); arg_info->type = (zend_type) ZEND_TYPE_INIT_NONE(0); - if (attributes_ast) { - zend_compile_attributes( - &op_array->attributes, attributes_ast, i + 1, ZEND_ATTRIBUTE_TARGET_PARAMETER, - is_promoted ? ZEND_ATTRIBUTE_TARGET_PROPERTY : 0 - ); - } - bool forced_allow_nullable = false; if (type_ast) { uint32_t default_type = *default_ast_ptr ? Z_TYPE(default_node.u.constant) : IS_UNDEF; @@ -7835,6 +7829,13 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zval_ptr_dtor(&default_node.u.constant); } + if (attributes_ast) { + zend_compile_attributes( + &op_array->attributes, attributes_ast, i + 1, ZEND_ATTRIBUTE_TARGET_PARAMETER, + is_promoted ? ZEND_ATTRIBUTE_TARGET_PROPERTY : 0, op_array + ); + } + opline = zend_emit_op(NULL, opcode, NULL, &default_node); SET_NODE(opline->result, &var_node); opline->op1.num = i + 1; @@ -7917,12 +7918,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 } if (attributes_ast) { zend_compile_attributes( - &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, ZEND_ATTRIBUTE_TARGET_PARAMETER); - - zend_attribute *override_attribute = zend_get_attribute_str(prop->attributes, "override", sizeof("override")-1); - if (override_attribute) { - prop->flags |= ZEND_ACC_OVERRIDE; - } + &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, ZEND_ATTRIBUTE_TARGET_PARAMETER, prop); } } } @@ -8429,37 +8425,7 @@ static zend_op_array *zend_compile_func_decl_ex( target = ZEND_ATTRIBUTE_TARGET_METHOD; } - zend_compile_attributes(&op_array->attributes, decl->child[4], 0, target, 0); - - zend_attribute *override_attribute = zend_get_attribute_str( - op_array->attributes, - "override", - sizeof("override")-1 - ); - - if (override_attribute) { - op_array->fn_flags |= ZEND_ACC_OVERRIDE; - } - - zend_attribute *deprecated_attribute = zend_get_attribute_str( - op_array->attributes, - "deprecated", - sizeof("deprecated")-1 - ); - - if (deprecated_attribute) { - op_array->fn_flags |= ZEND_ACC_DEPRECATED; - } - - zend_attribute *nodiscard_attribute = zend_get_attribute_str( - op_array->attributes, - "nodiscard", - sizeof("nodiscard")-1 - ); - - if (nodiscard_attribute) { - op_array->fn_flags |= ZEND_ACC_NODISCARD; - } + zend_compile_attributes(&op_array->attributes, decl->child[4], 0, target, 0, op_array); } /* Do not leak the class scope into free standing functions, even if they are dynamically @@ -8901,12 +8867,7 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f } if (attr_ast) { - zend_compile_attributes(&info->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, 0); - - zend_attribute *override_attribute = zend_get_attribute_str(info->attributes, "override", sizeof("override")-1); - if (override_attribute) { - info->flags |= ZEND_ACC_OVERRIDE; - } + zend_compile_attributes(&info->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, 0, info); } CG(context).active_property_info_name = old_active_property_info_name; @@ -8983,17 +8944,7 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as c = zend_declare_typed_class_constant(ce, name, &value_zv, flags, doc_comment, type); if (attr_ast) { - zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0); - - zend_attribute *deprecated = zend_get_attribute_str(c->attributes, "deprecated", sizeof("deprecated")-1); - - if (deprecated) { - ZEND_CLASS_CONST_FLAGS(c) |= ZEND_ACC_DEPRECATED; - /* For deprecated constants, we need to flag the zval for recursion - * detection. Make sure the zval is separated out of shm. */ - ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; - ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; - } + zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0, c); } } } @@ -9263,7 +9214,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) CG(active_class_entry) = ce; if (decl->child[3]) { - zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); + zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0, ce); } if (implements_ast) { @@ -9440,13 +9391,7 @@ static void zend_compile_enum_case(zend_ast *ast) zend_ast *attr_ast = ast->child[3]; if (attr_ast) { - zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0); - - zend_attribute *deprecated = zend_get_attribute_str(c->attributes, "deprecated", sizeof("deprecated")-1); - - if (deprecated) { - ZEND_CLASS_CONST_FLAGS(c) |= ZEND_ACC_DEPRECATED; - } + zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0, c); } } @@ -9660,7 +9605,7 @@ static void zend_compile_const_decl(zend_ast *ast) /* {{{ */ } HashTable *attributes = NULL; - zend_compile_attributes(&attributes, list->child[1], 0, ZEND_ATTRIBUTE_TARGET_CONST, 0); + zend_compile_attributes(&attributes, list->child[1], 0, ZEND_ATTRIBUTE_TARGET_CONST, 0, NULL); ZEND_ASSERT(last_op != NULL); last_op->opcode = ZEND_DECLARE_ATTRIBUTED_CONST; diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index cdab1bfced40b..0a6fcc663c6fc 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -561,17 +561,10 @@ void zend_constant_add_attributes(zend_constant *c, HashTable *attributes) { GC_TRY_ADDREF(attributes); c->attributes = attributes; - zend_attribute *deprecated_attribute = zend_get_attribute_str( - c->attributes, - "deprecated", - strlen("deprecated") - ); - - if (deprecated_attribute) { - ZEND_CONSTANT_SET_FLAGS( - c, - ZEND_CONSTANT_FLAGS(c) | CONST_DEPRECATED, - ZEND_CONSTANT_MODULE_NUMBER(c) - ); - } + ZEND_HASH_PACKED_FOREACH_PTR(attributes, zend_attribute *attr) { + zend_internal_attribute *config = zend_internal_attribute_get(attr->lcname); + if (config && config->validator != NULL) { + config->validator(attr, ZEND_ATTRIBUTE_TARGET_CONST, CG(active_class_entry), c, 0); + } + } ZEND_HASH_FOREACH_END(); } From 49b4e45d7fa0864707e00c51b771a8ecc2b6ae0c Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 12 Aug 2025 19:43:51 +0200 Subject: [PATCH 2/2] Fix zend_test --- ext/zend_test/test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 255b0a7f99443..228728921a0d5 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -951,9 +951,9 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze return zend_std_get_static_method(ce, name, NULL); } -void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope) +void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target_type, zend_class_entry *scope, void *target, uint32_t offset) { - if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { + if (target_type != ZEND_ATTRIBUTE_TARGET_CLASS) { zend_error(E_COMPILE_ERROR, "Only classes can be marked with #[ZendTestAttribute]"); } }