Commit f04eaec41ac for php.net
commit f04eaec41ac88ea75e51592e91aa3f7c65939d21
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Mon Jul 6 15:26:00 2026 -0400
Fix GH-22549: GMP compound pow/shift assign asserts on negative operand (#22582)
shift_operator_helper() wrote ZVAL_UNDEF(return_value) on the out-of-range
exponent/shift path, but for a compound assignment return_value aliases op1,
so pow_function's fallback then reached the scalar conversion with op1 set to
IS_UNDEF and hit ZEND_UNREACHABLE(). Drop the stray undef; the helper's other
error paths already return FAILURE without touching return_value, leaving the
operand intact for the thrown ValueError to propagate.
Fixes GH-22549
diff --git a/NEWS b/NEWS
index 11b47065438..2a266243a74 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,10 @@ PHP NEWS
. Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"
warning when an IFD is not followed by a next-IFD offset). (Eyüp Can Akman)
+- GMP:
+ . Fixed bug GH-22549 (Assertion failure / UB on a compound GMP power or shift
+ assignment with a negative exponent). (iliaal)
+
- Intl:
. Fixed NumberFormatter::parse() and NumberFormatter::parseCurrency() to
reject offset values outside the 32-bit range instead of silently
diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c
index ef06d0fda30..59209c4e1be 100644
--- a/ext/gmp/gmp.c
+++ b/ext/gmp/gmp.c
@@ -361,7 +361,6 @@ static zend_result shift_operator_helper(gmp_binary_ui_op_t op, zval *return_val
zend_ce_value_error, "%s must be between 0 and %lu",
opcode == ZEND_POW ? "Exponent" : "Shift", ULONG_MAX
);
- ZVAL_UNDEF(return_value);
return FAILURE;
} else {
mpz_ptr gmpnum_op, gmpnum_result;
diff --git a/ext/gmp/tests/gh22549.phpt b/ext/gmp/tests/gh22549.phpt
new file mode 100644
index 00000000000..82a66901bd0
--- /dev/null
+++ b/ext/gmp/tests/gh22549.phpt
@@ -0,0 +1,55 @@
+--TEST--
+GH-22549 (Assertion failure in _zendi_try_convert_scalar_to_number with compound GMP shift/pow)
+--EXTENSIONS--
+gmp
+--FILE--
+<?php
+// Compound assignment aliases result and op1. A negative GMP shift/exponent
+// must throw a ValueError, not clobber op1 into IS_UNDEF and abort.
+$cases = [
+ 'int ** gmp' => [2, fn(&$x) => $x **= gmp_init(-5)],
+ 'gmp ** gmp' => [gmp_init(2), fn(&$x) => $x **= gmp_init(-5)],
+ 'array ** gmp' => [[0], fn(&$x) => $x **= gmp_init(-5)],
+ 'int << gmp' => [8, fn(&$x) => $x <<= gmp_init(-2)],
+ 'int >> gmp' => [8, fn(&$x) => $x >>= gmp_init(-2)],
+ 'gmp << gmp' => [gmp_init(8), fn(&$x) => $x <<= gmp_init(-2)],
+];
+
+foreach ($cases as $label => [$x, $op]) {
+ try {
+ $op($x);
+ echo "$label: no exception\n";
+ } catch (\Throwable $e) {
+ echo "$label: ", $e::class, ": ", $e->getMessage(), "\n";
+ }
+ var_dump($x);
+}
+
+// Valid operations must still succeed (guard against an over-broad regression).
+$p = 2; $p **= gmp_init(3); echo "valid pow: ", gmp_strval($p), "\n";
+$s = 1; $s <<= gmp_init(4); echo "valid shift: ", gmp_strval($s), "\n";
+?>
+--EXPECTF--
+int ** gmp: ValueError: Exponent must be between 0 and %d
+int(2)
+gmp ** gmp: ValueError: Exponent must be between 0 and %d
+object(GMP)#%d (1) {
+ ["num"]=>
+ string(1) "2"
+}
+array ** gmp: ValueError: Exponent must be between 0 and %d
+array(1) {
+ [0]=>
+ int(0)
+}
+int << gmp: ValueError: Shift must be between 0 and %d
+int(8)
+int >> gmp: ValueError: Shift must be between 0 and %d
+int(8)
+gmp << gmp: ValueError: Shift must be between 0 and %d
+object(GMP)#%d (1) {
+ ["num"]=>
+ string(1) "8"
+}
+valid pow: 8
+valid shift: 16