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 <ilija.tovilo@me.com>
This commit is contained in:
DanielEScherzer 2024-10-23 06:04:18 -07:00 committed by GitHub
parent e64e531e3d
commit 0b94cf65e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 64 additions and 10 deletions

View file

@ -0,0 +1,19 @@
--TEST--
Abstract methods not allowed in classes that are not abstract (GH-16067)
--FILE--
<?php
// Still allowed via trait
trait TraitWithAbstract {
abstract public function foo();
}
class TraitWorks {
use TraitWithAbstract;
}
class NotAbstract {
abstract public function bar();
}
?>
--EXPECTF--
Fatal error: Class NotAbstract declares abstract method bar() and must therefore be declared abstract in %s on line %d

View file

@ -0,0 +1,11 @@
--TEST--
Compiler prevents explicit `abstract` methods on anonymous classes
--FILE--
<?php
$c = new class {
abstract public function f();
}
?>
--EXPECTF--
Fatal error: Anonymous class method f() must not be abstract in %s on line 4

View file

@ -0,0 +1,12 @@
--TEST--
Compiler prevents `abstract` methods on enums classes (GH-16067)
--FILE--
<?php
enum Example {
abstract public function foo();
}
?>
--EXPECTF--
Fatal error: Enum method Example::foo() must not be abstract in %s on line 4

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -4,7 +4,7 @@ ZE2 An interface method cannot be private
<?php
interface if_a {
abstract private function err();
private function err();
}
?>