Skip to content

Commit 65d4234

Browse files
committed
Merge branch 'PHP-8.2' into PHP-8.3
* PHP-8.2: ext/gmp: Fix segfault when null is encountered on an overloaded operator ext/gmp: Add behavioural tests for operator overloading
2 parents 8df5133 + 5253647 commit 65d4234

13 files changed

+1095
-10
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ PHP NEWS
7171
(David Carlier)
7272
. Fixed gmp_pow() overflow bug with large base/exponents.
7373
(David Carlier)
74+
. Fixed segfaults and other issues related to operator overloading with
75+
GMP objects. (Girgias)
7476

7577
- MBstring:
7678
. Fixed bug GH-16361 (mb_substr overflow on start/length arguments).

ext/gmp/gmp.c

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -333,25 +333,89 @@ static zend_object *gmp_clone_obj(zend_object *obj) /* {{{ */
333333
}
334334
/* }}} */
335335

336-
static void shift_operator_helper(gmp_binary_ui_op_t op, zval *return_value, zval *op1, zval *op2, uint8_t opcode) {
337-
zend_long shift = zval_get_long(op2);
336+
static zend_result shift_operator_helper(gmp_binary_ui_op_t op, zval *return_value, zval *op1, zval *op2, uint8_t opcode) {
337+
zend_long shift = 0;
338+
339+
if (UNEXPECTED(Z_TYPE_P(op2) != IS_LONG)) {
340+
if (UNEXPECTED(!IS_GMP(op2))) {
341+
// For PHP 8.3 and up use zend_try_get_long()
342+
switch (Z_TYPE_P(op2)) {
343+
case IS_DOUBLE:
344+
shift = zval_get_long(op2);
345+
if (UNEXPECTED(EG(exception))) {
346+
return FAILURE;
347+
}
348+
break;
349+
case IS_STRING:
350+
if (is_numeric_str_function(Z_STR_P(op2), &shift, NULL) != IS_LONG) {
351+
goto valueof_op_failure;
352+
}
353+
break;
354+
default:
355+
goto typeof_op_failure;
356+
}
357+
} else {
358+
// TODO We shouldn't cast the GMP object to int here
359+
shift = zval_get_long(op2);
360+
}
361+
} else {
362+
shift = Z_LVAL_P(op2);
363+
}
338364

339365
if (shift < 0) {
340366
zend_throw_error(
341367
zend_ce_value_error, "%s must be greater than or equal to 0",
342368
opcode == ZEND_POW ? "Exponent" : "Shift"
343369
);
344370
ZVAL_UNDEF(return_value);
345-
return;
371+
return FAILURE;
346372
} else {
347373
mpz_ptr gmpnum_op, gmpnum_result;
348374
gmp_temp_t temp;
349375

350-
FETCH_GMP_ZVAL(gmpnum_op, op1, temp, 1);
376+
/* We do not use FETCH_GMP_ZVAL(...); here as we don't use convert_to_gmp()
377+
* as we want to handle the emitted exception ourself. */
378+
if (UNEXPECTED(!IS_GMP(op1))) {
379+
if (UNEXPECTED(Z_TYPE_P(op1) != IS_LONG)) {
380+
goto typeof_op_failure;
381+
}
382+
mpz_init(temp.num);
383+
mpz_set_si(temp.num, Z_LVAL_P(op1));
384+
temp.is_used = 1;
385+
gmpnum_op = temp.num;
386+
} else {
387+
gmpnum_op = GET_GMP_FROM_ZVAL(op1);
388+
temp.is_used = 0;
389+
}
351390
INIT_GMP_RETVAL(gmpnum_result);
352391
op(gmpnum_result, gmpnum_op, (gmp_ulong) shift);
353392
FREE_GMP_TEMP(temp);
393+
return SUCCESS;
394+
}
395+
396+
typeof_op_failure: ;
397+
/* Returning FAILURE without throwing an exception would emit the
398+
* Unsupported operand types: GMP OP TypeOfOp2
399+
* However, this leads to the engine trying to interpret the GMP object as an integer
400+
* and doing the operation that way, which is not something we want. */
401+
const char *op_sigil;
402+
switch (opcode) {
403+
case ZEND_POW:
404+
op_sigil = "**";
405+
break;
406+
case ZEND_SL:
407+
op_sigil = "<<";
408+
break;
409+
case ZEND_SR:
410+
op_sigil = ">>";
411+
break;
412+
EMPTY_SWITCH_DEFAULT_CASE();
354413
}
414+
zend_type_error("Unsupported operand types: %s %s %s", zend_zval_type_name(op1), op_sigil, zend_zval_type_name(op2));
415+
return FAILURE;
416+
valueof_op_failure:
417+
zend_value_error("Number is not an integer string");
418+
return FAILURE;
355419
}
356420

357421
#define DO_BINARY_UI_OP_EX(op, uop, check_b_zero) \
@@ -380,18 +444,15 @@ static zend_result gmp_do_operation_ex(uint8_t opcode, zval *result, zval *op1,
380444
case ZEND_MUL:
381445
DO_BINARY_UI_OP(mpz_mul);
382446
case ZEND_POW:
383-
shift_operator_helper(mpz_pow_ui, result, op1, op2, opcode);
384-
return SUCCESS;
447+
return shift_operator_helper(mpz_pow_ui, result, op1, op2, opcode);
385448
case ZEND_DIV:
386449
DO_BINARY_UI_OP_EX(mpz_tdiv_q, gmp_mpz_tdiv_q_ui, 1);
387450
case ZEND_MOD:
388451
DO_BINARY_UI_OP_EX(mpz_mod, gmp_mpz_mod_ui, 1);
389452
case ZEND_SL:
390-
shift_operator_helper(mpz_mul_2exp, result, op1, op2, opcode);
391-
return SUCCESS;
453+
return shift_operator_helper(mpz_mul_2exp, result, op1, op2, opcode);
392454
case ZEND_SR:
393-
shift_operator_helper(mpz_fdiv_q_2exp, result, op1, op2, opcode);
394-
return SUCCESS;
455+
return shift_operator_helper(mpz_fdiv_q_2exp, result, op1, op2, opcode);
395456
case ZEND_BW_OR:
396457
DO_BINARY_OP(mpz_ior);
397458
case ZEND_BW_AND:
@@ -619,6 +680,13 @@ static zend_result convert_to_gmp(mpz_t gmpnumber, zval *val, zend_long base, ui
619680
case IS_STRING: {
620681
return convert_zstr_to_gmp(gmpnumber, Z_STR_P(val), base, arg_pos);
621682
}
683+
case IS_NULL:
684+
/* Just reject null for operator overloading */
685+
if (arg_pos == 0) {
686+
zend_type_error("Number must be of type GMP|string|int, %s given", zend_zval_type_name(val));
687+
return FAILURE;
688+
}
689+
ZEND_FALLTHROUGH;
622690
default: {
623691
zend_long lval;
624692
if (!zend_parse_arg_long_slow(val, &lval, arg_pos)) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
GMP comparison operator overloading supports null
3+
--EXTENSIONS--
4+
gmp
5+
--FILE--
6+
<?php
7+
8+
$num = gmp_init(42);
9+
10+
try {
11+
var_dump($num < null);
12+
} catch (Throwable $e) {
13+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
14+
}
15+
16+
try {
17+
var_dump($num > null);
18+
} catch (Throwable $e) {
19+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
20+
}
21+
22+
try {
23+
var_dump($num <= null);
24+
} catch (Throwable $e) {
25+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
26+
}
27+
28+
try {
29+
var_dump($num >= null);
30+
} catch (Throwable $e) {
31+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
32+
}
33+
34+
try {
35+
var_dump($num == null);
36+
} catch (Throwable $e) {
37+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
38+
}
39+
40+
try {
41+
var_dump($num <=> null);
42+
} catch (Throwable $e) {
43+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
44+
}
45+
46+
?>
47+
--EXPECT--
48+
bool(false)
49+
bool(true)
50+
bool(false)
51+
bool(true)
52+
bool(false)
53+
int(1)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
--TEST--
2+
GMP operator overloading does not support []
3+
--EXTENSIONS--
4+
gmp
5+
--FILE--
6+
<?php
7+
8+
$num = gmp_init(42);
9+
10+
try {
11+
var_dump($num + []);
12+
} catch (Throwable $e) {
13+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
14+
}
15+
16+
try {
17+
var_dump($num - []);
18+
} catch (Throwable $e) {
19+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
20+
}
21+
22+
try {
23+
var_dump($num * []);
24+
} catch (Throwable $e) {
25+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
26+
}
27+
28+
try {
29+
var_dump($num / []);
30+
} catch (Throwable $e) {
31+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
32+
}
33+
34+
try {
35+
var_dump($num % []);
36+
} catch (Throwable $e) {
37+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
38+
}
39+
40+
try {
41+
var_dump($num ** []);
42+
} catch (Throwable $e) {
43+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
44+
}
45+
46+
try {
47+
var_dump($num | []);
48+
} catch (Throwable $e) {
49+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
50+
}
51+
try {
52+
var_dump($num & []);
53+
} catch (Throwable $e) {
54+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
55+
}
56+
try {
57+
var_dump($num ^ []);
58+
} catch (Throwable $e) {
59+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
60+
}
61+
try {
62+
var_dump($num << []);
63+
} catch (Throwable $e) {
64+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
65+
}
66+
try {
67+
var_dump($num >> []);
68+
} catch (Throwable $e) {
69+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
70+
}
71+
72+
?>
73+
--EXPECT--
74+
TypeError: Number must be of type GMP|string|int, array given
75+
TypeError: Number must be of type GMP|string|int, array given
76+
TypeError: Number must be of type GMP|string|int, array given
77+
TypeError: Number must be of type GMP|string|int, array given
78+
TypeError: Number must be of type GMP|string|int, array given
79+
TypeError: Unsupported operand types: GMP ** array
80+
TypeError: Number must be of type GMP|string|int, array given
81+
TypeError: Number must be of type GMP|string|int, array given
82+
TypeError: Number must be of type GMP|string|int, array given
83+
TypeError: Unsupported operand types: GMP << array
84+
TypeError: Unsupported operand types: GMP >> array
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
--TEST--
2+
GMP operator overloading does support float with no fractional
3+
--EXTENSIONS--
4+
gmp
5+
--FILE--
6+
<?php
7+
8+
$num = gmp_init(42);
9+
10+
try {
11+
var_dump($num + 42.0);
12+
} catch (Throwable $e) {
13+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
14+
}
15+
16+
try {
17+
var_dump($num - 42.0);
18+
} catch (Throwable $e) {
19+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
20+
}
21+
22+
try {
23+
var_dump($num * 42.0);
24+
} catch (Throwable $e) {
25+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
26+
}
27+
28+
try {
29+
var_dump($num / 42.0);
30+
} catch (Throwable $e) {
31+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
32+
}
33+
34+
try {
35+
var_dump($num % 42.0);
36+
} catch (Throwable $e) {
37+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
38+
}
39+
40+
try {
41+
var_dump($num ** 42.0);
42+
} catch (Throwable $e) {
43+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
44+
}
45+
46+
try {
47+
var_dump($num | 42.0);
48+
} catch (Throwable $e) {
49+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
50+
}
51+
try {
52+
var_dump($num & 42.0);
53+
} catch (Throwable $e) {
54+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
55+
}
56+
try {
57+
var_dump($num ^ 42.0);
58+
} catch (Throwable $e) {
59+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
60+
}
61+
try {
62+
var_dump($num << 42.0);
63+
} catch (Throwable $e) {
64+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
65+
}
66+
try {
67+
var_dump($num >> 42.0);
68+
} catch (Throwable $e) {
69+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
70+
}
71+
72+
?>
73+
--EXPECT--
74+
object(GMP)#2 (1) {
75+
["num"]=>
76+
string(2) "84"
77+
}
78+
object(GMP)#2 (1) {
79+
["num"]=>
80+
string(1) "0"
81+
}
82+
object(GMP)#2 (1) {
83+
["num"]=>
84+
string(4) "1764"
85+
}
86+
object(GMP)#2 (1) {
87+
["num"]=>
88+
string(1) "1"
89+
}
90+
object(GMP)#2 (1) {
91+
["num"]=>
92+
string(1) "0"
93+
}
94+
object(GMP)#2 (1) {
95+
["num"]=>
96+
string(69) "150130937545296572356771972164254457814047970568738777235893533016064"
97+
}
98+
object(GMP)#2 (1) {
99+
["num"]=>
100+
string(2) "42"
101+
}
102+
object(GMP)#2 (1) {
103+
["num"]=>
104+
string(2) "42"
105+
}
106+
object(GMP)#2 (1) {
107+
["num"]=>
108+
string(1) "0"
109+
}
110+
object(GMP)#2 (1) {
111+
["num"]=>
112+
string(15) "184717953466368"
113+
}
114+
object(GMP)#2 (1) {
115+
["num"]=>
116+
string(1) "0"
117+
}

0 commit comments

Comments
 (0)