From 0b94cf65e4b34286c468c9bd79f089979c5cf461 Mon Sep 17 00:00:00 2001 From: DanielEScherzer Date: Wed, 23 Oct 2024 06:04:18 -0700 Subject: [PATCH] GH-16067: prevent invalid `abstract` during compilation of methods (GH-16069) For classes that are not declared `abstract`, produce a compiler error for any `abstract` methods. For anonymous classes, since they cannot be made abstract, the error message is slightly different. Co-authored-by: Ilija Tovilo --- Zend/tests/abstract_implicit.phpt | 19 +++++++++++++++++++ Zend/tests/anon/gh16067.phpt | 11 +++++++++++ Zend/tests/enum/no-abstract.phpt | 12 ++++++++++++ Zend/tests/errmsg/errmsg_018.phpt | 2 +- Zend/zend_compile.c | 20 ++++++++++++++++---- tests/classes/abstract_derived.phpt | 2 +- tests/classes/abstract_not_declared.phpt | 2 +- tests/classes/abstract_redeclare.phpt | 2 +- tests/classes/abstract_static.phpt | 2 +- tests/classes/interface_method_private.phpt | 2 +- 10 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 Zend/tests/abstract_implicit.phpt create mode 100644 Zend/tests/anon/gh16067.phpt create mode 100644 Zend/tests/enum/no-abstract.phpt diff --git a/Zend/tests/abstract_implicit.phpt b/Zend/tests/abstract_implicit.phpt new file mode 100644 index 00000000000..1ccd4d3653f --- /dev/null +++ b/Zend/tests/abstract_implicit.phpt @@ -0,0 +1,19 @@ +--TEST-- +Abstract methods not allowed in classes that are not abstract (GH-16067) +--FILE-- + +--EXPECTF-- +Fatal error: Class NotAbstract declares abstract method bar() and must therefore be declared abstract in %s on line %d diff --git a/Zend/tests/anon/gh16067.phpt b/Zend/tests/anon/gh16067.phpt new file mode 100644 index 00000000000..dc09d14964a --- /dev/null +++ b/Zend/tests/anon/gh16067.phpt @@ -0,0 +1,11 @@ +--TEST-- +Compiler prevents explicit `abstract` methods on anonymous classes +--FILE-- + +--EXPECTF-- +Fatal error: Anonymous class method f() must not be abstract in %s on line 4 diff --git a/Zend/tests/enum/no-abstract.phpt b/Zend/tests/enum/no-abstract.phpt new file mode 100644 index 00000000000..2593d67e44d --- /dev/null +++ b/Zend/tests/enum/no-abstract.phpt @@ -0,0 +1,12 @@ +--TEST-- +Compiler prevents `abstract` methods on enums classes (GH-16067) +--FILE-- + +--EXPECTF-- +Fatal error: Enum method Example::foo() must not be abstract in %s on line 4 diff --git a/Zend/tests/errmsg/errmsg_018.phpt b/Zend/tests/errmsg/errmsg_018.phpt index 070247e4d41..e94f99446ce 100644 --- a/Zend/tests/errmsg/errmsg_018.phpt +++ b/Zend/tests/errmsg/errmsg_018.phpt @@ -10,4 +10,4 @@ class test { echo "Done\n"; ?> --EXPECTF-- -Fatal error: Class test contains 1 abstract method and must therefore be declared abstract or implement the remaining method (test::foo) in %s on line %d +Fatal error: Class test declares abstract method foo() and must therefore be declared abstract in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 146af9a22df..a0222b4cfd8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8067,6 +8067,22 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string zend_error(E_COMPILE_WARNING, "Private methods cannot be final as they are never overridden by other classes"); } + if ((fn_flags & ZEND_ACC_ABSTRACT) + && !(ce->ce_flags & (ZEND_ACC_EXPLICIT_ABSTRACT_CLASS|ZEND_ACC_TRAIT))) { + // Don't say that the class should be declared abstract if it is + // anonymous or an enum and can't be abstract + if (ce->ce_flags & ZEND_ACC_ANON_CLASS) { + zend_error_noreturn(E_COMPILE_ERROR, "Anonymous class method %s() must not be abstract", + ZSTR_VAL(name)); + } else if (ce->ce_flags & (ZEND_ACC_ENUM|ZEND_ACC_INTERFACE)) { + zend_error_noreturn(E_COMPILE_ERROR, "%s method %s::%s() must not be abstract", + zend_get_object_type_case(ce, true), ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } else { + zend_error_noreturn(E_COMPILE_ERROR, "Class %s declares abstract method %s() and must therefore be declared abstract", + ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } + } + if (in_interface) { if (!(fn_flags & ZEND_ACC_PUBLIC)) { zend_error_noreturn(E_COMPILE_ERROR, "Access type for interface method " @@ -8076,10 +8092,6 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string zend_error_noreturn(E_COMPILE_ERROR, "Interface method " "%s::%s() must not be final", ZSTR_VAL(ce->name), ZSTR_VAL(name)); } - if (fn_flags & ZEND_ACC_ABSTRACT) { - zend_error_noreturn(E_COMPILE_ERROR, "Interface method " - "%s::%s() must not be abstract", ZSTR_VAL(ce->name), ZSTR_VAL(name)); - } op_array->fn_flags |= ZEND_ACC_ABSTRACT; } diff --git a/tests/classes/abstract_derived.phpt b/tests/classes/abstract_derived.phpt index 11c9125a772..7d0ffff5ca0 100644 --- a/tests/classes/abstract_derived.phpt +++ b/tests/classes/abstract_derived.phpt @@ -13,4 +13,4 @@ class derived extends base { ?> ===DONE=== --EXPECTF-- -Fatal error: Class derived contains 1 abstract method and must therefore be declared abstract or implement the remaining method (derived::show) in %sabstract_derived.php on line %d +Fatal error: Class derived declares abstract method show() and must therefore be declared abstract in %sabstract_derived.php on line %d diff --git a/tests/classes/abstract_not_declared.phpt b/tests/classes/abstract_not_declared.phpt index 8f899f0e718..c9ba306a110 100644 --- a/tests/classes/abstract_not_declared.phpt +++ b/tests/classes/abstract_not_declared.phpt @@ -10,4 +10,4 @@ class fail { echo "Done\n"; // shouldn't be displayed ?> --EXPECTF-- -Fatal error: Class fail contains 1 abstract method and must therefore be declared abstract or implement the remaining method (fail::show) in %s on line %d +Fatal error: Class fail declares abstract method show() and must therefore be declared abstract in %s on line %d diff --git a/tests/classes/abstract_redeclare.phpt b/tests/classes/abstract_redeclare.phpt index f8e3d234652..eb73da571ca 100644 --- a/tests/classes/abstract_redeclare.phpt +++ b/tests/classes/abstract_redeclare.phpt @@ -16,4 +16,4 @@ class fail extends pass { echo "Done\n"; // Shouldn't be displayed ?> --EXPECTF-- -Fatal error: Class fail contains 1 abstract method and must therefore be declared abstract or implement the remaining method (fail::show) in %sabstract_redeclare.php on line %d +Fatal error: Class fail declares abstract method show() and must therefore be declared abstract in %sabstract_redeclare.php on line %d diff --git a/tests/classes/abstract_static.phpt b/tests/classes/abstract_static.phpt index ab13289ab4c..15db2f9a03b 100644 --- a/tests/classes/abstract_static.phpt +++ b/tests/classes/abstract_static.phpt @@ -31,4 +31,4 @@ echo "Done\n"; // shouldn't be displayed --EXPECTF-- Call to function show() -Fatal error: Class fail contains 1 abstract method and must therefore be declared abstract or implement the remaining method (fail::func) in %sabstract_static.php(%d) : eval()'d code on line %d +Fatal error: Class fail declares abstract method func() and must therefore be declared abstract in %sabstract_static.php(%d) : eval()'d code on line %d diff --git a/tests/classes/interface_method_private.phpt b/tests/classes/interface_method_private.phpt index c7a0852ea4a..d32e3dbdc8e 100644 --- a/tests/classes/interface_method_private.phpt +++ b/tests/classes/interface_method_private.phpt @@ -4,7 +4,7 @@ ZE2 An interface method cannot be private