Tracing JIT loses the result of a dynamic method call traced with Closure::__invoke (8.5 regression)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
The payload points at the tracing JIT path around zend_execute in zend_vm_execute.h and the call return handling in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER, with the valgrind traces naming zend_vm_stack_new_page as where the never-written memory comes from. Start by reproducing with the opcache.jit=tracing command in the issue, then compare against opcache.jit=function. Done would mean the result slot is written before it is read and the output is bool(false), with valgrind clean outside the JIT compiler.
索引モデルが issue の本文から書いたものです。
説明
Description
With the tracing JIT, the result of a dynamic method call
$o->{$m}()is lost when the call site was first traced while callingClosure::__invoke. The caller then reads whatever is left in the result slot.
The following code:
<?php
class A { function m() { return false; } }
function call($o, $m) { return $o->{$m}(); }
function every($items, $m) {
foreach ($items as $i) { if (!call($i, $m)) return false; }
return true;
}
$c = function () { return false; };
for ($i = 0; $i < 2000; $i++) every([$c], '__invoke');
var_dump(call(new A, 'm'));
php -n -d opcache.enable_cli=1 -d opcache.jit_buffer_size=64M -d opcache.jit=tracing test.php
Resulted in this output:
UNKNOWN:0
But I expected this output instead:
bool(false)
- Wrong on 8.5.0, 8.5.11 and PHP-8.5 @ bbfdd69f, with
opcache.jit=tracingand with1054.- Correct with
opcache.jit=function, with the JIT off, and on 8.4.26 and 8.3.35 (tracing).- Not fixed by #23611 (GH-23572).
- The warm-up through
Closure::__invokeis required: warming upcall()with a plain object's method does not reproduce it. The earlyreturn falseinevery()is also required.
Valgrind on PHP-8.5 @ bbfdd69f (
-g -O2,USE_ZEND_ALLOC=0 valgrind --smc-check=all --track-origins=yes). Leaving out their_sparse_set_inreports from the JIT compiler, every remaining error is about the same never-written VM stack memory: it is used in JIT code, printed byvar_dump(), then destroyed:
Conditional jump or move depends on uninitialised value(s)
at 0x11800ADF: ???
by 0x8C4E9F: zend_execute (zend_vm_execute.h:122008)
by 0x9270D5: zend_execute_script (zend.c:1985)
Uninitialised value was created by a heap allocation
by 0x88CAE8: zend_vm_stack_new_page (zend_execute.h:327)
by 0x88CAE8: zend_vm_stack_init (zend_execute.c:195)
Conditional jump or move depends on uninitialised value(s)
at 0x7668EE: php_var_dump (var.c:130)
by 0x7672E0: zif_var_dump (var.c:257)
by 0x8C0273: ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER (zend_vm_execute.h:1360)
Uninitialised value was created by a heap allocation
by 0x88CAE8: zend_vm_stack_new_page (zend_execute.h:327)
by 0x88CAE8: zend_vm_stack_init (zend_execute.c:195)
Conditional jump or move depends on uninitialised value(s)
at 0x8C02AE: zval_ptr_dtor_nogc (zend_variables.h:35)
by 0x8C02AE: zend_vm_stack_free_args (zend_execute.h:405)
by 0x8C02AE: ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER (zend_vm_execute.h:1379)
Uninitialised value was created by a heap allocation
by 0x88CAE8: zend_vm_stack_new_page (zend_execute.h:327)
by 0x88CAE8: zend_vm_stack_init (zend_execute.c:195)
Real-world impact: Laravel's
HigherOrderCollectionProxyclosure has this shape. Telescope calls it with->every->__invoke($entry)for its filters, and then with->partition->isException()when storing entries. On a freshlaravel/laravel13.35 +laravel/telescope5.25 install (officialphp:8.5.11-cliimage, default JIT settings apart fromopcache.jit=tracing), an artisan command that logs, uses the cache and runs queries in a loop crashes withzend_mm_heap corruptedor SIGSEGV withAPP_ENV=production. It crashed in 28 of 32 runs from 250 iterations up. It does not crash with function JIT, with the JIT off, or on 8.4.26.
PHP Version
PHP 8.5.11 (cli) (built: Oct 6 2026 01:22:31) (NTS)
Copyright (c) The PHP Group
Built by https://github.com/docker-library/php
Zend Engine v4.5.11, Copyright (c) Zend Technologies
with Zend OPcache v8.5.11, Copyright (c), by Zend Technologies
Operating System
Debian 13 (official Docker image), x86_64
- 主要言語
- C
- スター
- 40.7k
- フォーク
- 8.3k
- 平均マージ
- 2日 16時間
- マージ済み PR(30日)
- 151
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
php/php-src のほかの issue
-
Bug Status: Needs Triage
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
zend_set_local_var() overwrites existing compiled variable (CV) zval without calling zval_ptr_dtor()オープンBug Status: Needs Triage
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
Case-insensitive substr_compare, ksort, krsort are still locale-dependent対応中かも @lazerg が 3 日前に担当しました。 オープンBug Extension: standard Status: Verified
難易度 2/5 1〜3時間 初心者へのやさしさ 73/100
php/php-src#24552 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
master: part of the mbstring bailout fix got lost in a merge対応中かも @lazerg が 3 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
-
priority:medium type:chore
難易度 1/5 1〜3時間 初心者へのやさしさ 84/100
crazy-goat/php-fpm-ng#1041 ·
メンテナーはふだん 1 日以内に返信
-
Test failure on FreeBSD: #26 unexpected system_clock source対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンlow priority rt_lib tests
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
メンテナーはふだん 1 日以内に返信
-
good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100