Commit 0b8612d06df for php
commit 0b8612d06df9faafd060666889ff4cc7ce15e7ec
Author: Jorg Sowa <jorg.sowa@gmail.com>
Date: Sun Nov 8 16:54:02 2026 +0100
Optimizer: null array keys may throw (GH-24654)
Null keys emit a deprecation since PHP 8.5, which an error handler can
turn into an exception. Add MAY_BE_NULL to MAY_BE_THROWING_KEY.
Co-authored-by: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Fixes GH-25269
Closes GH-24654
diff --git a/NEWS b/NEWS
index e1b2dad5fcd..2ce93fa3739 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,8 @@ PHP NEWS
(Ilia Alshanetsky)
. Fixed bug GH-25195 (smm_shared_globals left dangling without an
SHM backend). (Lazizbek Ergashev)
+ . Fixed bug GH-25269 (JIT: misaligned zval passed to
+ ZEND_ADD_ARRAY_ELEMENT handler). (Jorg Sowa, ndossche)
- SOAP:
. Fixed bug GH-25267 (NULL dereference in to_xml_any() with integer key).
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
index 9aa6b2a46a7..2f14ef8e288 100644
--- a/Zend/Optimizer/zend_inference.c
+++ b/Zend/Optimizer/zend_inference.c
@@ -4935,7 +4935,7 @@ ZEND_API zend_result zend_ssa_inference(zend_arena **arena, const zend_op_array
/* }}} */
/* Key types whose conversion to an array offset may emit a diagnostic */
-#define MAY_BE_THROWING_KEY (MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)
+#define MAY_BE_THROWING_KEY (MAY_BE_NULL|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)
ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, const zend_ssa *ssa, uint32_t t1, uint32_t t2)
{
@@ -5273,7 +5273,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
case ZEND_INIT_ARRAY:
return (opline->op2_type != IS_UNUSED) && (t2 & MAY_BE_THROWING_KEY);
case ZEND_ADD_ARRAY_ELEMENT:
- return (opline->op2_type == IS_UNUSED) || (t2 & (MAY_BE_NULL|MAY_BE_THROWING_KEY));
+ return (opline->op2_type == IS_UNUSED) || (t2 & MAY_BE_THROWING_KEY);
case ZEND_STRLEN:
return (t1 & MAY_BE_ANY) != MAY_BE_STRING;
case ZEND_COUNT:
diff --git a/ext/opcache/tests/jit/add_array_element_null_key.phpt b/ext/opcache/tests/jit/add_array_element_null_key.phpt
new file mode 100644
index 00000000000..3b535d93fc6
--- /dev/null
+++ b/ext/opcache/tests/jit/add_array_element_null_key.phpt
@@ -0,0 +1,27 @@
+--TEST--
+JIT ADD_ARRAY_ELEMENT: null key conversion may throw
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=function
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ var_dump([0, $k => 1]);
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/jit/array_key_exists_null_key.phpt b/ext/opcache/tests/jit/array_key_exists_null_key.phpt
new file mode 100644
index 00000000000..4f0b2f25e6a
--- /dev/null
+++ b/ext/opcache/tests/jit/array_key_exists_null_key.phpt
@@ -0,0 +1,27 @@
+--TEST--
+JIT ARRAY_KEY_EXISTS: null key conversion may throw
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=function
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ var_dump(array_key_exists($k, []));
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as the key parameter for array_key_exists() is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/jit/assign_dim_null_key.phpt b/ext/opcache/tests/jit/assign_dim_null_key.phpt
new file mode 100644
index 00000000000..bb255df6a6c
--- /dev/null
+++ b/ext/opcache/tests/jit/assign_dim_null_key.phpt
@@ -0,0 +1,28 @@
+--TEST--
+JIT ASSIGN_DIM: null key conversion may throw
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=function
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ $a = [];
+ var_dump($a[$k] = 1);
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/jit/fetch_dim_is_null_key.phpt b/ext/opcache/tests/jit/fetch_dim_is_null_key.phpt
new file mode 100644
index 00000000000..2a09da67e17
--- /dev/null
+++ b/ext/opcache/tests/jit/fetch_dim_is_null_key.phpt
@@ -0,0 +1,28 @@
+--TEST--
+JIT FETCH_DIM_IS: null key conversion may throw
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=function
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ $a = [];
+ var_dump($a[$k] ?? 0);
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/jit/fetch_dim_w_null_key.phpt b/ext/opcache/tests/jit/fetch_dim_w_null_key.phpt
new file mode 100644
index 00000000000..3ec27c5893a
--- /dev/null
+++ b/ext/opcache/tests/jit/fetch_dim_w_null_key.phpt
@@ -0,0 +1,37 @@
+--TEST--
+JIT FETCH_DIM_W: null key conversion may throw
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=function
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+class C {
+ function __destruct() {
+ echo "destruct\n";
+ }
+}
+
+function g($x, &$y) {}
+
+function test(null $k) {
+ $c = new C;
+ g($c, $a[$k]);
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+destruct
+Exception: Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/jit/gh25269.phpt b/ext/opcache/tests/jit/gh25269.phpt
new file mode 100644
index 00000000000..9a260491945
--- /dev/null
+++ b/ext/opcache/tests/jit/gh25269.phpt
@@ -0,0 +1,27 @@
+--TEST--
+GH-25269 (JIT: misaligned zval passed to ZEND_ADD_ARRAY_ELEMENT handler)
+--CREDITS--
+YuanchengJiang
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit_buffer_size=64M
+opcache.jit=function
+--FILE--
+<?php
+set_error_handler(function($errno, $errstr) {
+ throw new ErrorException($errstr);
+});
+function test() {
+ return [null => 1, "a" => 2, "b" => 3];
+}
+try {
+ test();
+} catch (ErrorException $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/jit/isset_isempty_dim_obj_null_key.phpt b/ext/opcache/tests/jit/isset_isempty_dim_obj_null_key.phpt
new file mode 100644
index 00000000000..2b84e5a4ad6
--- /dev/null
+++ b/ext/opcache/tests/jit/isset_isempty_dim_obj_null_key.phpt
@@ -0,0 +1,28 @@
+--TEST--
+JIT ISSET_ISEMPTY_DIM_OBJ: null key conversion may throw
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=function
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ $a = [];
+ var_dump(isset($a[$k]));
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/opt/dce_023.phpt b/ext/opcache/tests/opt/dce_023.phpt
new file mode 100644
index 00000000000..ffb4f3ba846
--- /dev/null
+++ b/ext/opcache/tests/opt/dce_023.phpt
@@ -0,0 +1,27 @@
+--TEST--
+DCE must not remove ASSIGN_DIM with a null key
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ $a[$k] = 1;
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/opt/dce_024.phpt b/ext/opcache/tests/opt/dce_024.phpt
new file mode 100644
index 00000000000..5db1400c995
--- /dev/null
+++ b/ext/opcache/tests/opt/dce_024.phpt
@@ -0,0 +1,27 @@
+--TEST--
+DCE must not remove INIT_ARRAY with a null key
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ $a = [$k => 1];
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/opt/dce_025.phpt b/ext/opcache/tests/opt/dce_025.phpt
new file mode 100644
index 00000000000..16d31607644
--- /dev/null
+++ b/ext/opcache/tests/opt/dce_025.phpt
@@ -0,0 +1,27 @@
+--TEST--
+DCE must not remove ARRAY_KEY_EXISTS with a null key
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ array_key_exists($k, []);
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as the key parameter for array_key_exists() is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/opt/dce_026.phpt b/ext/opcache/tests/opt/dce_026.phpt
new file mode 100644
index 00000000000..b582063f7b2
--- /dev/null
+++ b/ext/opcache/tests/opt/dce_026.phpt
@@ -0,0 +1,28 @@
+--TEST--
+DCE must not remove ISSET_ISEMPTY_DIM_OBJ with a null key
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ $a = [];
+ isset($a[$k]);
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as an array offset is deprecated, use an empty string instead
diff --git a/ext/opcache/tests/opt/dce_027.phpt b/ext/opcache/tests/opt/dce_027.phpt
new file mode 100644
index 00000000000..a9613621540
--- /dev/null
+++ b/ext/opcache/tests/opt/dce_027.phpt
@@ -0,0 +1,28 @@
+--TEST--
+DCE must not remove FETCH_DIM_IS with a null key
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.file_update_protection=0
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr) {
+ throw new Exception($errstr);
+});
+
+function test(null $k) {
+ $a = [];
+ $a[$k] ?? 0;
+}
+
+try {
+ test(null);
+} catch (Exception $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Exception: Using null as an array offset is deprecated, use an empty string instead