From 0914a115ebaa15f6adf6c3e948affa72c9be006d Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 19 Jan 2023 21:49:19 +0100 Subject: [PATCH] Fix GH-10377: Unable to have an anonymous readonly class This fixes the oversight that an anonymous class should be able to be readonly. Other identifiers such as final and abstract do not make sense. As we still want nice errors for when users try to use these modifiers, or use multiple modifiers, we introduce a new function zend_add_anonymous_class_modifier that will perform verification for anonymous class modifiers, just like zend_add_class_modifier does for non-anonymous classes. --- Zend/tests/gh10377.phpt | 42 +++++++++++++++++++++++++++++++++++++ Zend/tests/gh10377_1.phpt | 15 +++++++++++++ Zend/tests/gh10377_2.phpt | 10 +++++++++ Zend/tests/gh10377_3.phpt | 10 +++++++++ Zend/tests/gh10377_4.phpt | 10 +++++++++ Zend/zend_compile.c | 19 +++++++++++++++++ Zend/zend_compile.h | 1 + Zend/zend_language_parser.y | 22 ++++++++++++++----- 8 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/gh10377.phpt create mode 100644 Zend/tests/gh10377_1.phpt create mode 100644 Zend/tests/gh10377_2.phpt create mode 100644 Zend/tests/gh10377_3.phpt create mode 100644 Zend/tests/gh10377_4.phpt 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); } ;