Skip to content

Commit cbf5e37

Browse files
committed
Zend: Add zval_try_get_double()
Add a failure-reporting double conversion API analogous to zval_try_get_long(), and use it to validate GD affine translate and scale options.
1 parent 4384e7d commit cbf5e37

7 files changed

Lines changed: 193 additions & 2 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ PHP NEWS
3535
wrong argument in error messages. (Weilin Du)
3636
. Fixed imageaffinematrixget() to enforce the documented array|float type
3737
for the $options parameter. (Weilin Du)
38+
. Fixed imageaffinematrixget() accepting non-convertible "x" and "y" entries
39+
for translate and scale matrices. (Weilin Du)
3840

3941
- Intl:
4042
. Fixed grapheme_strrev() treating UBRK_DONE as a byte index and leaving

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ PHP 8.6 UPGRADE NOTES
5454
. imageaffinematrixget() now enforces the documented array|float type for the
5555
$options parameter, including the corresponding weak and strict typing
5656
behavior.
57+
. The "x" and "y" entries accepted by imageaffinematrixget() for translate
58+
and scale matrices now throw a TypeError when they cannot be converted to
59+
float, instead of coercing arbitrary values.
5760

5861
- GMP:
5962
. GMP power and shift operators now throw a ValueError when GMP right operands

UPGRADING.INTERNALS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES
222222
. Added zend_string_ends_with() and related variants.
223223
. Added trait support for internal classes.
224224
. Added do_php_cli().
225+
. Added zval_try_get_double(), which converts a zval to a double and reports
226+
conversion failures through a bool pointer, analogous to
227+
zval_try_get_long().
225228

226229
========================
227230
2. Build system changes

Zend/zend_operators.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,66 @@ ZEND_API double ZEND_FASTCALL zval_get_double_func(const zval *op) /* {{{ */
10591059
}
10601060
/* }}} */
10611061

1062+
ZEND_API zend_never_inline double ZEND_FASTCALL zval_try_get_double_func(const zval *op, bool *failed) /* {{{ */
1063+
{
1064+
*failed = false;
1065+
try_again:
1066+
switch (Z_TYPE_P(op)) {
1067+
case IS_NULL:
1068+
case IS_FALSE:
1069+
return 0.0;
1070+
case IS_TRUE:
1071+
return 1.0;
1072+
case IS_LONG:
1073+
return (double) Z_LVAL_P(op);
1074+
case IS_DOUBLE:
1075+
return Z_DVAL_P(op);
1076+
case IS_STRING:
1077+
{
1078+
uint8_t type;
1079+
zend_long lval;
1080+
double dval;
1081+
bool trailing_data = false;
1082+
1083+
/* For BC reasons we allow errors so that we can warn on leading numeric string */
1084+
type = is_numeric_string_ex(Z_STRVAL_P(op), Z_STRLEN_P(op), &lval, &dval,
1085+
/* allow errors */ true, NULL, &trailing_data);
1086+
if (type == 0) {
1087+
*failed = true;
1088+
return 0.0;
1089+
}
1090+
if (UNEXPECTED(trailing_data)) {
1091+
zend_error(E_WARNING, "A non-numeric value encountered");
1092+
if (UNEXPECTED(EG(exception))) {
1093+
*failed = true;
1094+
return 0.0;
1095+
}
1096+
}
1097+
return type == IS_LONG ? (double) lval : dval;
1098+
}
1099+
case IS_OBJECT:
1100+
{
1101+
zval dst;
1102+
if (Z_OBJ_HT_P(op)->cast_object(Z_OBJ_P(op), &dst, IS_DOUBLE) == FAILURE
1103+
|| EG(exception)) {
1104+
*failed = true;
1105+
return 0.0;
1106+
}
1107+
ZEND_ASSERT(Z_TYPE(dst) == IS_DOUBLE);
1108+
return Z_DVAL(dst);
1109+
}
1110+
case IS_RESOURCE:
1111+
case IS_ARRAY:
1112+
*failed = true;
1113+
return 0.0;
1114+
case IS_REFERENCE:
1115+
op = Z_REFVAL_P(op);
1116+
goto try_again;
1117+
default: ZEND_UNREACHABLE();
1118+
}
1119+
}
1120+
/* }}} */
1121+
10621122
static zend_always_inline zend_string* __zval_get_string_func(const zval *op, bool try) /* {{{ */
10631123
{
10641124
try_again:

Zend/zend_operators.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ ZEND_API void ZEND_FASTCALL convert_to_object(zval *op);
323323
ZEND_API zend_long ZEND_FASTCALL zval_get_long_func(const zval *op, bool is_strict);
324324
ZEND_API zend_long ZEND_FASTCALL zval_try_get_long(const zval *op, bool *failed);
325325
ZEND_API double ZEND_FASTCALL zval_get_double_func(const zval *op);
326+
ZEND_API zend_never_inline double ZEND_FASTCALL zval_try_get_double_func(const zval *op, bool *failed);
326327
ZEND_API zend_string* ZEND_FASTCALL zval_get_string_func(const zval *op);
327328
ZEND_API zend_string* ZEND_FASTCALL zval_try_get_string_func(const zval *op);
328329

@@ -335,6 +336,13 @@ static zend_always_inline zend_long zval_get_long_ex(const zval *op, bool is_str
335336
static zend_always_inline double zval_get_double(const zval *op) {
336337
return EXPECTED(Z_TYPE_P(op) == IS_DOUBLE) ? Z_DVAL_P(op) : zval_get_double_func(op);
337338
}
339+
static zend_always_inline double zval_try_get_double(const zval *op, bool *failed) {
340+
if (EXPECTED(Z_TYPE_P(op) == IS_DOUBLE)) {
341+
*failed = false;
342+
return Z_DVAL_P(op);
343+
}
344+
return zval_try_get_double_func(op, failed);
345+
}
338346
static zend_always_inline zend_string *zval_get_string(const zval *op) {
339347
return EXPECTED(Z_TYPE_P(op) == IS_STRING) ? zend_string_copy(Z_STR_P(op)) : zval_get_string_func(op);
340348
}

ext/gd/gd.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3844,6 +3844,18 @@ static bool php_gd_zval_try_get_c_int(zval *tmp, const char *field, int *res) {
38443844
return true;
38453845
}
38463846

3847+
static bool php_gd_zval_try_get_double(const zval *value, const char *field, double *res) {
3848+
bool failed;
3849+
*res = zval_try_get_double(value, &failed);
3850+
if (UNEXPECTED(failed)) {
3851+
if (!EG(exception)) {
3852+
zend_argument_type_error(2, "\"%s\" key must be of type float, %s given", field, zend_zval_type_name(value));
3853+
}
3854+
return false;
3855+
}
3856+
return true;
3857+
}
3858+
38473859
/* {{{ Crop an image using the given coordinates and size, x, y, width and height. */
38483860
PHP_FUNCTION(imagecrop)
38493861
{
@@ -4190,14 +4202,18 @@ PHP_FUNCTION(imageaffinematrixget)
41904202
}
41914203

41924204
if ((tmp = zend_hash_str_find(options, "x", sizeof("x") - 1)) != NULL) {
4193-
x = zval_get_double(tmp);
4205+
if (!php_gd_zval_try_get_double(tmp, "x", &x)) {
4206+
RETURN_THROWS();
4207+
}
41944208
} else {
41954209
zend_argument_value_error(2, "must have an \"x\" key");
41964210
RETURN_THROWS();
41974211
}
41984212

41994213
if ((tmp = zend_hash_str_find(options, "y", sizeof("y") - 1)) != NULL) {
4200-
y = zval_get_double(tmp);
4214+
if (!php_gd_zval_try_get_double(tmp, "y", &y)) {
4215+
RETURN_THROWS();
4216+
}
42014217
} else {
42024218
zend_argument_value_error(2, "must have a \"y\" key");
42034219
RETURN_THROWS();
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
--TEST--
2+
imageaffinematrixget() translate option conversions
3+
--EXTENSIONS--
4+
gd
5+
--FILE--
6+
<?php
7+
8+
$resource = fopen(__FILE__, 'r');
9+
$values = [
10+
'null' => null,
11+
'false' => false,
12+
'true' => true,
13+
'int' => 42,
14+
'float' => 42.5,
15+
'numeric integer string' => '42',
16+
'numeric float string' => '42.5',
17+
'numeric scientific string' => '1e3',
18+
'non-numeric string' => 'not numeric',
19+
'array' => [],
20+
'object' => new stdClass(),
21+
'resource' => $resource,
22+
];
23+
24+
foreach ($values as $name => $value) {
25+
echo "$name:\n";
26+
try {
27+
$matrix = imageaffinematrixget(IMG_AFFINE_TRANSLATE, ['x' => $value, 'y' => 0]);
28+
var_dump($matrix[4]);
29+
} catch (Throwable $e) {
30+
echo $e::class, ': ', $e->getMessage(), "\n";
31+
}
32+
}
33+
34+
echo "references:\n";
35+
$x = 1.5;
36+
$y = 2.5;
37+
$matrix = imageaffinematrixget(IMG_AFFINE_TRANSLATE, ['x' => &$x, 'y' => &$y]);
38+
var_dump($matrix[4], $matrix[5]);
39+
40+
echo "invalid y:\n";
41+
try {
42+
imageaffinematrixget(IMG_AFFINE_TRANSLATE, ['x' => 0, 'y' => []]);
43+
} catch (Throwable $e) {
44+
echo $e::class, ': ', $e->getMessage(), "\n";
45+
}
46+
47+
echo "trailing data:\n";
48+
$matrix = imageaffinematrixget(IMG_AFFINE_TRANSLATE, ['x' => '42 with trailing data', 'y' => 0]);
49+
var_dump($matrix[4]);
50+
51+
echo "warning converted to exception:\n";
52+
set_error_handler(static function (int $errno, string $errstr): never {
53+
throw new Exception($errstr);
54+
});
55+
try {
56+
imageaffinematrixget(IMG_AFFINE_TRANSLATE, ['x' => '42 with trailing data', 'y' => 0]);
57+
} catch (Throwable $e) {
58+
echo $e::class, ': ', $e->getMessage(), "\n";
59+
}
60+
61+
fclose($resource);
62+
63+
?>
64+
--EXPECTF--
65+
null:
66+
float(0)
67+
false:
68+
float(0)
69+
true:
70+
float(1)
71+
int:
72+
float(42)
73+
float:
74+
float(42.5)
75+
numeric integer string:
76+
float(42)
77+
numeric float string:
78+
float(42.5)
79+
numeric scientific string:
80+
float(1000)
81+
non-numeric string:
82+
TypeError: imageaffinematrixget(): Argument #2 ($options) "x" key must be of type float, string given
83+
array:
84+
TypeError: imageaffinematrixget(): Argument #2 ($options) "x" key must be of type float, array given
85+
object:
86+
TypeError: imageaffinematrixget(): Argument #2 ($options) "x" key must be of type float, stdClass given
87+
resource:
88+
TypeError: imageaffinematrixget(): Argument #2 ($options) "x" key must be of type float, resource given
89+
references:
90+
float(1.5)
91+
float(2.5)
92+
invalid y:
93+
TypeError: imageaffinematrixget(): Argument #2 ($options) "y" key must be of type float, array given
94+
trailing data:
95+
96+
Warning: A non-numeric value encountered in %s on line %d
97+
float(42)
98+
warning converted to exception:
99+
Exception: A non-numeric value encountered

0 commit comments

Comments
 (0)