Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 63 additions & 23 deletions lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -17594,30 +17594,41 @@ protected function inheritFromParent(ClassEntry $entry): void
$entry->methodNames[$name] = $parent->methodNames[$name] ?? $name;
}
foreach ($parent->staticProperties as $name => $storage) {
if (!isset($entry->staticProperties[$name])) {
// Inherited statics share parent storage (class-declared #4668; trait-composed #4670).
$entry->staticProperties[$name] = $storage;
if (isset($parent->traitStaticPropertyNames[$name])) {
$entry->traitStaticPropertyNames[$name] = true;
}
if (isset($parent->staticPropertyVisibility[$name])) {
$entry->staticPropertyVisibility[$name] = $parent->staticPropertyVisibility[$name];
}
if (isset($parent->staticPropertySetVisibility[$name])) {
$entry->staticPropertySetVisibility[$name] = $parent->staticPropertySetVisibility[$name];
}
if (isset($parent->staticPropertyGetVisibility[$name])) {
$entry->staticPropertyGetVisibility[$name] = $parent->staticPropertyGetVisibility[$name];
}
if (isset($parent->staticPropertyAsymmetricExplicitRead[$name])) {
$entry->staticPropertyAsymmetricExplicitRead[$name] = $parent->staticPropertyAsymmetricExplicitRead[$name];
}
if (isset($parent->staticPropertyDeclaringClassLc[$name])) {
$entry->staticPropertyDeclaringClassLc[$name] = $parent->staticPropertyDeclaringClassLc[$name];
}
if (isset($parent->staticPropertyFinal[$name])) {
$entry->staticPropertyFinal[$name] = true;
if (isset($entry->staticProperties[$name])) {
// Child redeclared a parent final static — same-script compile is covered by
// FinalPropertyOverrideCheck; cross-eval needs this runtime path (#24992, #22988).
// php-src zend_inheritance.c — "Cannot override final property %s::$%s".
$vis = $parent->staticPropertyVisibility[$name] ?? \PHPCfg\Func::FLAG_PUBLIC;
if (($vis & \PHPCfg\Func::FLAG_PRIVATE) === 0
&& !empty($parent->staticPropertyFinal[$name])
) {
$this->rejectChildOverrideOfFinalStaticProperty($entry, $parent, $name);
}

continue;
}
// Inherited statics share parent storage (class-declared #4668; trait-composed #4670).
$entry->staticProperties[$name] = $storage;
if (isset($parent->traitStaticPropertyNames[$name])) {
$entry->traitStaticPropertyNames[$name] = true;
}
if (isset($parent->staticPropertyVisibility[$name])) {
$entry->staticPropertyVisibility[$name] = $parent->staticPropertyVisibility[$name];
}
if (isset($parent->staticPropertySetVisibility[$name])) {
$entry->staticPropertySetVisibility[$name] = $parent->staticPropertySetVisibility[$name];
}
if (isset($parent->staticPropertyGetVisibility[$name])) {
$entry->staticPropertyGetVisibility[$name] = $parent->staticPropertyGetVisibility[$name];
}
if (isset($parent->staticPropertyAsymmetricExplicitRead[$name])) {
$entry->staticPropertyAsymmetricExplicitRead[$name] = $parent->staticPropertyAsymmetricExplicitRead[$name];
}
if (isset($parent->staticPropertyDeclaringClassLc[$name])) {
$entry->staticPropertyDeclaringClassLc[$name] = $parent->staticPropertyDeclaringClassLc[$name];
}
if (isset($parent->staticPropertyFinal[$name])) {
$entry->staticPropertyFinal[$name] = true;
}
}
foreach ($parent->staticPropertyHooks as $name => $hooks) {
Expand Down Expand Up @@ -19624,6 +19635,35 @@ private function rejectChildOverrideOfFinalProperty(
));
}

/**
* php-src zend_inheritance.c — final static property override (#24992, #23403).
* Mirror of {@see rejectChildOverrideOfFinalProperty()} for ClassEntry::$staticPropertyFinal.
*/
private function rejectChildOverrideOfFinalStaticProperty(
ClassEntry $entry,
ClassEntry $parent,
string $propLc
): void {
$declaringLc = $parent->staticPropertyDeclaringClassLc[$propLc]
?? strtolower(ltrim($parent->name, '\\'));
$ownerDisplay = $declaringLc;
if (isset($this->context->classes[$ownerDisplay])) {
$ownerDisplay = $this->context->classes[$ownerDisplay]->name;
}
if ('' === $ownerDisplay) {
$ownerDisplay = $parent->name !== '' ? $parent->name : 'parent';
}
$storage = $parent->staticProperties[$propLc] ?? null;
$propDisplay = ($storage instanceof Variable && null !== $storage->objectPropertyName)
? $storage->objectPropertyName
: $propLc;
throw new \CompileError(sprintf(
'Cannot override final property %s::$%s',
$ownerDisplay,
$propDisplay
));
}

/**
* php-src zend_inheritance.c — property type invariance (#23505).
* Same-script compile is covered by TypedPropertyInheritCheck; cross-eval needs this path.
Expand Down
4 changes: 4 additions & 0 deletions test/compliance/JITTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,10 @@ public static function providePHPTests(): \Generator
if (str_contains($name, 'final_plain_property_eval_override')) {
continue;
}
// eval() final static property override: VM inheritFromParent (#24992); MCJIT inline eval deferral pending.
if (str_contains($name, 'final_static_property_eval_override')) {
continue;
}
// eval() final method override: VM inheritFromParent (#24884); MCJIT inline eval deferral pending.
if (str_contains($name, 'final_method_eval_override')) {
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Language: eval cannot override final static property under PROFILE=8.4 (#24992, Zend/zend_inheritance.c)
--ENV--
PHP_COMPILER_PROFILE=8.4
--FILE--
<?php
class A {
public final static string $x = 'a';
}
// Guard isFinal without emitting stdout before the inherit fatal (BaseTest #7468).
if (!(new ReflectionProperty('A', 'x'))->isFinal()) {
echo "not-final\n";
}
eval('class B extends A { public static string $x = "b"; }');
echo "EVAL_OK\n";
--EXPECTF--
PHP Fatal error: Cannot override final property A::$x in %s : eval()'d code on line %d
--EXPECT_EXIT--
255
18 changes: 18 additions & 0 deletions test/repro/issue_24992_final_static_property_eval_override.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* Issue #24992 — final static property override via eval under PROFILE=8.4.
*
* Same-script compile is covered by FinalPropertyOverrideCheck; cross-eval
* must hit VM inheritFromParent (Zend/zend_inheritance.c).
*
* PHP_COMPILER_PROFILE=8.4 php bin/vm.php test/repro/issue_24992_final_static_property_eval_override.php
* # expect isFinal=1 then Fatal: Cannot override final property A::$x
* # never: override_ok
*/
class A
{
public final static string $x = 'a';
}
echo 'isFinal=', (new ReflectionProperty('A', 'x'))->isFinal() ? '1' : '0', "\n";
eval('class B extends A { public static string $x = "b"; }');
echo "override_ok\n";
22 changes: 22 additions & 0 deletions test/repro/maintainer_gap_final_plain_property.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
/**
* Issue #24992 (re-#24686) — final plain properties on the default reference
* profile must compile-fatal like Zend 8.2 (Zend/zend_compile.c).
*
* php bin/vm.php test/repro/maintainer_gap_final_plain_property.php
* # expect exit 255 + Cannot declare property A::$x final...
* # never: compiled / override_ok / write=
*/
class A
{
public final string $x = 'a';
}
echo "compiled\n";
class B extends A
{
public string $x = 'b';
}
echo "override_ok\n";
$o = new A();
$o->x = 'z';
echo 'write=', $o->x, "\n";
18 changes: 18 additions & 0 deletions test/repro/maintainer_gap_final_property_profile84.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* Issue #24992 — PROFILE=8.4 final plain property Reflection + child override.
*
* php-src-strict (Zend 8.4 / Zend/zend_compile.c + zend_inheritance.c +
* ext/reflection/php_reflection.c):
* - ReflectionProperty::isFinal() true
* - eval child redeclaration is compile Fatal (not override_ok)
*
* PHP_COMPILER_PROFILE=8.4 php bin/vm.php test/repro/maintainer_gap_final_property_profile84.php
*/
class A
{
public final string $x = 'a';
}
echo 'isFinal=', (new ReflectionProperty('A', 'x'))->isFinal() ? '1' : '0', "\n";
eval('class B extends A { public string $x = "b"; }');
echo "override_ok\n";
Loading