diff --git a/Zend/tests/gh10377.phpt b/Zend/tests/gh10377.phpt new file mode 100644 index 0000000000000..c9e902ae739da --- /dev/null +++ b/Zend/tests/gh10377.phpt @@ -0,0 +1,42 @@ +--TEST-- +GH-10377 (Unable to have an anonymous readonly class) +--FILE-- +field = 2; + } +}; + +$anon = new class { + public int $field; + function __construct() { + $this->field = 2; + } +}; + +var_dump($readonly_anon->field); +try { + $readonly_anon->field = 123; +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} +var_dump($readonly_anon->field); + +var_dump($anon->field); +try { + $anon->field = 123; +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} +var_dump($anon->field); + +?> +--EXPECT-- +int(2) +Cannot modify readonly property class@anonymous::$field +int(2) +int(2) +int(123) diff --git a/Zend/tests/gh10377_1.phpt b/Zend/tests/gh10377_1.phpt new file mode 100644 index 0000000000000..ac421cb8a5171 --- /dev/null +++ b/Zend/tests/gh10377_1.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-10377 (Unable to have an anonymous readonly class) - usage variation: dynamic properties attribute +--FILE-- +field = 2; + } +}; + +?> +--EXPECTF-- +Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class class@anonymous in %s on line %d diff --git a/Zend/tests/gh10377_2.phpt b/Zend/tests/gh10377_2.phpt new file mode 100644 index 0000000000000..e2e5d4cf3d8d0 --- /dev/null +++ b/Zend/tests/gh10377_2.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-10377 (Unable to have an anonymous readonly class) - usage variation: abstract modifier +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the abstract modifier on an anonymous class in %s on line %d diff --git a/Zend/tests/gh10377_3.phpt b/Zend/tests/gh10377_3.phpt new file mode 100644 index 0000000000000..d777eed5809c5 --- /dev/null +++ b/Zend/tests/gh10377_3.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-10377 (Unable to have an anonymous readonly class) - usage variation: final modifier +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the final modifier on an anonymous class in %s on line %d diff --git a/Zend/tests/gh10377_4.phpt b/Zend/tests/gh10377_4.phpt new file mode 100644 index 0000000000000..730b15b6c53f5 --- /dev/null +++ b/Zend/tests/gh10377_4.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-10377 (Unable to have an anonymous readonly class) - usage variation: multiple readonly modifiers +--FILE-- + +--EXPECTF-- +Fatal error: Multiple readonly modifiers are not allowed in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c2d53bd943ec7..631d16eddd6c4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -903,6 +903,25 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ } /* }}} */ +uint32_t zend_add_anonymous_class_modifier(uint32_t flags, uint32_t new_flag) +{ + uint32_t new_flags = flags | new_flag; + if (new_flag & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) { + zend_throw_exception(zend_ce_compile_error, + "Cannot use the abstract modifier on an anonymous class", 0); + return 0; + } + if (new_flag & ZEND_ACC_FINAL) { + zend_throw_exception(zend_ce_compile_error, "Cannot use the final modifier on an anonymous class", 0); + return 0; + } + if ((flags & ZEND_ACC_READONLY_CLASS) && (new_flag & ZEND_ACC_READONLY_CLASS)) { + zend_throw_exception(zend_ce_compile_error, "Multiple readonly modifiers are not allowed", 0); + return 0; + } + return new_flags; +} + uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifier_target target) /* {{{ */ { uint32_t new_flags = flags | new_flag; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 97b11cdb3034b..0149366578ee0 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -817,6 +817,7 @@ typedef enum { zend_ast *zend_ast_append_str(zend_ast *left, zend_ast *right); zend_ast *zend_negate_num_string(zend_ast *ast); uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag); +uint32_t zend_add_anonymous_class_modifier(uint32_t flags, uint32_t new_flag); uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifier_target target); uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t flags); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index b62dce7237c71..fe16f93a1ab1f 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -282,7 +282,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type returns_ref function fn is_reference is_variadic property_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers -%type class_modifiers class_modifier use_type backup_fn_flags +%type class_modifiers class_modifier anonymous_class_modifiers anonymous_class_modifiers_optional use_type backup_fn_flags %type backup_lex_pos %type backup_doc_comment @@ -601,6 +601,18 @@ class_modifiers: { $$ = zend_add_class_modifier($1, $2); if (!$$) { YYERROR; } } ; +anonymous_class_modifiers: + class_modifier + { $$ = zend_add_anonymous_class_modifier(0, $1); if (!$$) { YYERROR; } } + | anonymous_class_modifiers class_modifier + { $$ = zend_add_anonymous_class_modifier($1, $2); if (!$$) { YYERROR; } } +; + +anonymous_class_modifiers_optional: + %empty { $$ = 0; } + | anonymous_class_modifiers { $$ = $1; } +; + class_modifier: T_ABSTRACT { $$ = ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; } | T_FINAL { $$ = ZEND_ACC_FINAL; } @@ -1095,12 +1107,12 @@ non_empty_for_exprs: ; anonymous_class: - T_CLASS { $$ = CG(zend_lineno); } ctor_arguments + anonymous_class_modifiers_optional T_CLASS { $$ = CG(zend_lineno); } ctor_arguments extends_from implements_list backup_doc_comment '{' class_statement_list '}' { zend_ast *decl = zend_ast_create_decl( - ZEND_AST_CLASS, ZEND_ACC_ANON_CLASS, $2, $6, NULL, - $4, $5, $8, NULL, NULL); - $$ = zend_ast_create(ZEND_AST_NEW, decl, $3); + ZEND_AST_CLASS, ZEND_ACC_ANON_CLASS | $1, $3, $7, NULL, + $5, $6, $9, NULL, NULL); + $$ = zend_ast_create(ZEND_AST_NEW, decl, $4); } ;