Skip to content

Regression: final class constants can be overridden by child class (re-#4455, Zend/zend_inheritance.c) #22329

Closed

Description

@PurHur

Category

bug · php-src-strict · language · final class constants

Problem

PHP 8.1+ final const on a class must refuse child redeclaration. After #4455, parse accepts final const, but inheritance still allows override — Zend fatals at compile/eval time.

Verified 2026-07-22 on af276f464 (host PHP 8.2.32 Zend vs php bin/vm.php):

Repro Zend 8.2 VM
class A { final public const X = 'a'; } eval('class B extends A { public const X = \"b\"; }') Fatal: B::X cannot override final constant A::X override allowed, B::X=b

Same under PHP_COMPILER_PROFILE=8.4.

php-src reference

PHP implementation target

  • lib/Compiler.php / inheritance checks — reject child const when parent const is final (compile-time)
  • lib/VM.php class-entry merge — same guard if constants register at runtime/eval
  • Prefer PHP-in-PHP; no new runtime/*.c

Repro

./script/docker-exec.sh -- bash -lc 'php bin/vm.php -r '"'"'class A { final public const X = \"a\"; } eval(\"class B extends A { public const X = \\"b\\"; }\"); echo B::X, \"\n\";'"'"''

Done when

  • Child redeclaration of final const fatals like Zend (cannot override final constant)
  • Works for eval and normal compile; ReflectionClassConstant::isFinal() stays true
  • Compliance .phpt under test/compliance/cases/; php-src-strict; no php-compiler-strict shortcut

Activity

  1. added
    bugSomething isn't working
    implementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claim
    on Jul 22, 2026
  2. PurHur commented on Jul 22, 2026

    @PurHur
    OwnerAuthor

    claim: Worker lane A — starting this run

    Pre-triage gates (this workspace):

    • bootstrap-inventory --check: doc drift on ext/openssl/VmOpenssl.php method count (not inventory count failure)
    • release-readiness --json: still running
    • Focusing Pillar 4 language regression: final class const override vs Zend
  3. PurHur commented on Jul 22, 2026

    @PurHur
    OwnerAuthor

    Merged via #22342

    Verification: eval('class B extends A { public const X = "b"; }') after final public const X now fatals with B::X cannot override final constant A::X (exit 255). ReflectionClassConstant::isFinal() remains true. Guard lives in VM inheritFromParent / inheritFromInterfaces (rejectChildOverrideOfFinalClassConst). Compliance: final_class_const_eval_override.phpt; unit: FinalClassConstCheckTest::testEvalCannotOverrideFinalClassConstant.

    Pre-triage: release-readiness --json → user_release_ready: yes; AOT smoke red on master independently (not this change).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:compilerCompiler / CFG / JITbugSomething isn't workingimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-2:languagePhase 2 – language features

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions