Commit 1cb8a2fd42b for php
commit 1cb8a2fd42bba377f774089f75e4681c70eef0c6
Author: ChrisAbner <chrisabner@hotmail.com>
Date: Tue Nov 10 21:53:06 2026 -0500
Fix tracing JIT reading the int result of ++/-- on a reference as a double
After zend_jit_inc_dec(), the double case dropped the speculative guard
of the result when !(res_info & MAY_BE_STRING), which is always true
there. For a reference the trace does not know the type of the value,
so the result can be an int that the next ASSIGN then reads as a double.
Check op1_info, like the long case does.
Close GH-25383
diff --git a/NEWS b/NEWS
index 18ec48a2acf..82567c5abf3 100644
--- a/NEWS
+++ b/NEWS
@@ -64,6 +64,8 @@ PHP NEWS
. Fixed use-after-free in JIT when the error handler releases a string while
an "Undefined variable" warning is raised for its offset in ?? or nested
isset()/empty(). (Jorg Sowa)
+ . Fixed tracing JIT reading the int result of ++/-- on a reference as a
+ double. (ChrisAbner)
- PDO_Firebird:
. Fixed stack buffer overflow with long connection user names, passwords,
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c
index 9c54767fc22..2b9852aa614 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -4493,7 +4493,7 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
} else if (opline->result_type != IS_UNUSED
&& (res_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_DOUBLE|MAY_BE_GUARD)
- && !(res_info & MAY_BE_STRING)) {
+ && !(op1_info & MAY_BE_STRING)) {
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
}
goto done;
diff --git a/ext/opcache/tests/jit/inc_025.phpt b/ext/opcache/tests/jit/inc_025.phpt
new file mode 100644
index 00000000000..c6dceb7ea8e
--- /dev/null
+++ b/ext/opcache/tests/jit/inc_025.phpt
@@ -0,0 +1,43 @@
+--TEST--
+JIT INC: 025 (result type of ++/-- on a reference after the trace saw a float)
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.protect_memory=1
+opcache.jit=tracing
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+function pre_inc($a) { $v = $a; $x = &$v; $out = []; for ($i = 0; $i < 4; $i++) { $out[] = ++$x; } return $out; }
+function post_inc($a) { $v = $a; $x = &$v; $out = []; for ($i = 0; $i < 4; $i++) { $out[] = $x++; } return $out; }
+function pre_dec($a) { $v = $a; $x = &$v; $out = []; for ($i = 0; $i < 4; $i++) { $out[] = --$x; } return $out; }
+function glob_inc($a) { global $g; $g = $a; $out = []; for ($i = 0; $i < 4; $i++) { $out[] = ++$g; } return $out; }
+function stat_inc($a) { static $s; $s = $a; $out = []; for ($i = 0; $i < 4; $i++) { $out[] = ++$s; } return $out; }
+
+class P { public float|string $t = ''; }
+function typed_ref_inc($a) {
+ $o = new P; $o->t = $a; $x = &$o->t; $out = [];
+ for ($i = 0; $i < 4; $i++) { $out[] = $x++; }
+ return $out;
+}
+
+foreach (['pre_inc', 'post_inc', 'pre_dec', 'glob_inc', 'stat_inc'] as $f) {
+ for ($n = 0; $n < 20; $n++) {
+ $f(1.5);
+ }
+ echo $f, ' ', json_encode($f(5)), "\n";
+}
+for ($n = 0; $n < 20; $n++) {
+ typed_ref_inc(1.5);
+}
+echo 'typed_ref_inc ', json_encode(typed_ref_inc("2.5")), "\n";
+?>
+--EXPECT--
+pre_inc [6,7,8,9]
+post_inc [5,6,7,8]
+pre_dec [4,3,2,1]
+glob_inc [6,7,8,9]
+stat_inc [6,7,8,9]
+typed_ref_inc ["2.5",3.5,4.5,5.5]