|
18 | 18 | #include <zend_smart_str.h>
|
19 | 19 | #include <ext/standard/php_var.h>
|
20 | 20 | #include <Zend/zend_interfaces.h>
|
| 21 | +#include <Zend/zend_exceptions.h> |
21 | 22 |
|
22 | 23 | #include "php_phongo.h"
|
23 | 24 | #include "phongo_error.h"
|
@@ -86,7 +87,25 @@ HashTable* php_phongo_int64_get_properties_hash(phongo_compat_object_handler_typ
|
86 | 87 | return props;
|
87 | 88 | }
|
88 | 89 |
|
89 |
| -PHONGO_DISABLED_CONSTRUCTOR(MongoDB_BSON_Int64) |
| 90 | +static PHP_METHOD(MongoDB_BSON_Int64, __construct) |
| 91 | +{ |
| 92 | + php_phongo_int64_t* intern; |
| 93 | + zval* value; |
| 94 | + |
| 95 | + intern = Z_INT64_OBJ_P(getThis()); |
| 96 | + |
| 97 | + PHONGO_PARSE_PARAMETERS_START(1, 1) |
| 98 | + Z_PARAM_ZVAL(value); |
| 99 | + PHONGO_PARSE_PARAMETERS_END(); |
| 100 | + |
| 101 | + if (Z_TYPE_P(value) == IS_STRING) { |
| 102 | + php_phongo_int64_init_from_string(intern, Z_STRVAL_P(value), Z_STRLEN_P(value)); |
| 103 | + } else if (Z_TYPE_P(value) == IS_LONG) { |
| 104 | + php_phongo_int64_init(intern, Z_LVAL_P(value)); |
| 105 | + } else { |
| 106 | + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected value to be integer or string, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(value)); |
| 107 | + } |
| 108 | +} |
90 | 109 |
|
91 | 110 | /* Return the Int64's value as a string. */
|
92 | 111 | static PHP_METHOD(MongoDB_BSON_Int64, __toString)
|
@@ -244,6 +263,261 @@ static int php_phongo_int64_compare_objects(zval* o1, zval* o2)
|
244 | 263 | return 0;
|
245 | 264 | }
|
246 | 265 |
|
| 266 | +static zend_result php_phongo_int64_cast_object(phongo_compat_object_handler_type* readobj, zval* retval, int type) |
| 267 | +{ |
| 268 | + php_phongo_int64_t* intern; |
| 269 | + |
| 270 | + intern = Z_OBJ_INT64(PHONGO_COMPAT_GET_OBJ(readobj)); |
| 271 | + |
| 272 | + switch (type) { |
| 273 | + case IS_DOUBLE: |
| 274 | + ZVAL_DOUBLE(retval, (double) intern->integer); |
| 275 | + |
| 276 | + return SUCCESS; |
| 277 | + |
| 278 | + case IS_LONG: |
| 279 | +#if PHP_VERSION_ID >= 70300 |
| 280 | + case _IS_NUMBER: |
| 281 | +#endif |
| 282 | +#if SIZEOF_ZEND_LONG == 4 |
| 283 | + if (intern->integer > INT32_MAX || intern->integer < INT32_MIN) { |
| 284 | + zend_error(E_WARNING, "Truncating 64-bit integer value %" PRId64 " to 32 bits", intern->integer); |
| 285 | + } |
| 286 | +#endif |
| 287 | + |
| 288 | + ZVAL_LONG(retval, intern->integer); |
| 289 | + |
| 290 | + return SUCCESS; |
| 291 | + |
| 292 | + case _IS_BOOL: |
| 293 | + ZVAL_BOOL(retval, intern->integer != 0); |
| 294 | + |
| 295 | + return SUCCESS; |
| 296 | + |
| 297 | + default: |
| 298 | + return zend_std_cast_object_tostring(readobj, retval, type); |
| 299 | + } |
| 300 | +} |
| 301 | + |
| 302 | +/* Computes the power of two int64_t values by using the exponentiation by |
| 303 | + * squaring algorithm. This is necessary because in case the result exceeds |
| 304 | + * the range of a int64_t, we want PHP to return a float as it would when |
| 305 | + * using 64-bit values directly. We can't use anything involving zend_long |
| 306 | + * here as this would limit us to 32 bits on a 32-bit platform. This also |
| 307 | + * prohibits us from falling back to PHP's default functions after unwrapping |
| 308 | + * the int64_t from the php_phongo_int64_t instance. */ |
| 309 | +static int64_t phongo_pow_int64(int64_t base, int64_t exp) |
| 310 | +{ |
| 311 | + if (exp == 0) { |
| 312 | + return 1; |
| 313 | + } |
| 314 | + |
| 315 | + if (exp % 2) { |
| 316 | + return base * phongo_pow_int64(base * base, (exp - 1) / 2); |
| 317 | + } |
| 318 | + |
| 319 | + return phongo_pow_int64(base * base, exp / 2); |
| 320 | +} |
| 321 | + |
| 322 | +#define OPERATION_RESULT_INT64(value) ZVAL_INT64_OBJ(result, value); |
| 323 | + |
| 324 | +#define PHONGO_GET_INT64(int64, zval) \ |
| 325 | + if (Z_TYPE_P((zval)) == IS_LONG) { \ |
| 326 | + (int64) = Z_LVAL_P((zval)); \ |
| 327 | + } else if (Z_TYPE_P((zval)) == IS_OBJECT && Z_OBJCE_P((zval)) == php_phongo_int64_ce) { \ |
| 328 | + (int64) = Z_INT64_OBJ_P((zval))->integer; \ |
| 329 | + } else { \ |
| 330 | + return FAILURE; \ |
| 331 | + } |
| 332 | + |
| 333 | +#define INT64_SIGN_MASK INT64_MIN |
| 334 | + |
| 335 | +/* Overload arithmetic operators for computation on int64_t values. |
| 336 | + * This ensures that any computation involving at least one php_phongo_int64_t |
| 337 | + * results in a php_phongo_int64_t value, regardless of whether the result |
| 338 | + * would fit in an int32_t or not. Results that exceed the 64-bit integer |
| 339 | + * range are returned as float as PHP would do when using 64-bit integers. |
| 340 | + * Note that ZEND_(PRE|POST)_(INC|DEC) are not handled here: when checking for |
| 341 | + * a do_operation handler for inc/dec, PHP calls the handler with a ZEND_ADD |
| 342 | + * or ZEND_SUB opcode and the same pointer for result and op1, and a ZVAL_LONG |
| 343 | + * of 1 for op2. */ |
| 344 | +static zend_result php_phongo_int64_do_operation_ex(zend_uchar opcode, zval* result, zval* op1, zval* op2) |
| 345 | +{ |
| 346 | + int64_t value1, value2, lresult; |
| 347 | + |
| 348 | + PHONGO_GET_INT64(value1, op1); |
| 349 | + |
| 350 | + switch (opcode) { |
| 351 | + case ZEND_ADD: |
| 352 | + PHONGO_GET_INT64(value2, op2); |
| 353 | + |
| 354 | + lresult = value1 + value2; |
| 355 | + |
| 356 | + /* The following is based on the logic in fast_long_add_function() in PHP. |
| 357 | + * If the result sign differs from the first operand sign, we have an overflow if: |
| 358 | + * - adding a positive to a positive yields a negative, or |
| 359 | + * - adding a negative to a negative (i.e. subtraction) yields a positive */ |
| 360 | + if ((value1 & INT64_SIGN_MASK) != (lresult & INT64_SIGN_MASK) && (value1 & INT64_SIGN_MASK) == (value2 & INT64_SIGN_MASK)) { |
| 361 | + ZVAL_DOUBLE(result, (double) value1 + (double) value2); |
| 362 | + } else { |
| 363 | + OPERATION_RESULT_INT64(lresult); |
| 364 | + } |
| 365 | + |
| 366 | + return SUCCESS; |
| 367 | + |
| 368 | + case ZEND_SUB: |
| 369 | + PHONGO_GET_INT64(value2, op2); |
| 370 | + |
| 371 | + lresult = value1 - value2; |
| 372 | + |
| 373 | + /* The following is based on the logic in fast_long_sub_function() in PHP. |
| 374 | + * If the result sign differs from the first operand sign, we have an overflow if: |
| 375 | + * - subtracting a positive from a negative yields a positive, or |
| 376 | + * - subtracting a negative from a positive (i.e. addition) yields a negative */ |
| 377 | + if ((value1 & INT64_SIGN_MASK) != (lresult & INT64_SIGN_MASK) && (value1 & INT64_SIGN_MASK) != (value2 & INT64_SIGN_MASK)) { |
| 378 | + ZVAL_DOUBLE(result, (double) value1 - (double) value2); |
| 379 | + } else { |
| 380 | + OPERATION_RESULT_INT64(lresult); |
| 381 | + } |
| 382 | + |
| 383 | + return SUCCESS; |
| 384 | + |
| 385 | + case ZEND_MUL: |
| 386 | + PHONGO_GET_INT64(value2, op2); |
| 387 | + |
| 388 | + /* The following is based on the C-native implementation of |
| 389 | + * ZEND_SIGNED_MULTIPLY_LONG() in PHP if no other methods (e.g. ASM |
| 390 | + * or _builtin_smull_overflow) can be used. */ |
| 391 | + { |
| 392 | + int64_t lres = value1 * value2; |
| 393 | + long double dres = (long double) value1 * (long double) value2; |
| 394 | + long double delta = (long double) lres - dres; |
| 395 | + |
| 396 | + if ((dres + delta) != dres) { |
| 397 | + ZVAL_DOUBLE(result, dres); |
| 398 | + } else { |
| 399 | + OPERATION_RESULT_INT64(lres); |
| 400 | + } |
| 401 | + } |
| 402 | + |
| 403 | + return SUCCESS; |
| 404 | + |
| 405 | + case ZEND_DIV: |
| 406 | + PHONGO_GET_INT64(value2, op2); |
| 407 | + if (value2 == 0) { |
| 408 | + zend_throw_exception(zend_ce_division_by_zero_error, "Division by zero", 0); |
| 409 | + return FAILURE; |
| 410 | + } |
| 411 | + |
| 412 | + /* The following is based on div_function_base() in PHP. |
| 413 | + * - INT64_MIN / 1 exceeds the int64 range -> return double |
| 414 | + * - if division has a remainder, return double as result can't be |
| 415 | + * an int */ |
| 416 | + if ((value1 == INT64_MIN && value2 == -1) || (value1 % value2 != 0)) { |
| 417 | + ZVAL_DOUBLE(result, (double) value1 / value2); |
| 418 | + } else { |
| 419 | + OPERATION_RESULT_INT64(value1 / value2); |
| 420 | + } |
| 421 | + |
| 422 | + return SUCCESS; |
| 423 | + |
| 424 | + case ZEND_MOD: |
| 425 | + PHONGO_GET_INT64(value2, op2); |
| 426 | + if (value2 == 0) { |
| 427 | + zend_throw_exception(zend_ce_division_by_zero_error, "Division by zero", 0); |
| 428 | + return FAILURE; |
| 429 | + } |
| 430 | + |
| 431 | + OPERATION_RESULT_INT64(value1 % value2); |
| 432 | + return SUCCESS; |
| 433 | + |
| 434 | + case ZEND_SL: |
| 435 | + PHONGO_GET_INT64(value2, op2); |
| 436 | + OPERATION_RESULT_INT64(value1 << value2); |
| 437 | + return SUCCESS; |
| 438 | + |
| 439 | + case ZEND_SR: |
| 440 | + PHONGO_GET_INT64(value2, op2); |
| 441 | + OPERATION_RESULT_INT64(value1 >> value2); |
| 442 | + return SUCCESS; |
| 443 | + |
| 444 | + case ZEND_POW: |
| 445 | + PHONGO_GET_INT64(value2, op2); |
| 446 | + |
| 447 | + // Negative exponents always yield floats, leave them for PHP to handle |
| 448 | + if (value2 < 0) { |
| 449 | + return FAILURE; |
| 450 | + } |
| 451 | + |
| 452 | + // Handle 0 separately to distinguish between base 0 and |
| 453 | + // phongo_pow_int64 overflowing |
| 454 | + if (value1 == 0) { |
| 455 | + OPERATION_RESULT_INT64(0); |
| 456 | + return SUCCESS; |
| 457 | + } |
| 458 | + |
| 459 | + { |
| 460 | + int64_t pow_result = phongo_pow_int64(value1, value2); |
| 461 | + |
| 462 | + // If the result would overflow an int64_t, we let PHP fall back |
| 463 | + // to its default pow() implementation which returns a float. |
| 464 | + if (pow_result == 0) { |
| 465 | + return FAILURE; |
| 466 | + } |
| 467 | + |
| 468 | + OPERATION_RESULT_INT64(pow_result); |
| 469 | + } |
| 470 | + |
| 471 | + return SUCCESS; |
| 472 | + |
| 473 | + case ZEND_BW_AND: |
| 474 | + PHONGO_GET_INT64(value2, op2); |
| 475 | + OPERATION_RESULT_INT64(value1 & value2); |
| 476 | + return SUCCESS; |
| 477 | + |
| 478 | + case ZEND_BW_OR: |
| 479 | + PHONGO_GET_INT64(value2, op2); |
| 480 | + OPERATION_RESULT_INT64(value1 | value2); |
| 481 | + return SUCCESS; |
| 482 | + |
| 483 | + case ZEND_BW_XOR: |
| 484 | + PHONGO_GET_INT64(value2, op2); |
| 485 | + OPERATION_RESULT_INT64(value1 ^ value2); |
| 486 | + return SUCCESS; |
| 487 | + |
| 488 | + case ZEND_BW_NOT: |
| 489 | + OPERATION_RESULT_INT64(~value1); |
| 490 | + return SUCCESS; |
| 491 | + |
| 492 | + default: |
| 493 | + return FAILURE; |
| 494 | + } |
| 495 | +} |
| 496 | + |
| 497 | +static zend_result php_phongo_int64_do_operation(zend_uchar opcode, zval* result, zval* op1, zval* op2) |
| 498 | +{ |
| 499 | + zval op1_copy; |
| 500 | + int retval; |
| 501 | + |
| 502 | + // Copy op1 for unary operations (e.g. $int64++) to ensure correct return values |
| 503 | + if (result == op1) { |
| 504 | + ZVAL_COPY_VALUE(&op1_copy, op1); |
| 505 | + op1 = &op1_copy; |
| 506 | + } |
| 507 | + |
| 508 | + retval = php_phongo_int64_do_operation_ex(opcode, result, op1, op2); |
| 509 | + |
| 510 | + if (retval == SUCCESS && op1 == &op1_copy) { |
| 511 | + zval_ptr_dtor(op1); |
| 512 | + } |
| 513 | + |
| 514 | + return retval; |
| 515 | +} |
| 516 | + |
| 517 | +#undef OPERATION_RESULT_INT64 |
| 518 | +#undef PHONGO_GET_INT64 |
| 519 | +#undef INT64_SIGN_MASK |
| 520 | + |
247 | 521 | static HashTable* php_phongo_int64_get_debug_info(phongo_compat_object_handler_type* object, int* is_temp)
|
248 | 522 | {
|
249 | 523 | *is_temp = 1;
|
@@ -271,4 +545,6 @@ void php_phongo_int64_init_ce(INIT_FUNC_ARGS)
|
271 | 545 | php_phongo_handler_int64.get_properties = php_phongo_int64_get_properties;
|
272 | 546 | php_phongo_handler_int64.free_obj = php_phongo_int64_free_object;
|
273 | 547 | php_phongo_handler_int64.offset = XtOffsetOf(php_phongo_int64_t, std);
|
| 548 | + php_phongo_handler_int64.cast_object = php_phongo_int64_cast_object; |
| 549 | + php_phongo_handler_int64.do_operation = php_phongo_int64_do_operation; |
274 | 550 | }
|
0 commit comments