Skip to content

Commit f47915e

Browse files
authored
PHPC-2420: Compare Int64 instances without casting (#1617)
* PHPC-2420: Compare Int64 instances without casting * Fix comparison logic on PHP 7.4 * Handle Int64 comparisons with other zvals in PHP 7.4
1 parent 03f72d4 commit f47915e

File tree

3 files changed

+153
-9
lines changed

3 files changed

+153
-9
lines changed

src/BSON/Int64.c

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,23 @@ static zend_object* php_phongo_int64_clone_object(phongo_compat_object_handler_t
265265
return new_object;
266266
}
267267

268-
static int php_phongo_int64_compare_objects(zval* o1, zval* o2)
268+
static bool php_phongo_int64_is_int64_object(zval* object)
269269
{
270-
php_phongo_int64_t *intern1, *intern2;
270+
if (Z_TYPE_P(object) != IS_OBJECT) {
271+
return false;
272+
}
271273

272-
ZEND_COMPARE_OBJECTS_FALLBACK(o1, o2);
274+
return Z_OBJ_P(object)->ce == php_phongo_int64_ce;
275+
}
276+
277+
static bool php_phongo_int64_is_long_or_double(zval* value)
278+
{
279+
return Z_TYPE_P(value) == IS_LONG || Z_TYPE_P(value) == IS_DOUBLE;
280+
}
281+
282+
static int php_phongo_int64_compare_int64_objects(zval* o1, zval* o2)
283+
{
284+
php_phongo_int64_t *intern1, *intern2;
273285

274286
intern1 = Z_INT64_OBJ_P(o1);
275287
intern2 = Z_INT64_OBJ_P(o2);
@@ -281,6 +293,105 @@ static int php_phongo_int64_compare_objects(zval* o1, zval* o2)
281293
return 0;
282294
}
283295

296+
static int php_phongo_int64_compare_with_long_or_float(zval* object, zval* value)
297+
{
298+
php_phongo_int64_t* intern;
299+
int64_t long_value;
300+
double double_value;
301+
302+
intern = Z_INT64_OBJ_P(object);
303+
304+
assert(php_phongo_int64_is_long_or_double(value));
305+
306+
switch (Z_TYPE_P(value)) {
307+
case IS_LONG:
308+
long_value = Z_LVAL_P(value);
309+
if (intern->integer != long_value) {
310+
return intern->integer < long_value ? -1 : 1;
311+
}
312+
break;
313+
314+
case IS_DOUBLE:
315+
double_value = Z_DVAL_P(value);
316+
if (intern->integer != double_value) {
317+
return intern->integer < double_value ? -1 : 1;
318+
}
319+
break;
320+
321+
default:
322+
return 0;
323+
}
324+
325+
return 0;
326+
}
327+
328+
static int php_phongo_int64_compare_objects(zval* o1, zval* o2)
329+
{
330+
if (php_phongo_int64_is_int64_object(o1) && php_phongo_int64_is_int64_object(o2)) {
331+
return php_phongo_int64_compare_int64_objects(o1, o2);
332+
}
333+
334+
if (php_phongo_int64_is_int64_object(o1) && php_phongo_int64_is_long_or_double(o2)) {
335+
return php_phongo_int64_compare_with_long_or_float(o1, o2);
336+
}
337+
338+
if (php_phongo_int64_is_long_or_double(o1) && php_phongo_int64_is_int64_object(o2)) {
339+
// Invert the result as we're flipping the values used for comparison
340+
return -1 * php_phongo_int64_compare_with_long_or_float(o2, o1);
341+
}
342+
343+
ZEND_COMPARE_OBJECTS_FALLBACK(o1, o2);
344+
345+
return 0;
346+
}
347+
348+
#if PHP_VERSION_ID < 80000
349+
static int php_phongo_int64_compare_with_other_type(zval* object, zval* value)
350+
{
351+
zval tmp_value;
352+
zval result;
353+
int ret;
354+
355+
if (Z_OBJ_HT_P(object)->cast_object(object, &tmp_value, ((Z_TYPE_P(value) == IS_FALSE || Z_TYPE_P(value) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(value))) == FAILURE) {
356+
zval_ptr_dtor(&tmp_value);
357+
return 1;
358+
}
359+
360+
compare_function(&result, &tmp_value, value);
361+
362+
ret = Z_LVAL(result);
363+
zval_ptr_dtor(&tmp_value);
364+
zval_ptr_dtor(&result);
365+
366+
return ret;
367+
}
368+
369+
static int php_phongo_int64_compare_zvals(zval* result, zval* op1, zval* op2)
370+
{
371+
/* Happy case: compare an int64 object with another object, long, or double */
372+
if ((php_phongo_int64_is_int64_object(op1) || php_phongo_int64_is_long_or_double(op1)) && (php_phongo_int64_is_int64_object(op2) || php_phongo_int64_is_long_or_double(op2))) {
373+
ZVAL_LONG(result, php_phongo_int64_compare_objects(op1, op2));
374+
return SUCCESS;
375+
}
376+
377+
/* When comparing an int64 object with any other type, cast the int64 object to the desired type.
378+
* We know that if op1 is an object, op2 has to be the other type and vice versa. For op2 being
379+
* the object, we can again flip the values used for comparison and multiply the result with -1. */
380+
381+
if (php_phongo_int64_is_int64_object(op1)) {
382+
ZVAL_LONG(result, php_phongo_int64_compare_with_other_type(op1, op2));
383+
return SUCCESS;
384+
}
385+
386+
if (php_phongo_int64_is_int64_object(op2)) {
387+
ZVAL_LONG(result, -1 * php_phongo_int64_compare_with_other_type(op2, op1));
388+
return SUCCESS;
389+
}
390+
391+
return FAILURE;
392+
}
393+
#endif
394+
284395
static zend_result php_phongo_int64_cast_object(phongo_compat_object_handler_type* readobj, zval* retval, int type)
285396
{
286397
php_phongo_int64_t* intern;
@@ -563,6 +674,12 @@ void php_phongo_int64_init_ce(INIT_FUNC_ARGS)
563674
php_phongo_handler_int64.offset = XtOffsetOf(php_phongo_int64_t, std);
564675
php_phongo_handler_int64.cast_object = php_phongo_int64_cast_object;
565676
php_phongo_handler_int64.do_operation = php_phongo_int64_do_operation;
677+
678+
/* On PHP 7.4, compare_objects is only used when comparing two objects.
679+
* Use the compare handler to compare an object with any other zval */
680+
#if PHP_VERSION_ID < 80000
681+
php_phongo_handler_int64.compare = php_phongo_int64_compare_zvals;
682+
#endif
566683
}
567684

568685
bool phongo_int64_new(zval* object, int64_t integer)
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
--TEST--
2-
MongoDB\BSON\Int64 comparisons with scalars (64-bit values, 64-bit platforms only)
3-
--SKIPIF--
4-
<?php if (8 !== PHP_INT_SIZE) { die('skip Only for 64-bit platform'); } ?>
2+
MongoDB\BSON\Int64 comparisons with scalars (64-bit values, all platforms)
53
--FILE--
64
<?php
75

86
// Use 2**33 to ensure it still fits in a float
97
$int64 = new MongoDB\BSON\Int64('8589934592');
108

119
$tests = [
12-
'matching int' => 8589934592,
13-
'wrong int' => 8589934593,
10+
'matching float' => (float) 2**33,
11+
'wrong int' => 0,
1412
];
1513

1614
foreach ($tests as $name => $value) {
1715
printf('Testing %s: %s' . PHP_EOL, $name, var_export($int64 == $value, true));
1816
}
1917

18+
var_dump($int64 > 123);
19+
2020
?>
2121
===DONE===
2222
<?php exit(0); ?>
2323
--EXPECT--
24-
Testing matching int: true
24+
Testing matching float: true
2525
Testing wrong int: false
26+
bool(true)
2627
===DONE===
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
MongoDB\BSON\Int64 comparisons with scalars (64-bit values, 64-bit platforms only)
3+
--SKIPIF--
4+
<?php if (8 !== PHP_INT_SIZE) { die('skip Only for 64-bit platform'); } ?>
5+
--FILE--
6+
<?php
7+
8+
// Use 2**33 to ensure it still fits in a float
9+
$int64 = new MongoDB\BSON\Int64('8589934592');
10+
11+
$tests = [
12+
'matching int' => 8589934592,
13+
'wrong int' => 8589934593,
14+
];
15+
16+
foreach ($tests as $name => $value) {
17+
printf('Testing %s: %s' . PHP_EOL, $name, var_export($int64 == $value, true));
18+
}
19+
20+
?>
21+
===DONE===
22+
<?php exit(0); ?>
23+
--EXPECT--
24+
Testing matching int: true
25+
Testing wrong int: false
26+
===DONE===

0 commit comments

Comments
 (0)