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
36 changes: 34 additions & 2 deletions lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -16945,9 +16945,16 @@ protected function inheritFromParent(ClassEntry $entry): void
$exists = true;
break;
}
if (!$exists) {
$entry->properties[] = $property;
if ($exists) {
// Child redeclared a non-private parent final property (#22988, Zend/zend_inheritance.c).
// Same-script compile is covered by FinalPropertyOverrideCheck; cross-eval needs
// this runtime path (see final class const #22329).
if (!$isPrivate && $property->propertyFinal) {
$this->rejectChildOverrideOfFinalProperty($entry, $property);
}
continue;
}
$entry->properties[] = $property;
}
}

Expand Down Expand Up @@ -18742,6 +18749,31 @@ private function rejectChildOverrideOfFinalClassConst(
));
}

/**
* php-src zend_inheritance.c — "Cannot override final property %s::$%s" (#22988, #22339).
*/
private function rejectChildOverrideOfFinalProperty(
ClassEntry $entry,
VM\ClassProperty $parentProperty
): void {
if (!$parentProperty->propertyFinal) {
return;
}
$declaringLc = $parentProperty->declaringClassLc;
$ownerDisplay = $declaringLc !== '' ? $declaringLc : $entry->parentLc;
if (is_string($ownerDisplay) && isset($this->context->classes[$ownerDisplay])) {
$ownerDisplay = $this->context->classes[$ownerDisplay]->name;
}
if (!is_string($ownerDisplay) || '' === $ownerDisplay) {
$ownerDisplay = 'parent';
}
throw new \CompileError(sprintf(
'Cannot override final property %s::$%s',
$ownerDisplay,
$parentProperty->name
));
}

/**
* Class body constant after trait use must not redefine an inherited trait constant
* with an incompatible value (Zend/zend_traits.c zend_traits_compile_role_constants, #7012).
Expand Down
4 changes: 4 additions & 0 deletions test/compliance/JITTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,10 @@ public static function providePHPTests(): \Generator
if (str_contains($name, 'final_class_const_eval_override')) {
continue;
}
// eval() final plain property override: VM inheritFromParent (#22988); MCJIT inline eval deferral pending.
if (str_contains($name, 'final_plain_property_eval_override')) {
continue;
}
// preserve_keys=true: VM + JIT/AOT via ArrayBuiltinHelper (#3524).
// array_merge_recursive(): VM + JIT via ArrayMergeRecursiveJitHelper PHP (#3297, #10183).
if (str_contains(strtolower($case[0]), 'array_merge_recursive')) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Language: eval() cannot override final plain property (#22988, Zend/zend_inheritance.c)
--ENV--
PHP_COMPILER_PROFILE=8.4
--FILE--
<?php
class A {
final public 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 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
8 changes: 8 additions & 0 deletions test/repro/issue_22988_final_plain_property_eval_override.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
// Issue #22988 — separate eval must not override final plain property (Zend/zend_inheritance.c).
class A {
final public string $x = 'a';
}
eval('class B extends A { public string $x = "b"; }');
echo "EVAL_OK\n";
echo (new B)->x, "\n";
19 changes: 19 additions & 0 deletions test/unit/FinalPropertyOverrideCheckTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ class C {
self::assertSame('ok', ob_get_clean());
}

/** @covers issue #22988 — cross-eval must hit inheritFromParent, not only same-script FinalPropertyOverrideCheck */
public function testEvalCannotOverrideFinalPlainProperty(): void
{
$runtime = new Runtime();
$code = <<<'PHP'
<?php
class A {
final public string $x = 'a';
}
eval('class B extends A { public string $x = "b"; }');
echo "EVAL_OK\n";
PHP;
$block = $runtime->parseAndCompile($code, 'final_plain_eval_override.php');
$this->assertNotNull($block);
$this->expectException(\CompileError::class);
$this->expectExceptionMessage('Cannot override final property A::$x');
$runtime->run($block);
}

/** @covers issue #22474 */
public function testChildCannotOverrideFinalSetHook(): void
{
Expand Down