Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

ReflectionProperty::skipLazyInitialization() copies invalid constant defaults with OPcache

オープン
#23,842 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
78/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
php
領域
backend

調査の方向性

Start in ext/reflection/php_reflection.c at ReflectionProperty::skipLazyInitialization(), then review the supplied PHPT regression test and the CE_DEFAULT_PROPERTIES_TABLE() handling described in the issue. Run the regression test with OPcache enabled; done means the class-constant default produces string(3) "EUR" and the related reflection and lazy-object tests remain passing.

索引モデルが issue の本文から書いたものです。

説明

Bug Status: Needs Triage
Description

AI assistance disclosure: this report was prepared with an LLM. The reproduction commands, source build, candidate patch, and test results below were executed and checked.

ReflectionProperty::skipLazyInitialization() gives a typed property an invalid value when its default refers to a class constant and shared-memory OPcache is enabled. This reproduces without Doctrine, Composer, autoloading, a database, or application code.

Reproduced on PHP 8.4.21 and on a clean build of the official PHP 8.4.25 release archive (Linux aarch64, NTS).

Minimal reproduction

Save as repro.php (run from a file, not php -r):

<?php
class Currency {
    public const string EUR = 'EUR';
}
class Product {
    public string $currency = Currency::EUR;
    public function getCurrency(): string { return $this->currency; }
}
$reflection = new ReflectionClass(Product::class);
$product = $reflection->newLazyGhost(static function (Product $product): void {});
$reflection->getProperty('currency')->skipLazyInitialization($product);
var_dump($product->getCurrency());

With OPcache loaded, run:

php -d opcache.enable=1 -d opcache.enable_cli=1 \
    -d opcache.file_cache_only=0 -d opcache.file_cache= \
    -d opcache.preload= -d opcache.file_update_protection=0 \
    -d opcache.optimization_level=0 \
    -d opcache.jit=off -d opcache.jit_buffer_size=0 repro.php

Expected:

string(3) "EUR"

Actual (both tested versions):

Fatal error: Uncaught TypeError: Product::getCurrency(): Return value must be of type string, int|float returned

Removing -d opcache.optimization_level=0 produces UNKNOWN:0 from var_dump() instead, with exit status 0, on both versions. JIT is disabled in both cases. No disk cache is required to reproduce the defect.

Controls

On unmodified PHP 8.4.25, disabling CLI OPcache or enabling disk-only OPcache with a valid writable cache directory returns the expected string(3) "EUR".

On PHP 8.4.21, replacing Currency::EUR with the literal 'EUR' also passes with shared-memory OPcache. So does replacing skipLazyInitialization() with:

$property = $reflection->getProperty('currency');
$property->setRawValueWithoutLazyInitialization($product, $property->getDefaultValue());
Candidate fix and validation

In ext/reflection/php_reflection.c, skipLazyInitialization() reads ce->default_properties_table directly. CE_DEFAULT_PROPERTIES_TABLE() accounts for the resolved defaults held in the class's mutable data under OPcache. The following one-line change fixes the reproduction with both the disabled and default optimizer in the local PHP 8.4.25 build:

--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -6461,7 +6461,7 @@
 		RETURN_THROWS();
 	}
 
-	zval *src = &object->ce->default_properties_table[OBJ_PROP_TO_NUM(prop->offset)];
+	zval *src = &CE_DEFAULT_PROPERTIES_TABLE(object->ce)[OBJ_PROP_TO_NUM(prop->offset)];
 	zval *dst = OBJ_PROP(object, prop->offset);
 
 	if (!(Z_PROP_FLAG_P(dst) & IS_PROP_LAZY)) {

The PHPT below fails before this change and passes afterward. Running it together with Zend/tests/lazy_objects and ext/reflection/tests on the patched build produced 729 passed, 14 skipped, 0 failed, 0 warned (743 tests total). This is local validation on PHP 8.4.25/aarch64; other architectures and PHP 8.5 were not tested.

PHPT regression test
--TEST--
skipLazyInitialization() preserves a class-constant property default with OPcache
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache_only=0
opcache.file_cache=
opcache.optimization_level=0
opcache.file_update_protection=0
opcache.jit_buffer_size=0
--FILE--
<?php
class Currency {
    public const string EUR = 'EUR';
}
class Product {
    public string $currency = Currency::EUR;
    public function getCurrency(): string { return $this->currency; }
}
$reflection = new ReflectionClass(Product::class);
$product = $reflection->newLazyGhost(static function (Product $product): void {});
$reflection->getProperty('currency')->skipLazyInitialization($product);
var_dump($product->getCurrency());
--EXPECT--
string(3) "EUR"
PHP Version
PHP 8.4.25 (cli) (built: Sep 22 2026 10:44:45) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.25, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.25, Copyright (c), by Zend Technologies

Also reproduced on PHP 8.4.21 (NTS), Zend OPcache v8.4.21.
Operating System

Alpine Linux 3.23.4, Linux aarch64 (Docker)

主要言語
C
スター
40.4k
フォーク
8.2k
平均マージ
2日 17時間
マージ済み PR(30日)
115

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

php/php-src のほかの issue

php/php-src の issue をすべて見る

似ている issue

C の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。