Skip to content

Commit 0d5e68e

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 1053403 commit 0d5e68e

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
@@ -116,6 +116,8 @@ PHP NEWS
116116
wrong argument in error messages. (Weilin Du)
117117
. Fixed imageaffinematrixget() to enforce the documented array|float type
118118
for the $options parameter. (Weilin Du)
119+
. Fixed imageaffinematrixget() accepting non-convertible "x" and "y" entries
120+
for translate and scale matrices. (Weilin Du)
119121

120122
- Intl:
121123
. 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
@@ -227,6 +227,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES
227227
. Added zend_string_ends_with() and related variants.
228228
. Added trait support for internal classes.
229229
. Added do_php_cli().
230+
. Added zval_try_get_double(), which converts a zval to a double and reports
231+
conversion failures through a bool pointer, analogous to
232+
zval_try_get_long().
230233

231234
========================
232235
2. Build system changes

‎Zend/zend_operators.c‎

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

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

3874+
static bool php_gd_zval_try_get_double(const zval *value, const char *field, double *res) {
3875+
bool failed;
3876+
*res = zval_try_get_double(value, &failed);
3877+
if (UNEXPECTED(failed)) {
3878+
if (!EG(exception)) {
3879+
zend_argument_type_error(2, "\"%s\" key must be of type float, %s given", field, zend_zval_type_name(value));
3880+
}
3881+
return false;
3882+
}
3883+
return true;
3884+
}
3885+
38743886
/* {{{ Crop an image using the given coordinates and size, x, y, width and height. */
38753887
PHP_FUNCTION(imagecrop)
38763888
{
@@ -4217,14 +4229,18 @@ PHP_FUNCTION(imageaffinematrixget)
42174229
}
42184230

42194231
if ((tmp = zend_hash_str_find(options, "x", sizeof("x") - 1)) != NULL) {
4220-
x = zval_get_double(tmp);
4232+
if (!php_gd_zval_try_get_double(tmp, "x", &x)) {
4233+
RETURN_THROWS();
4234+
}
42214235
} else {
42224236
zend_argument_value_error(2, "must have an \"x\" key");
42234237
RETURN_THROWS();
42244238
}
42254239

42264240
if ((tmp = zend_hash_str_find(options, "y", sizeof("y") - 1)) != NULL) {
4227-
y = zval_get_double(tmp);
4241+
if (!php_gd_zval_try_get_double(tmp, "y", &y)) {
4242+
RETURN_THROWS();
4243+
}
42284244
} else {
42294245
zend_argument_value_error(2, "must have a \"y\" key");
42304246
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)