Commit dcce3459c6e for php.net

commit dcce3459c6ef79af600d5c1c06cd4d73206250af
Author: Weilin Du <weilindu@php.net>
Date:   Fri Aug 21 00:05:00 2026 +0800

    ext/gd: Add ZPP specifier for array|float parameters (#23384)

    Follow-up #23356.
    Fixed imageaffinematrixget() to enforce the documented array|float type
    for the $options parameter.

diff --git a/NEWS b/NEWS
index 1569f2e064d..78265622e7d 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,8 @@ PHP                                                                        NEWS
 - GD:
   . Fixed imageaffinematrixget() and imageaffinematrixconcat() reporting the
     wrong argument in error messages. (Weilin Du)
+  . Fixed imageaffinematrixget() to enforce the documented array|float type
+    for the $options parameter. (Weilin Du)

 - Intl:
   . Fixed grapheme_strrev() treating UBRK_DONE as a byte index and leaving
diff --git a/UPGRADING b/UPGRADING
index a99299098f9..5054e0a4d81 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -51,6 +51,9 @@ PHP 8.6 UPGRADE NOTES
 - GD:
   . imagesetstyle(), imagefilter() and imagecrop() filter their array arguments
     types / values and raise a TypeError / ValueError accordingly.
+  . imageaffinematrixget() now enforces the documented array|float type for the
+    $options parameter, including the corresponding weak and strict typing
+    behavior.

 - GMP:
   . GMP power and shift operators now throw a ValueError when GMP right operands
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index a83b4f882fe..dc2a35cc9c2 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -185,6 +185,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES
   . Added zend_ast_call_get_args() to fetch the argument node from any call
     node.
   . Added Z_PARAM_ENUM().
+  . Added PHP_GD_Z_PARAM_ARRAY_HT_OR_DOUBLE() in ext/gd to parse array|float
+    arguments into either a HashTable pointer or a double.
   . Added zend_enum_fetch_case_id().
   . Added zend_enum_get_case_by_id().
   . Added zend_bin2hex() and zend_bin2hex_str() as helper functions to remove
diff --git a/ext/gd/gd.c b/ext/gd/gd.c
index f178cea1d79..48f594663b3 100644
--- a/ext/gd/gd.c
+++ b/ext/gd/gd.c
@@ -4152,37 +4152,51 @@ PHP_FUNCTION(imageaffine)
 }
 /* }}} */

+#define PHP_GD_Z_PARAM_ARRAY_HT_OR_DOUBLE(dest_ht, dest_double) \
+	Z_PARAM_PROLOGUE(0, 0); \
+	if (EXPECTED(Z_TYPE_P(_arg) == IS_ARRAY)) { \
+		dest_ht = Z_ARRVAL_P(_arg); \
+	} else { \
+		dest_ht = NULL; \
+		if (UNEXPECTED(!zend_parse_arg_double(_arg, &dest_double, NULL, false, _i))) { \
+			zend_argument_type_error(_i, "must be of type array|float, %s given", zend_zval_value_name(_arg)); \
+			_error_code = ZPP_ERROR_FAILURE; \
+			break; \
+		} \
+	}
+
 /* {{{ Return an image containing the affine tramsformed src image, using an optional clipping area */
 PHP_FUNCTION(imageaffinematrixget)
 {
 	double affine[6];
+	double dval_option = 0.0;
 	zend_long type;
-	zval *options = NULL;
+	HashTable *options;
 	zval *tmp;
 	int res = GD_FALSE;

 	ZEND_PARSE_PARAMETERS_START(2, 2)
 		Z_PARAM_LONG(type)
-		Z_PARAM_ZVAL(options)
+		PHP_GD_Z_PARAM_ARRAY_HT_OR_DOUBLE(options, dval_option)
 	ZEND_PARSE_PARAMETERS_END();

 	switch((gdAffineStandardMatrix)type) {
 		case GD_AFFINE_TRANSLATE:
 		case GD_AFFINE_SCALE: {
 			double x, y;
-			if (Z_TYPE_P(options) != IS_ARRAY) {
+			if (options == NULL) {
 				zend_argument_type_error(2, "must be of type array when using translate or scale");
 				RETURN_THROWS();
 			}

-			if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "x", sizeof("x") - 1)) != NULL) {
+			if ((tmp = zend_hash_str_find(options, "x", sizeof("x") - 1)) != NULL) {
 				x = zval_get_double(tmp);
 			} else {
 				zend_argument_value_error(2, "must have an \"x\" key");
 				RETURN_THROWS();
 			}

-			if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "y", sizeof("y") - 1)) != NULL) {
+			if ((tmp = zend_hash_str_find(options, "y", sizeof("y") - 1)) != NULL) {
 				y = zval_get_double(tmp);
 			} else {
 				zend_argument_value_error(2, "must have a \"y\" key");
@@ -4202,7 +4216,11 @@ PHP_FUNCTION(imageaffinematrixget)
 		case GD_AFFINE_SHEAR_VERTICAL: {
 			double angle;

-			angle = zval_get_double(options);
+			if (options != NULL) {
+				zend_argument_type_error(2, "must be of type float when using rotate or shear");
+				RETURN_THROWS();
+			}
+			angle = dval_option;

 			if (type == GD_AFFINE_SHEAR_HORIZONTAL) {
 				res = gdAffineShearHorizontal(affine, angle);
diff --git a/ext/gd/gd.stub.php b/ext/gd/gd.stub.php
index 0632724b44f..6a5882c8be7 100644
--- a/ext/gd/gd.stub.php
+++ b/ext/gd/gd.stub.php
@@ -762,11 +762,10 @@ function imagescale(GdImage $image, int $width, int $height = -1, int $mode = IM
 function imageaffine(GdImage $image, array $affine, ?array $clip = null): GdImage|false {}

 /**
- * @param array|float $options
  * @refcount 1
  * @return array<int, float>|false
  */
-function imageaffinematrixget(int $type, $options): array|false {}
+function imageaffinematrixget(int $type, array|float $options): array|false {}

 /**
  * @return array<int, float>|false
diff --git a/ext/gd/gd_arginfo.h b/ext/gd/gd_arginfo.h
index 6b6327fd682..978a7744ec2 100644
Binary files a/ext/gd/gd_arginfo.h and b/ext/gd/gd_arginfo.h differ
diff --git a/ext/gd/tests/bug67248.phpt b/ext/gd/tests/bug67248.phpt
index 5f00b94fadc..9dbbca9fc68 100644
--- a/ext/gd/tests/bug67248.phpt
+++ b/ext/gd/tests/bug67248.phpt
@@ -17,55 +17,10 @@
 }
 ?>
 --EXPECTF--
-!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array when using translate or scale
-!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array when using translate or scale
-
-Warning: Object of class stdClass could not be converted to float in %s on line %d
-array(6) {
-  [0]=>
-  float(%f)
-  [1]=>
-  float(%f)
-  [2]=>
-  float(%f)
-  [3]=>
-  float(%f)
-  [4]=>
-  float(0)
-  [5]=>
-  float(0)
-}
-
-Warning: Object of class stdClass could not be converted to float in %s on line %d
-array(6) {
-  [0]=>
-  float(1)
-  [1]=>
-  float(0)
-  [2]=>
-  float(%f)
-  [3]=>
-  float(1)
-  [4]=>
-  float(0)
-  [5]=>
-  float(0)
-}
-
-Warning: Object of class stdClass could not be converted to float in %s on line %d
-array(6) {
-  [0]=>
-  float(1)
-  [1]=>
-  float(%f)
-  [2]=>
-  float(0)
-  [3]=>
-  float(1)
-  [4]=>
-  float(0)
-  [5]=>
-  float(0)
-}
-!! [ValueError] imageaffinematrixget(): Argument #1 ($type) must be a valid element type
-!! [ValueError] imageaffinematrixget(): Argument #1 ($type) must be a valid element type
+!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
+!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
+!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
+!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
+!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
+!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
+!! [TypeError] imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
diff --git a/ext/gd/tests/bug71952.phpt b/ext/gd/tests/bug71952.phpt
index fa583fc795e..cc86984e9ae 100644
--- a/ext/gd/tests/bug71952.phpt
+++ b/ext/gd/tests/bug71952.phpt
@@ -5,8 +5,13 @@
 --FILE--
 <?php
 $vals=[str_repeat("A","200"),0,1,2,3,4,5,6,7,8,9];
-imageaffinematrixget(4,$vals[0]);
+try {
+    imageaffinematrixget(4, $vals[0]);
+} catch (TypeError $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
 var_dump($vals[0]);
 ?>
 --EXPECT--
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, string given
 string(200) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
diff --git a/ext/gd/tests/imageaffinematrixget_zpp_strict_mode.phpt b/ext/gd/tests/imageaffinematrixget_zpp_strict_mode.phpt
new file mode 100644
index 00000000000..1fe66b0e516
--- /dev/null
+++ b/ext/gd/tests/imageaffinematrixget_zpp_strict_mode.phpt
@@ -0,0 +1,66 @@
+--TEST--
+imageaffinematrixget() array|float parameter coercions (strict mode)
+--EXTENSIONS--
+gd
+--FILE--
+<?php
+
+declare(strict_types=1);
+
+$values = [
+    'null' => null,
+    'false' => false,
+    'true' => true,
+    'int' => 42,
+    'float' => 73.5,
+    'numeric string' => '15',
+    'non-numeric string' => 'string',
+    'array' => [],
+    'object' => new stdClass(),
+];
+
+foreach ($values as $name => $value) {
+    echo "$name:\n";
+    try {
+        imageaffinematrixget(IMG_AFFINE_ROTATE, $value);
+        echo "accepted\n";
+    } catch (Throwable $e) {
+        echo $e::class, ': ', $e->getMessage(), "\n";
+    }
+}
+
+echo "array for translate:\n";
+imageaffinematrixget(IMG_AFFINE_TRANSLATE, ['x' => 1, 'y' => 2]);
+echo "accepted\n";
+
+echo "float for translate:\n";
+try {
+    imageaffinematrixget(IMG_AFFINE_TRANSLATE, 1.0);
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+null:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, null given
+false:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, false given
+true:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, true given
+int:
+accepted
+float:
+accepted
+numeric string:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, string given
+non-numeric string:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, string given
+array:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type float when using rotate or shear
+object:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
+array for translate:
+accepted
+float for translate:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array when using translate or scale
diff --git a/ext/gd/tests/imageaffinematrixget_zpp_weak_mode.phpt b/ext/gd/tests/imageaffinematrixget_zpp_weak_mode.phpt
new file mode 100644
index 00000000000..ffd5523fb83
--- /dev/null
+++ b/ext/gd/tests/imageaffinematrixget_zpp_weak_mode.phpt
@@ -0,0 +1,66 @@
+--TEST--
+imageaffinematrixget() array|float parameter coercions (weak mode)
+--EXTENSIONS--
+gd
+--FILE--
+<?php
+
+$values = [
+    'null' => null,
+    'false' => false,
+    'true' => true,
+    'int' => 42,
+    'float' => 73.5,
+    'numeric string' => '15',
+    'non-numeric string' => 'string',
+    'array' => [],
+    'object' => new stdClass(),
+];
+
+foreach ($values as $name => $value) {
+    echo "$name:\n";
+    try {
+        imageaffinematrixget(IMG_AFFINE_ROTATE, $value);
+        echo "accepted\n";
+    } catch (Throwable $e) {
+        echo $e::class, ': ', $e->getMessage(), "\n";
+    }
+}
+
+echo "array for translate:\n";
+imageaffinematrixget(IMG_AFFINE_TRANSLATE, ['x' => 1, 'y' => 2]);
+echo "accepted\n";
+
+echo "float for translate:\n";
+try {
+    imageaffinematrixget(IMG_AFFINE_TRANSLATE, 1.0);
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+null:
+
+Deprecated: imageaffinematrixget(): Passing null to parameter #2 ($options) of type array|float is deprecated in %s on line %d
+accepted
+false:
+accepted
+true:
+accepted
+int:
+accepted
+float:
+accepted
+numeric string:
+accepted
+non-numeric string:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, string given
+array:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type float when using rotate or shear
+object:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array|float, stdClass given
+array for translate:
+accepted
+float for translate:
+TypeError: imageaffinematrixget(): Argument #2 ($options) must be of type array when using translate or scale