fix(preprocessor): reject abstract method bodies and abstract private class methods (#62) --skip-tests

* fix(preprocessor): reject abstract method bodies and abstract private class methods

Two abstract-method rules Zend enforces at compile time were missing:

- an abstract method with a body was accepted and the body silently
  dropped; Zend fatals with "Abstract function A::f() cannot contain
  body" (applies to classes and traits alike, probed on 8.4.13)
- `abstract private function` in a class can never be implemented,
  since private methods do not participate in overriding; Zend fatals
  with "Abstract function A::f() cannot be declared private". Traits
  keep accepting it (allowed since PHP 8.0: the consuming class
  supplies the private implementation)

Zend reports the private-modifier error before the body error when both
apply; the checks are ordered to match.

* test(preprocessor): cover abstract method declaration rules
master
Alessio Giacobbe 9 hours ago committed by GitHub
parent f03c0b12c6
commit 58c3bb64b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      phpunit/code/abstract_rule_body.php
  2. 4
      phpunit/code/abstract_rule_private.php
  3. 19
      phpunit/code/abstract_rule_private_trait_valid.php
  4. 23
      phpunit/src/AbstractMethodDeclarationTest.php
  5. 11
      src/Preprocessor.php

@ -0,0 +1,4 @@
<?php
abstract class Job { abstract public function run(): void {} }
function main() {}

@ -0,0 +1,4 @@
<?php
abstract class Job { abstract private function run(): void; }
function main() {}

@ -0,0 +1,19 @@
<?php
trait JobTrait
{
abstract private function run(): void;
public function go(): void
{
$this->run();
}
}
class Job
{
use JobTrait;
private function run(): void {}
}
function main() {}

@ -0,0 +1,23 @@
<?php
/**
* Zend abstract-method declaration rules: an abstract method may not
* carry a body, and abstract private is only legal inside traits.
*/
class AbstractMethodDeclarationTest extends BaseTest
{
public function testAbstractMethodCannotContainBody(): void
{
$this->exec('Abstract function `Job::run()` cannot contain body', 'abstract_rule_body.php');
}
public function testAbstractClassMethodCannotBePrivate(): void
{
$this->exec('Abstract function `Job::run()` cannot be declared private', 'abstract_rule_private.php');
}
public function testAbstractPrivateTraitMethodIsAllowed(): void
{
$this->compile('abstract_rule_private_trait_valid.php');
}
}

@ -2099,6 +2099,17 @@ class Preprocessor extends CompilerBase
if ($this->classDef->hasMethod($name) || $this->classDef->hasAbstractMethod($name)) {
$this->fatalError($v, "Duplicate method `{$this->method}`");
}
// A private method cannot be overridden, so an abstract private
// method could never be implemented. Traits are exempt since PHP
// 8.0: the consuming class provides the private implementation.
if (!$class instanceof Node\Stmt\Trait_ && ($flags & Modifiers::PRIVATE)) {
$this->fatalError($v, "Abstract function `{$this->class}::{$name}()` cannot be declared private");
}
// An abstract method declares a signature only; Zend rejects a body
// instead of silently discarding it.
if ($v->stmts !== null) {
$this->fatalError($v, "Abstract function `{$this->class}::{$name}()` cannot contain body");
}
if (!$class instanceof Node\Stmt\Trait_ && isset($class->flags) && !($class->flags & Modifiers::ABSTRACT)) {
$this->fatalError($v, "Non-abstract class {$this->class} contains abstract method {$v->name}");
}

Loading…
Cancel
Save