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
60 changes: 59 additions & 1 deletion lib/JIT.php
Original file line number Diff line number Diff line change
Expand Up @@ -10038,7 +10038,11 @@ private function compileBlockInternal(
$classId,
$nameOp->value
);
$fetched = $this->context->type->object->staticPropertyFetch($classId, $nameOp->value);
$fetched = $this->context->type->object->staticPropertyFetch(
$classId,
$nameOp->value,
$forWrite
);
if ($forWrite) {
$fetched->staticPropertyHookClassLc = strtolower(ltrim($className, '\\'));
$fetched->objectPropertyName = $nameOp->value;
Expand Down Expand Up @@ -16903,6 +16907,34 @@ function () use ($result, $value): void {
Variable::TYPE_NATIVE_DOUBLE,
], true)
) {
// Class static property lvalues must store via module globals + init flag,
// not a promoted stack slot (#20877, #31965).
if (null !== $result->staticPropertyGlobal && null !== $result->staticPropertyType) {
if (
!JIT\AsymmetricVisibilityGuard::emitBeforeStaticPropertyStore(
$this->context,
$this,
$result,
$this->context->jitEnclosingBlock
)
&& !JIT\PropertyHookDispatch::emitStaticSetHookIfNeeded(
$this->context,
$result,
$value,
$this->context->jitEnclosingBlock,
$this
)
) {
$this->context->type->object->staticPropertyStore(
$result->staticPropertyGlobal,
$value,
$result->staticPropertyType,
$result->staticPropertyInitGlobal
);
}

return;
}
$llvmFunc = $this->context->builder->getInsertBlock()->getParent();
$slot = JIT\BasicBlockHelper::entryAllocaForFunction(
$this->context,
Expand Down Expand Up @@ -17012,6 +17044,32 @@ function () use ($result, $value): void {
$value->type === $result->type
&& !($branchMergeTarget && Variable::TYPE_VALUE === $result->type)
) {
if (null !== $result->staticPropertyGlobal && null !== $result->staticPropertyType) {
if (
!JIT\AsymmetricVisibilityGuard::emitBeforeStaticPropertyStore(
$this->context,
$this,
$result,
$this->context->jitEnclosingBlock
)
&& !JIT\PropertyHookDispatch::emitStaticSetHookIfNeeded(
$this->context,
$result,
$value,
$this->context->jitEnclosingBlock,
$this
)
) {
$this->context->type->object->staticPropertyStore(
$result->staticPropertyGlobal,
$value,
$result->staticPropertyType,
$result->staticPropertyInitGlobal
);
}

return;
}
if (!$result->includeBinding) {
$result->free();
}
Expand Down
9 changes: 7 additions & 2 deletions lib/JIT/Builtin/Type/ObjectStaticPropertyLlvm.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private static function returnAfterPendingError(Context $context, \PHPLLVM\Value
$context->builder->returnVoid();
}

public static function fetch(Object_ $object, int $classId, string $name): Variable
public static function fetch(Object_ $object, int $classId, string $name, bool $forWrite = false): Variable
{
$entry = $object->staticPropertyGlobalEntry($classId, $name);
if (null === $entry) {
Expand All @@ -129,7 +129,12 @@ public static function fetch(Object_ $object, int $classId, string $name): Varia
);
}
$context = $object->jitContext();
if (!empty($entry['typedWithoutDefault']) && null !== ($entry['initGlobal'] ?? null)) {
// BP_VAR_W assign skips uninitialized typed-static read guard (Zend zend_execute.c; #31965).
if (
!$forWrite
&& !empty($entry['typedWithoutDefault'])
&& null !== ($entry['initGlobal'] ?? null)
) {
$declName = $object->classNameForId($classId);
$meta = $object->staticPropertyVisibilityMeta($classId, $name);
if (null !== $meta) {
Expand Down
4 changes: 2 additions & 2 deletions lib/JIT/Builtin/Type/Object_.php
Original file line number Diff line number Diff line change
Expand Up @@ -6840,9 +6840,9 @@ public function instancePropertyVisibilityMeta(int $classId, string $name): ?arr
return null;
}

public function staticPropertyFetch(int $classId, string $name): Variable
public function staticPropertyFetch(int $classId, string $name, bool $forWrite = false): Variable
{
return ObjectStaticPropertyLlvm::fetch($this, $classId, $name);
return ObjectStaticPropertyLlvm::fetch($this, $classId, $name, $forWrite);
}

/**
Expand Down
22 changes: 22 additions & 0 deletions test/fixtures/aot/cases/static_prop_closure_typed_write.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
AOT: typed static property write from closure marks init (#31965)
--FILE--
<?php
class C31965AotTyped
{
public static int $x;

public static function run(): void
{
$f = function (): void {
self::$x = 12;
};
$f();
echo self::$x, "\n";
}
}

C31965AotTyped::run();
?>
--EXPECT--
12
22 changes: 22 additions & 0 deletions test/fixtures/aot/cases/static_prop_closure_write.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
AOT: static property write/read through closure is stable (#31965)
--FILE--
<?php
class C31965Aot
{
public static $x;

public static function run(): void
{
$f = static function (): void {
self::$x = 12;
};
$f();
echo self::$x, "\n";
}
}

C31965Aot::run();
?>
--EXPECT--
12
18 changes: 18 additions & 0 deletions test/repro/issue_31965_static_prop_closure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
// Issue #31965 — static property write/read through closure (AOT uninitialised memory)

class C31965
{
public static $x;

public static function run(): void
{
$f = static function (): void {
self::$x = 12;
};
$f();
echo self::$x, "\n";
}
}

C31965::run();
18 changes: 18 additions & 0 deletions test/repro/issue_31965_typed_static_closure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
// Issue #31965 — typed static property write from closure (AOT init flag)

class C31965Typed
{
public static int $x;

public static function run(): void
{
$f = function (): void {
self::$x = 12;
};
$f();
echo self::$x, "\n";
}
}

C31965Typed::run();
Loading