From d8696f92166eea5e94cc82b64bce72f36fc81d46 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 17 Jul 2023 15:51:24 +0100 Subject: [PATCH] [RFC] Path to Saner Increment/Decrement operators (#10358) * Add behavioural tests for incdec operators * Add support to ++/-- for objects castable to _IS_NUMBER * Add str_increment() function * Add str_decrement() function RFC: https://wiki.php.net/rfc/saner-inc-dec-operators Co-authored-by: Ilija Tovilo Co-authored-by: Arnaud Le Blanc --- UPGRADING | 19 ++ Zend/Optimizer/sccp.c | 39 ++- Zend/Optimizer/zend_inference.c | 4 +- Zend/tests/decrement_001.phpt | 67 ----- Zend/tests/decrement_001_64bit.phpt | 67 ----- Zend/tests/in-de-crement/decrement_001.phpt | 25 ++ .../in-de-crement/decrement_001_64bit.phpt | 25 ++ .../decrement_diagnostic_change_type.phpt | 19 ++ ..._with_castable_objects_no_subtraction.phpt | 76 +++++ .../in-de-crement/incdec_bool_exception.phpt | 28 ++ .../incdec_ref_property.phpt | 0 Zend/tests/in-de-crement/incdec_strings.phpt | 126 +++++++++ .../incdec_strings_exception.phpt | 71 +++++ Zend/tests/in-de-crement/incdec_types.phpt | 100 +++++++ .../{ => in-de-crement}/incdec_undef.phpt | 3 +- Zend/tests/in-de-crement/increment_001.phpt | 24 ++ .../in-de-crement/increment_001_64bit.phpt | 24 ++ .../increment_diagnostic_change_type.phpt | 38 +++ ...nt_diagnostic_change_type_do_operator.phpt | 30 ++ .../increment_function_return_error.phpt | 0 ...ent_with_castable_objects_no_addition.phpt | 76 +++++ .../string_increment_various.phpt | 107 +++++++ Zend/tests/increment_001.phpt | 67 ----- Zend/tests/operator_unsupported_types.phpt | 1 + Zend/tests/remove_predecessor_of_pi_node.phpt | 2 + Zend/tests/unreachable_phi_cycle.phpt | 2 + Zend/zend_operators.c | 89 +++++- Zend/zend_operators.h | 2 + Zend/zend_vm_def.h | 12 + Zend/zend_vm_execute.h | 24 ++ ext/opcache/tests/bug77058.phpt | 2 + ext/opcache/tests/jit/inc_017.phpt | 3 +- ext/opcache/tests/jit/inc_018.phpt | 3 +- ext/opcache/tests/jit/inc_023.phpt | 5 +- ext/opcache/tests/jit/inc_024.phpt | 49 +++- ext/opcache/tests/jit/reg_alloc_014.phpt | 6 +- ext/opcache/tests/opt/inference_016.phpt | 6 +- ext/opcache/tests/opt/inference_019.phpt | 4 +- ext/opcache/tests/opt/sccp_038.phpt | 3 +- ext/reflection/tests/bug48336.phpt | 4 +- ext/reflection/tests/traits004.phpt | 3 +- ext/reflection/tests/traits005.phpt | 3 +- ext/standard/basic_functions.stub.php | 4 + ext/standard/basic_functions_arginfo.h | 10 +- ext/standard/string.c | 112 ++++++++ ext/standard/tests/array/array_combine.phpt | 261 ++++++++++-------- .../tests/strings/str_decrement_basic.phpt | 79 ++++++ .../tests/strings/str_decrement_errors.phpt | 53 ++++ .../strings/str_decrement_underflow.phpt | 32 +++ .../tests/strings/str_increment_basic.phpt | 54 ++++ .../tests/strings/str_increment_errors.phpt | 53 ++++ .../tests/strings/str_increment_polyfill.phpt | 59 ++++ ext/zend_test/test.c | 126 +++++++++ ext/zend_test/test.stub.php | 13 + ext/zend_test/test_arginfo.h | 84 +++++- .../lang/operators/postdec_variationStr.phpt | 18 +- .../lang/operators/postinc_variationStr.phpt | 12 +- tests/lang/operators/predec_variationStr.phpt | 18 +- tests/lang/operators/preinc_variationStr.phpt | 12 +- 59 files changed, 1897 insertions(+), 361 deletions(-) delete mode 100644 Zend/tests/decrement_001.phpt delete mode 100644 Zend/tests/decrement_001_64bit.phpt create mode 100644 Zend/tests/in-de-crement/decrement_001.phpt create mode 100644 Zend/tests/in-de-crement/decrement_001_64bit.phpt create mode 100644 Zend/tests/in-de-crement/decrement_diagnostic_change_type.phpt create mode 100644 Zend/tests/in-de-crement/decrement_with_castable_objects_no_subtraction.phpt create mode 100644 Zend/tests/in-de-crement/incdec_bool_exception.phpt rename Zend/tests/{ => in-de-crement}/incdec_ref_property.phpt (100%) create mode 100644 Zend/tests/in-de-crement/incdec_strings.phpt create mode 100644 Zend/tests/in-de-crement/incdec_strings_exception.phpt create mode 100644 Zend/tests/in-de-crement/incdec_types.phpt rename Zend/tests/{ => in-de-crement}/incdec_undef.phpt (65%) create mode 100644 Zend/tests/in-de-crement/increment_001.phpt create mode 100644 Zend/tests/in-de-crement/increment_001_64bit.phpt create mode 100644 Zend/tests/in-de-crement/increment_diagnostic_change_type.phpt create mode 100644 Zend/tests/in-de-crement/increment_diagnostic_change_type_do_operator.phpt rename Zend/tests/{ => in-de-crement}/increment_function_return_error.phpt (100%) create mode 100644 Zend/tests/in-de-crement/increment_with_castable_objects_no_addition.phpt create mode 100644 Zend/tests/in-de-crement/string_increment_various.phpt delete mode 100644 Zend/tests/increment_001.phpt create mode 100644 ext/standard/tests/strings/str_decrement_basic.phpt create mode 100644 ext/standard/tests/strings/str_decrement_errors.phpt create mode 100644 ext/standard/tests/strings/str_decrement_underflow.phpt create mode 100644 ext/standard/tests/strings/str_increment_basic.phpt create mode 100644 ext/standard/tests/strings/str_increment_errors.phpt create mode 100644 ext/standard/tests/strings/str_increment_polyfill.phpt diff --git a/UPGRADING b/UPGRADING index 18700b6872c2c..c7d1f35b1589a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -118,6 +118,14 @@ PHP 8.3 UPGRADE NOTES 4. Deprecated Functionality ======================================== +- Core + . Using the ++ operator on empty, non-numeric, or non-alphanumeric strings + is now deprecated. Moreover, incrementing non-numeric strings is soft + deprecated and the new str_increment() function should be used instead. + RFC: https://wiki.php.net/rfc/saner-inc-dec-operators + . Using the -- operator on empty or non-numeric strings is now deprecated. + RFC: https://wiki.php.net/rfc/saner-inc-dec-operators + - Intl . The U_MULTIPLE_DECIMAL_SEP*E*RATORS constant had been deprecated, using the U_MULTIPLE_DECIMAL_SEP*A*RATORS instead is recommended. @@ -303,6 +311,10 @@ PHP 8.3 UPGRADE NOTES - Sockets: . Added socket_atmark to checks if the socket is OOB marked. +- Standard: + . Added the str_increment() and str_decrement() functions. + RFC: https://wiki.php.net/rfc/saner-inc-dec-operators + - Zip: . Added ZipArchive::setArchiveFlag and ZipArchive::getArchiveFlag methods. @@ -324,6 +336,13 @@ PHP 8.3 UPGRADE NOTES not reachable except by iterating over the WeakMap (reachability via iteration is considered weak). Previously, such entries would never be automatically removed. (See GH-10932.) + . The ++ and -- operators now emit warnings when attempting to increment + values of type bool as this will change in the next major version. + A warning is emitted when trying to decrement values of type null, as + this will change in the next major version. + Internal objects that implement an _IS_NUMBER cast but not a do_operator + handler that overrides addition and substraction now can be incremented + and decrement as if one would do $o += 1 or $o -= 1 - DOM: . The DOM lifetime mechanism has been reworked such that implicitly removed diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c index c35c60fd6be93..d5486803e19a6 100644 --- a/Zend/Optimizer/sccp.c +++ b/Zend/Optimizer/sccp.c @@ -664,9 +664,14 @@ static inline zend_result ct_eval_assign_obj(zval *result, zval *value, const zv } static inline zend_result ct_eval_incdec(zval *result, uint8_t opcode, zval *op1) { + /* As of PHP 8.3 with the warning/deprecation notices any type other than int/double/null will emit a diagnostic if (Z_TYPE_P(op1) == IS_ARRAY || IS_PARTIAL_ARRAY(op1)) { return FAILURE; } + */ + if (Z_TYPE_P(op1) != IS_LONG && Z_TYPE_P(op1) != IS_DOUBLE && Z_TYPE_P(op1) != IS_NULL) { + return FAILURE; + } ZVAL_COPY(result, op1); if (opcode == ZEND_PRE_INC @@ -675,6 +680,11 @@ static inline zend_result ct_eval_incdec(zval *result, uint8_t opcode, zval *op1 || opcode == ZEND_POST_INC_OBJ) { increment_function(result); } else { + /* Decrement on null emits a deprecation notice */ + if (Z_TYPE_P(op1) == IS_NULL) { + zval_ptr_dtor(result); + return FAILURE; + } decrement_function(result); } return SUCCESS; @@ -1375,21 +1385,22 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { zval tmp1, tmp2; - if (ct_eval_fetch_obj(&tmp1, op1, op2) == SUCCESS - && ct_eval_incdec(&tmp2, opline->opcode, &tmp1) == SUCCESS) { - - dup_partial_object(&zv, op1); - ct_eval_assign_obj(&zv, &tmp2, op2); - if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_PRE_DEC_OBJ) { - SET_RESULT(result, &tmp2); - } else { - SET_RESULT(result, &tmp1); + if (ct_eval_fetch_obj(&tmp1, op1, op2) == SUCCESS) { + if (ct_eval_incdec(&tmp2, opline->opcode, &tmp1) == SUCCESS) { + dup_partial_object(&zv, op1); + ct_eval_assign_obj(&zv, &tmp2, op2); + if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_PRE_DEC_OBJ) { + SET_RESULT(result, &tmp2); + } else { + SET_RESULT(result, &tmp1); + } + zval_ptr_dtor_nogc(&tmp1); + zval_ptr_dtor_nogc(&tmp2); + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + break; } zval_ptr_dtor_nogc(&tmp1); - zval_ptr_dtor_nogc(&tmp2); - SET_RESULT(op1, &zv); - zval_ptr_dtor_nogc(&zv); - break; } } } @@ -2109,7 +2120,7 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var, break; default: break; - } + } } /* we cannot remove instruction that defines other variables */ return 0; diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 238098af1c5fb..dd71215980f72 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -4681,9 +4681,11 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)); case ZEND_PRE_INC: case ZEND_POST_INC: + return (t1 & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)); + /* null emits a warning as it has no effect compared to ++ which converts the value to 1 */ case ZEND_PRE_DEC: case ZEND_POST_DEC: - return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)); + return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)); case ZEND_BOOL_NOT: case ZEND_JMPZ: case ZEND_JMPNZ: diff --git a/Zend/tests/decrement_001.phpt b/Zend/tests/decrement_001.phpt deleted file mode 100644 index 6382491799563..0000000000000 --- a/Zend/tests/decrement_001.phpt +++ /dev/null @@ -1,67 +0,0 @@ ---TEST-- -decrementing different variables ---SKIPIF-- - ---INI-- -precision=14 ---FILE-- -getMessage(), "\n"; - } - var_dump($var); -} - -echo "Done\n"; -?> ---EXPECTF-- -Cannot decrement array -array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) -} -int(-1) -int(0) -float(1.5) -int(-1) -string(6) "string" -int(122) -float(1.5) -NULL -bool(true) -bool(false) -Cannot decrement stdClass -object(stdClass)#%d (0) { -} -Cannot decrement array -array(0) { -} -float(-2147483649) -float(-2147483649) -Done diff --git a/Zend/tests/decrement_001_64bit.phpt b/Zend/tests/decrement_001_64bit.phpt deleted file mode 100644 index b776bc5dd3896..0000000000000 --- a/Zend/tests/decrement_001_64bit.phpt +++ /dev/null @@ -1,67 +0,0 @@ ---TEST-- -decrementing different variables ---SKIPIF-- - ---INI-- -precision=14 ---FILE-- -getMessage(), "\n"; - } - var_dump($var); -} - -echo "Done\n"; -?> ---EXPECT-- -Cannot decrement array -array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) -} -int(-1) -int(0) -float(1.5) -int(-1) -string(6) "string" -int(122) -float(1.5) -NULL -bool(true) -bool(false) -Cannot decrement stdClass -object(stdClass)#1 (0) { -} -Cannot decrement array -array(0) { -} -float(-9.223372036854776E+18) -float(-9.223372036854776E+18) -Done diff --git a/Zend/tests/in-de-crement/decrement_001.phpt b/Zend/tests/in-de-crement/decrement_001.phpt new file mode 100644 index 0000000000000..63d5ae9975c0b --- /dev/null +++ b/Zend/tests/in-de-crement/decrement_001.phpt @@ -0,0 +1,25 @@ +--TEST-- +Decrementing min int values 32bit +--SKIPIF-- + +--INI-- +precision=14 +--FILE-- + +--EXPECT-- +float(-2147483649) +float(-2147483649) +Done diff --git a/Zend/tests/in-de-crement/decrement_001_64bit.phpt b/Zend/tests/in-de-crement/decrement_001_64bit.phpt new file mode 100644 index 0000000000000..7bc7e8c07fa1a --- /dev/null +++ b/Zend/tests/in-de-crement/decrement_001_64bit.phpt @@ -0,0 +1,25 @@ +--TEST-- +Decrementing min int values 64bit +--SKIPIF-- + +--INI-- +precision=14 +--FILE-- + +--EXPECT-- +float(-9.223372036854776E+18) +float(-9.223372036854776E+18) +Done diff --git a/Zend/tests/in-de-crement/decrement_diagnostic_change_type.phpt b/Zend/tests/in-de-crement/decrement_diagnostic_change_type.phpt new file mode 100644 index 0000000000000..aec9b5b3cdecb --- /dev/null +++ b/Zend/tests/in-de-crement/decrement_diagnostic_change_type.phpt @@ -0,0 +1,19 @@ +--TEST-- +Error handler can change type of operand of -- +--FILE-- + +DONE +--EXPECT-- +int(-1) +DONE diff --git a/Zend/tests/in-de-crement/decrement_with_castable_objects_no_subtraction.phpt b/Zend/tests/in-de-crement/decrement_with_castable_objects_no_subtraction.phpt new file mode 100644 index 0000000000000..21b4a9b01684a --- /dev/null +++ b/Zend/tests/in-de-crement/decrement_with_castable_objects_no_subtraction.phpt @@ -0,0 +1,76 @@ +--TEST-- +Decrementing objects which are castable to numeric types +--EXTENSIONS-- +zend_test +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($f - 1); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump($nl - 1); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump($nf - 1); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +/* Decrement */ +try { + $l--; + var_dump($l); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $f--; + var_dump($f); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $nl--; + var_dump($nl); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $nf--; + var_dump($nf); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Unsupported operand types: LongCastableNoOperations - int +Unsupported operand types: FloatCastableNoOperations - int +int(51) +float(57.3) +Cannot decrement LongCastableNoOperations +Cannot decrement FloatCastableNoOperations +int(51) +float(57.3) diff --git a/Zend/tests/in-de-crement/incdec_bool_exception.phpt b/Zend/tests/in-de-crement/incdec_bool_exception.phpt new file mode 100644 index 0000000000000..f819af1e23956 --- /dev/null +++ b/Zend/tests/in-de-crement/incdec_bool_exception.phpt @@ -0,0 +1,28 @@ +--TEST-- +Inc/dec on bool: warning converted to exception +--FILE-- +getMessage(), PHP_EOL; + } + try { + $value--; + } catch (\Exception $e) { + echo $e->getMessage(), PHP_EOL; + } +} +?> +--EXPECT-- +Increment on type bool has no effect, this will change in the next major version of PHP +Decrement on type bool has no effect, this will change in the next major version of PHP +Increment on type bool has no effect, this will change in the next major version of PHP +Decrement on type bool has no effect, this will change in the next major version of PHP diff --git a/Zend/tests/incdec_ref_property.phpt b/Zend/tests/in-de-crement/incdec_ref_property.phpt similarity index 100% rename from Zend/tests/incdec_ref_property.phpt rename to Zend/tests/in-de-crement/incdec_ref_property.phpt diff --git a/Zend/tests/in-de-crement/incdec_strings.phpt b/Zend/tests/in-de-crement/incdec_strings.phpt new file mode 100644 index 0000000000000..9df89536140b2 --- /dev/null +++ b/Zend/tests/in-de-crement/incdec_strings.phpt @@ -0,0 +1,126 @@ +--TEST-- +Inc/dec various strings +--FILE-- + +--EXPECTF-- +Using increment: +Initial value:string(0) "" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +Result value:string(1) "1" +Initial value:string(1) " " + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +Result value:string(1) " " +Initial value:string(1) "0" +Result value:int(1) +Initial value:string(4) "15.5" +Result value:float(16.5) +Initial value:string(4) "1e10" +Result value:float(10000000001) +Initial value:string(4) "199A" +Result value:string(4) "199B" +Initial value:string(4) "A199" +Result value:string(4) "A200" +Initial value:string(4) "199Z" +Result value:string(4) "200A" +Initial value:string(4) "Z199" +Result value:string(4) "Z200" +Initial value:string(11) "Hello world" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +Result value:string(11) "Hello worle" +Initial value:string(4) "🐘" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +Result value:string(4) "🐘" +Using decrement: +Initial value:string(0) "" + +Deprecated: Decrement on empty string is deprecated as non-numeric in %s on line %d +Result value:int(-1) +Initial value:string(1) " " + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d +Result value:string(1) " " +Initial value:string(1) "0" +Result value:int(-1) +Initial value:string(4) "15.5" +Result value:float(14.5) +Initial value:string(4) "1e10" +Result value:float(9999999999) +Initial value:string(4) "199A" + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d +Result value:string(4) "199A" +Initial value:string(4) "A199" + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d +Result value:string(4) "A199" +Initial value:string(4) "199Z" + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d +Result value:string(4) "199Z" +Initial value:string(4) "Z199" + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d +Result value:string(4) "Z199" +Initial value:string(11) "Hello world" + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d +Result value:string(11) "Hello world" +Initial value:string(4) "🐘" + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d +Result value:string(4) "🐘" diff --git a/Zend/tests/in-de-crement/incdec_strings_exception.phpt b/Zend/tests/in-de-crement/incdec_strings_exception.phpt new file mode 100644 index 0000000000000..4214a6a45878a --- /dev/null +++ b/Zend/tests/in-de-crement/incdec_strings_exception.phpt @@ -0,0 +1,71 @@ +--TEST-- +Inc/dec on string: warning/deprecations converted to exception +--FILE-- +getMessage(), PHP_EOL; + } + var_dump($value); + try { + $value--; + } catch (\Exception $e) { + echo $e->getMessage(), PHP_EOL; + } + var_dump($value); +} +?> +--EXPECT-- +Deprecated: Increment on non-alphanumeric string is deprecated +string(0) "" +Deprecated: Decrement on empty string is deprecated as non-numeric +string(0) "" +Deprecated: Increment on non-alphanumeric string is deprecated +string(1) " " +Deprecated: Decrement on non-numeric string has no effect and is deprecated +string(1) " " +string(4) "199B" +Deprecated: Decrement on non-numeric string has no effect and is deprecated +string(4) "199B" +string(4) "A200" +Deprecated: Decrement on non-numeric string has no effect and is deprecated +string(4) "A200" +string(4) "200A" +Deprecated: Decrement on non-numeric string has no effect and is deprecated +string(4) "200A" +string(4) "Z200" +Deprecated: Decrement on non-numeric string has no effect and is deprecated +string(4) "Z200" +Deprecated: Increment on non-alphanumeric string is deprecated +string(11) "Hello world" +Deprecated: Decrement on non-numeric string has no effect and is deprecated +string(11) "Hello world" +Deprecated: Increment on non-alphanumeric string is deprecated +string(4) "🐘" +Deprecated: Decrement on non-numeric string has no effect and is deprecated +string(4) "🐘" diff --git a/Zend/tests/in-de-crement/incdec_types.phpt b/Zend/tests/in-de-crement/incdec_types.phpt new file mode 100644 index 0000000000000..dfd850965146a --- /dev/null +++ b/Zend/tests/in-de-crement/incdec_types.phpt @@ -0,0 +1,100 @@ +--TEST-- +Inc/dec various types +--FILE-- +getMessage(), PHP_EOL; + } + try { + $type--; + } catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; + } +} + +echo "Using increment:\n"; +$values = [null, false, true, 0, 0.0, '', ' ', '0']; +foreach ($values as $value) { + echo "Initial value:"; + var_dump($value); + $value++; + echo "Result value:"; + var_dump($value); +} + +echo "Using decrement:\n"; +$values = [null, false, true, 0, 0.0, '', ' ', '0']; +foreach ($values as $value) { + echo "Initial value:"; + var_dump($value); + $value--; + echo "Result value:"; + var_dump($value); +} +?> +--EXPECTF-- +Cannot increment array +Cannot decrement array +Cannot increment stdClass +Cannot decrement stdClass +Cannot increment resource +Cannot decrement resource +Using increment: +Initial value:NULL +Result value:int(1) +Initial value:bool(false) + +Warning: Increment on type bool has no effect, this will change in the next major version of PHP in %s on line %d +Result value:bool(false) +Initial value:bool(true) + +Warning: Increment on type bool has no effect, this will change in the next major version of PHP in %s on line %d +Result value:bool(true) +Initial value:int(0) +Result value:int(1) +Initial value:float(0) +Result value:float(1) +Initial value:string(0) "" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +Result value:string(1) "1" +Initial value:string(1) " " + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +Result value:string(1) " " +Initial value:string(1) "0" +Result value:int(1) +Using decrement: +Initial value:NULL + +Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %s on line %d +Result value:NULL +Initial value:bool(false) + +Warning: Decrement on type bool has no effect, this will change in the next major version of PHP in %s on line %d +Result value:bool(false) +Initial value:bool(true) + +Warning: Decrement on type bool has no effect, this will change in the next major version of PHP in %s on line %d +Result value:bool(true) +Initial value:int(0) +Result value:int(-1) +Initial value:float(0) +Result value:float(-1) +Initial value:string(0) "" + +Deprecated: Decrement on empty string is deprecated as non-numeric in %s on line %d +Result value:int(-1) +Initial value:string(1) " " + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d +Result value:string(1) " " +Initial value:string(1) "0" +Result value:int(-1) diff --git a/Zend/tests/incdec_undef.phpt b/Zend/tests/in-de-crement/incdec_undef.phpt similarity index 65% rename from Zend/tests/incdec_undef.phpt rename to Zend/tests/in-de-crement/incdec_undef.phpt index c3a18cb53529a..db560e31c99f3 100644 --- a/Zend/tests/incdec_undef.phpt +++ b/Zend/tests/in-de-crement/incdec_undef.phpt @@ -4,7 +4,6 @@ Inc/dec undef var with error handler --EXPECT-- Undefined variable $x +Decrement on type null has no effect, this will change in the next major version of PHP NULL Undefined variable $x NULL Undefined variable $x +Decrement on type null has no effect, this will change in the next major version of PHP NULL Undefined variable $x int(1) diff --git a/Zend/tests/in-de-crement/increment_001.phpt b/Zend/tests/in-de-crement/increment_001.phpt new file mode 100644 index 0000000000000..a6f3484dff1c2 --- /dev/null +++ b/Zend/tests/in-de-crement/increment_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +Incrementing max int values 32bit +--SKIPIF-- + +--INI-- +precision=14 +--FILE-- + +--EXPECT-- +float(2147483648) +float(2147483648) +Done diff --git a/Zend/tests/in-de-crement/increment_001_64bit.phpt b/Zend/tests/in-de-crement/increment_001_64bit.phpt new file mode 100644 index 0000000000000..333bbcd460d6f --- /dev/null +++ b/Zend/tests/in-de-crement/increment_001_64bit.phpt @@ -0,0 +1,24 @@ +--TEST-- +Incrementing max int values 64bit +--SKIPIF-- + +--INI-- +precision=14 +--FILE-- + +--EXPECT-- +float(9.223372036854776E+18) +float(9.223372036854776E+18) +Done diff --git a/Zend/tests/in-de-crement/increment_diagnostic_change_type.phpt b/Zend/tests/in-de-crement/increment_diagnostic_change_type.phpt new file mode 100644 index 0000000000000..e124bfe907ebc --- /dev/null +++ b/Zend/tests/in-de-crement/increment_diagnostic_change_type.phpt @@ -0,0 +1,38 @@ +--TEST-- +Error handler can change type of operand of ++ +--FILE-- + +DONE +--EXPECT-- +string(1) "1" +string(50) "Increment on non-alphanumeric string is deprecated" +string(4) "foo!" +string(50) "Increment on non-alphanumeric string is deprecated" +string(1) "!" +DONE diff --git a/Zend/tests/in-de-crement/increment_diagnostic_change_type_do_operator.phpt b/Zend/tests/in-de-crement/increment_diagnostic_change_type_do_operator.phpt new file mode 100644 index 0000000000000..e20159a578cd9 --- /dev/null +++ b/Zend/tests/in-de-crement/increment_diagnostic_change_type_do_operator.phpt @@ -0,0 +1,30 @@ +--TEST-- +Error handler can change type of operand of ++ to object with do_operator +--EXTENSIONS-- +gmp +--FILE-- + +DONE +--EXPECT-- +string(50) "Increment on non-alphanumeric string is deprecated" +string(4) "foo!" +string(50) "Increment on non-alphanumeric string is deprecated" +string(1) "!" +DONE diff --git a/Zend/tests/increment_function_return_error.phpt b/Zend/tests/in-de-crement/increment_function_return_error.phpt similarity index 100% rename from Zend/tests/increment_function_return_error.phpt rename to Zend/tests/in-de-crement/increment_function_return_error.phpt diff --git a/Zend/tests/in-de-crement/increment_with_castable_objects_no_addition.phpt b/Zend/tests/in-de-crement/increment_with_castable_objects_no_addition.phpt new file mode 100644 index 0000000000000..cf08055383ed8 --- /dev/null +++ b/Zend/tests/in-de-crement/increment_with_castable_objects_no_addition.phpt @@ -0,0 +1,76 @@ +--TEST-- +Incrementing objects which are castable to numeric types +--EXTENSIONS-- +zend_test +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($f + 1); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump($nl + 1); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump($nf + 1); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +/* Decrement */ +try { + $l++; + var_dump($l); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $f++; + var_dump($f); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $nl++; + var_dump($nl); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $nf++; + var_dump($nf); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Unsupported operand types: LongCastableNoOperations + int +Unsupported operand types: FloatCastableNoOperations + int +int(53) +float(59.3) +Cannot increment LongCastableNoOperations +Cannot increment FloatCastableNoOperations +int(53) +float(59.3) diff --git a/Zend/tests/in-de-crement/string_increment_various.phpt b/Zend/tests/in-de-crement/string_increment_various.phpt new file mode 100644 index 0000000000000..8499d823042b6 --- /dev/null +++ b/Zend/tests/in-de-crement/string_increment_various.phpt @@ -0,0 +1,107 @@ +--TEST-- +String increment on a variety of strings +--FILE-- + +--EXPECTF-- +string(2) "Ba" +string(2) "bA" +string(2) "B0" +string(2) "b0" +string(3) "AAa" +string(3) "aaA" +string(3) "10a" +string(3) "10A" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(1) "1" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(3) "-cd" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(2) "Z " + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(2) " A" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(2) "é" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(15) "あいうえお" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(2) "α" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(2) "ω" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(2) "Α" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(2) "Ω" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(8) "foo1.txu" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(4) "1f.6" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(9) "foo.1.txu" + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d +string(5) "1.f.6" +string(3) "5e0" +float(6) diff --git a/Zend/tests/increment_001.phpt b/Zend/tests/increment_001.phpt deleted file mode 100644 index f34377192677c..0000000000000 --- a/Zend/tests/increment_001.phpt +++ /dev/null @@ -1,67 +0,0 @@ ---TEST-- -incrementing different variables ---SKIPIF-- - ---INI-- -precision=14 ---FILE-- -getMessage(), "\n"; - } - var_dump($var); -} - -echo "Done\n"; -?> ---EXPECTF-- -Cannot increment array -array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) -} -string(1) "1" -int(2) -float(3.5) -int(1) -string(6) "strinh" -int(124) -float(3.5) -int(1) -bool(true) -bool(false) -Cannot increment stdClass -object(stdClass)#%d (0) { -} -Cannot increment array -array(0) { -} -float(2147483648) -float(2147483648) -Done diff --git a/Zend/tests/operator_unsupported_types.phpt b/Zend/tests/operator_unsupported_types.phpt index c90f0e374ab56..ef169bb39fc29 100644 --- a/Zend/tests/operator_unsupported_types.phpt +++ b/Zend/tests/operator_unsupported_types.phpt @@ -2105,4 +2105,5 @@ Cannot decrement stdClass Cannot increment resource Cannot decrement resource No error for fop++ +Warning: Decrement on non-numeric string has no effect and is deprecated No error for foo-- diff --git a/Zend/tests/remove_predecessor_of_pi_node.phpt b/Zend/tests/remove_predecessor_of_pi_node.phpt index 6231f08b3f626..d33d8dc235768 100644 --- a/Zend/tests/remove_predecessor_of_pi_node.phpt +++ b/Zend/tests/remove_predecessor_of_pi_node.phpt @@ -12,3 +12,5 @@ test(); ?> --EXPECTF-- Warning: Undefined variable $n in %s on line %d + +Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %s on line %d diff --git a/Zend/tests/unreachable_phi_cycle.phpt b/Zend/tests/unreachable_phi_cycle.phpt index 84bfec214bb85..a8f4f145f0cb8 100644 --- a/Zend/tests/unreachable_phi_cycle.phpt +++ b/Zend/tests/unreachable_phi_cycle.phpt @@ -13,3 +13,5 @@ test(); ?> --EXPECTF-- Warning: Undefined variable $i in %s on line %d + +Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %s on line %d diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 1afce6a1f890a..293d7cc029eac 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -2493,7 +2493,20 @@ ZEND_API bool ZEND_FASTCALL instanceof_function_slow(const zend_class_entry *ins #define UPPER_CASE 2 #define NUMERIC 3 -static void ZEND_FASTCALL increment_string(zval *str) /* {{{ */ +ZEND_API bool zend_string_only_has_ascii_alphanumeric(const zend_string *str) +{ + const char *p = ZSTR_VAL(str); + const char *e = ZSTR_VAL(str) + ZSTR_LEN(str); + while (p < e) { + char c = *p++; + if (UNEXPECTED( c < '0' || c > 'z' || (c < 'a' && c > 'Z') || (c < 'A' && c > '9') ) ) { + return false; + } + } + return true; +} + +static bool ZEND_FASTCALL increment_string(zval *str) /* {{{ */ { int carry=0; size_t pos=Z_STRLEN_P(str)-1; @@ -2502,10 +2515,30 @@ static void ZEND_FASTCALL increment_string(zval *str) /* {{{ */ int last=0; /* Shut up the compiler warning */ int ch; - if (Z_STRLEN_P(str) == 0) { - zval_ptr_dtor_str(str); + if (UNEXPECTED(Z_STRLEN_P(str) == 0)) { + zend_error(E_DEPRECATED, "Increment on non-alphanumeric string is deprecated"); + if (EG(exception)) { + return false; + } + /* A userland error handler can change the type from string to something else */ + zval_ptr_dtor(str); ZVAL_CHAR(str, '1'); - return; + return true; + } + + if (UNEXPECTED(!zend_string_only_has_ascii_alphanumeric(Z_STR_P(str)))) { + zend_string *zstr = Z_STR_P(str); + GC_TRY_ADDREF(zstr); + zend_error(E_DEPRECATED, "Increment on non-alphanumeric string is deprecated"); + if (EG(exception)) { + GC_TRY_DELREF(zstr); + if (!GC_REFCOUNT(zstr)) { + efree(zstr); + } + return false; + } + zval_ptr_dtor(str); + ZVAL_STR(str, zstr); } if (!Z_REFCOUNTED_P(str)) { @@ -2577,6 +2610,7 @@ static void ZEND_FASTCALL increment_string(zval *str) /* {{{ */ zend_string_free(Z_STR_P(str)); ZVAL_NEW_STR(str, t); } + return true; } /* }}} */ @@ -2615,18 +2649,21 @@ ZEND_API zend_result ZEND_FASTCALL increment_function(zval *op1) /* {{{ */ default: /* Perl style string increment */ increment_string(op1); + if (EG(exception)) { + return FAILURE; + } break; } } break; case IS_FALSE: case IS_TRUE: - /* Do nothing. */ + zend_error(E_WARNING, "Increment on type bool has no effect, this will change in the next major version of PHP"); break; case IS_REFERENCE: op1 = Z_REFVAL_P(op1); goto try_again; - case IS_OBJECT: + case IS_OBJECT: { if (Z_OBJ_HANDLER_P(op1, do_operation)) { zval op2; ZVAL_LONG(&op2, 1); @@ -2634,7 +2671,15 @@ ZEND_API zend_result ZEND_FASTCALL increment_function(zval *op1) /* {{{ */ return SUCCESS; } } + zval tmp; + if (Z_OBJ_HT_P(op1)->cast_object(Z_OBJ_P(op1), &tmp, _IS_NUMBER) == SUCCESS) { + ZEND_ASSERT(Z_TYPE(tmp) == IS_LONG || Z_TYPE(tmp) == IS_DOUBLE); + zval_ptr_dtor(op1); + ZVAL_COPY_VALUE(op1, &tmp); + goto try_again; + } ZEND_FALLTHROUGH; + } case IS_RESOURCE: case IS_ARRAY: zend_type_error("Cannot increment %s", zend_zval_value_name(op1)); @@ -2660,7 +2705,12 @@ ZEND_API zend_result ZEND_FASTCALL decrement_function(zval *op1) /* {{{ */ break; case IS_STRING: /* Like perl we only support string increment */ if (Z_STRLEN_P(op1) == 0) { /* consider as 0 */ - zval_ptr_dtor_str(op1); + zend_error(E_DEPRECATED, "Decrement on empty string is deprecated as non-numeric"); + if (EG(exception)) { + return FAILURE; + } + /* A userland error handler can change the type from string to something else */ + zval_ptr_dtor(op1); ZVAL_LONG(op1, -1); break; } @@ -2678,17 +2728,30 @@ ZEND_API zend_result ZEND_FASTCALL decrement_function(zval *op1) /* {{{ */ zval_ptr_dtor_str(op1); ZVAL_DOUBLE(op1, dval - 1); break; + default: + zend_error(E_DEPRECATED, "Decrement on non-numeric string has no effect and is deprecated"); + if (EG(exception)) { + return FAILURE; + } } break; case IS_NULL: + zend_error(E_WARNING, "Decrement on type null has no effect, this will change in the next major version of PHP"); + if (EG(exception)) { + return FAILURE; + } + break; case IS_FALSE: case IS_TRUE: - /* Do nothing. */ + zend_error(E_WARNING, "Decrement on type bool has no effect, this will change in the next major version of PHP"); + if (EG(exception)) { + return FAILURE; + } break; case IS_REFERENCE: op1 = Z_REFVAL_P(op1); goto try_again; - case IS_OBJECT: + case IS_OBJECT: { if (Z_OBJ_HANDLER_P(op1, do_operation)) { zval op2; ZVAL_LONG(&op2, 1); @@ -2696,7 +2759,15 @@ ZEND_API zend_result ZEND_FASTCALL decrement_function(zval *op1) /* {{{ */ return SUCCESS; } } + zval tmp; + if (Z_OBJ_HT_P(op1)->cast_object(Z_OBJ_P(op1), &tmp, _IS_NUMBER) == SUCCESS) { + ZEND_ASSERT(Z_TYPE(tmp) == IS_LONG || Z_TYPE(tmp) == IS_DOUBLE); + zval_ptr_dtor(op1); + ZVAL_COPY_VALUE(op1, &tmp); + goto try_again; + } ZEND_FALLTHROUGH; + } case IS_RESOURCE: case IS_ARRAY: zend_type_error("Cannot decrement %s", zend_zval_value_name(op1)); diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index 67ac5fc45494d..4b47debc8032d 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -72,6 +72,8 @@ static zend_always_inline bool instanceof_function( return instance_ce == ce || instanceof_function_slow(instance_ce, ce); } +ZEND_API bool zend_string_only_has_ascii_alphanumeric(const zend_string *str); + /** * Checks whether the string "str" with length "length" is numeric. The value * of allow_errors determines whether it's required to be entirely numeric, or diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 121e150295923..f26e934337a1a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -1500,6 +1500,9 @@ ZEND_VM_HELPER(zend_pre_inc_helper, VAR|CV, ANY) } } increment_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -1552,6 +1555,9 @@ ZEND_VM_HELPER(zend_pre_dec_helper, VAR|CV, ANY) } } decrement_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -1606,6 +1612,9 @@ ZEND_VM_HELPER(zend_post_inc_helper, VAR|CV, ANY) ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); increment_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); FREE_OP1(); @@ -1654,6 +1663,9 @@ ZEND_VM_HELPER(zend_post_dec_helper, VAR|CV, ANY) ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); decrement_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); FREE_OP1(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 8d4f51e987516..2e625ca0d8a51 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -21615,6 +21615,9 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_pre_inc_help } } increment_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -21685,6 +21688,9 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_pre_dec_help } } decrement_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -21757,6 +21763,9 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_post_inc_hel ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); increment_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -21805,6 +21814,9 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_post_dec_hel ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); decrement_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -38974,6 +38986,9 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_pre_inc_help } } increment_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -39043,6 +39058,9 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_pre_dec_help } } decrement_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -39114,6 +39132,9 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_post_inc_hel ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); increment_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -39161,6 +39182,9 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_post_dec_hel ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); decrement_function(var_ptr); + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } } while (0); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); diff --git a/ext/opcache/tests/bug77058.phpt b/ext/opcache/tests/bug77058.phpt index e241281bd2daf..c8135c4a510d8 100644 --- a/ext/opcache/tests/bug77058.phpt +++ b/ext/opcache/tests/bug77058.phpt @@ -19,4 +19,6 @@ myfunc(); ?> --EXPECTF-- Warning: Undefined variable $x in %s on line %d + +Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %s on line %d '2' is expected to be 2 diff --git a/ext/opcache/tests/jit/inc_017.phpt b/ext/opcache/tests/jit/inc_017.phpt index 1d7c0f3d7b9ee..a78b6f013489b 100644 --- a/ext/opcache/tests/jit/inc_017.phpt +++ b/ext/opcache/tests/jit/inc_017.phpt @@ -17,5 +17,6 @@ function foo() { } var_dump(foo()); ?> ---EXPECT-- +--EXPECTF-- +Warning: Increment on type bool has no effect, this will change in the next major version of PHP in %sinc_017.php on line 4 bool(true) diff --git a/ext/opcache/tests/jit/inc_018.phpt b/ext/opcache/tests/jit/inc_018.phpt index 65c5f0e04ae65..d160164699412 100644 --- a/ext/opcache/tests/jit/inc_018.phpt +++ b/ext/opcache/tests/jit/inc_018.phpt @@ -17,5 +17,6 @@ function foo() { } var_dump(foo()); ?> ---EXPECT-- +--EXPECTF-- +Warning: Increment on type bool has no effect, this will change in the next major version of PHP in %sinc_018.php on line 4 bool(false) diff --git a/ext/opcache/tests/jit/inc_023.phpt b/ext/opcache/tests/jit/inc_023.phpt index 6ec64e057e503..e9af990b63673 100644 --- a/ext/opcache/tests/jit/inc_023.phpt +++ b/ext/opcache/tests/jit/inc_023.phpt @@ -19,6 +19,9 @@ $test->prop = "a"; var_dump(++$test->prop); var_dump(--$test->prop); ?> ---EXPECT-- +--EXPECTF-- +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d string(1) "c" + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d string(1) "c" diff --git a/ext/opcache/tests/jit/inc_024.phpt b/ext/opcache/tests/jit/inc_024.phpt index 8d2e361c0dc7f..e8a7f999f1ac4 100644 --- a/ext/opcache/tests/jit/inc_024.phpt +++ b/ext/opcache/tests/jit/inc_024.phpt @@ -23,6 +23,53 @@ function test($b) { } test("0"); ?> ---EXPECT-- +--EXPECTF-- +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d + +Deprecated: Decrement on non-numeric string has no effect and is deprecated in %s on line %d + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(5) "-INF0" string(260) "0-2-12-112-1112-11112-111112-1111112-11111112-111111112-1111111112-11111111112-111111111112-1111111111112-11111111111112-111111111111112-1111111111111112-11111111111111112-111111111111111112-1111111111111111112-1.1111111111111E+20-1.1111111111111E+191-ING-INF1" diff --git a/ext/opcache/tests/jit/reg_alloc_014.phpt b/ext/opcache/tests/jit/reg_alloc_014.phpt index 03b7ae0667c1f..ec048612c38f6 100644 --- a/ext/opcache/tests/jit/reg_alloc_014.phpt +++ b/ext/opcache/tests/jit/reg_alloc_014.phpt @@ -18,4 +18,8 @@ foo(); DONE --EXPECTF-- Warning: Undefined variable $a in %sreg_alloc_014.php on line 4 -DONE \ No newline at end of file + +Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %sreg_alloc_014.php on line 4 + +Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %sreg_alloc_014.php on line 4 +DONE diff --git a/ext/opcache/tests/opt/inference_016.phpt b/ext/opcache/tests/opt/inference_016.phpt index 6ce2488bcd395..7d1733934fb6b 100644 --- a/ext/opcache/tests/opt/inference_016.phpt +++ b/ext/opcache/tests/opt/inference_016.phpt @@ -13,11 +13,13 @@ function test() { } } try { - @test(); + test(); } catch (Throwable $e) { echo $e->getMessage(), "\n"; } ?> ---EXPECT-- +--EXPECTF-- NULL + +Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %sinference_016.php on line %d Modulo by zero diff --git a/ext/opcache/tests/opt/inference_019.phpt b/ext/opcache/tests/opt/inference_019.phpt index f7b951a38f814..5e1f52688467b 100644 --- a/ext/opcache/tests/opt/inference_019.phpt +++ b/ext/opcache/tests/opt/inference_019.phpt @@ -18,5 +18,7 @@ function test() { test(); ?> DONE ---EXPECT-- +--EXPECTF-- + +Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %sinference_019.php on line %d DONE diff --git a/ext/opcache/tests/opt/sccp_038.phpt b/ext/opcache/tests/opt/sccp_038.phpt index fdf663e87c362..165206056c6e5 100644 --- a/ext/opcache/tests/opt/sccp_038.phpt +++ b/ext/opcache/tests/opt/sccp_038.phpt @@ -14,5 +14,6 @@ function foo() { foo(); ?> DONE ---EXPECT-- +--EXPECTF-- +Deprecated: Increment on non-alphanumeric string is deprecated in %ssccp_038.php on line 5 DONE diff --git a/ext/reflection/tests/bug48336.phpt b/ext/reflection/tests/bug48336.phpt index ee90675f0c5d6..a7d441c1cb3fe 100644 --- a/ext/reflection/tests/bug48336.phpt +++ b/ext/reflection/tests/bug48336.phpt @@ -23,8 +23,8 @@ class F extends E { static protected $prop; } -$class = 'A'; -for($class = 'A'; $class <= 'F'; $class ++) { +$classes = ['A', 'B', 'C', 'D', 'E', 'F']; +foreach ($classes as $class) { print($class.' => '); try { $rp = new ReflectionProperty($class, 'prop'); diff --git a/ext/reflection/tests/traits004.phpt b/ext/reflection/tests/traits004.phpt index 0feed127d9928..da65bfa9b3843 100644 --- a/ext/reflection/tests/traits004.phpt +++ b/ext/reflection/tests/traits004.phpt @@ -9,7 +9,8 @@ class C1 { } class C2 { use T1; } class C3 { use T1; use T2; } -for ($c = "C1"; $c <= "C3"; $c++) { +$classes = ['C1', 'C2', 'C3']; +foreach ($classes as $c) { echo "class $c:\n"; $r = new ReflectionClass($c); var_dump($r->getTraitNames()); diff --git a/ext/reflection/tests/traits005.phpt b/ext/reflection/tests/traits005.phpt index fbc917eafce03..ef218608a382c 100644 --- a/ext/reflection/tests/traits005.phpt +++ b/ext/reflection/tests/traits005.phpt @@ -9,7 +9,8 @@ class C2 { use T1; } class C3 { use T1 { m1 as a1; } } class C4 { use T1 { m1 as a1; m2 as a2; } } -for ($c = "C1"; $c <= "C4"; $c++) { +$classes = ['C1', 'C2', 'C3', 'C4']; +foreach ($classes as $c) { echo "class $c:\n"; $r = new ReflectionClass($c); var_dump($r->getTraitAliases()); diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index f1603d66c4b78..0f2381c321b9f 100755 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -2348,6 +2348,10 @@ function strtoupper(string $string): string {} /** @compile-time-eval */ function strtolower(string $string): string {} +function str_increment(string $string): string {} + +function str_decrement(string $string): string {} + /** @refcount 1 */ function basename(string $path, string $suffix = ""): string {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index c4eba7d921d94..89f746aa5718c 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a15bbbd1d29dfd674dd2174b3be5678a0832116a */ + * Stub hash: e01f6a979e72b1c3baf4602421cc966edfe50312 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -872,6 +872,10 @@ ZEND_END_ARG_INFO() #define arginfo_strtolower arginfo_base64_encode +#define arginfo_str_increment arginfo_base64_encode + +#define arginfo_str_decrement arginfo_base64_encode + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_basename, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, suffix, IS_STRING, 0, "\"\"") @@ -2447,6 +2451,8 @@ ZEND_FUNCTION(implode); ZEND_FUNCTION(strtok); ZEND_FUNCTION(strtoupper); ZEND_FUNCTION(strtolower); +ZEND_FUNCTION(str_increment); +ZEND_FUNCTION(str_decrement); ZEND_FUNCTION(basename); ZEND_FUNCTION(dirname); ZEND_FUNCTION(pathinfo); @@ -3081,6 +3087,8 @@ static const zend_function_entry ext_functions[] = { ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strtok, arginfo_strtok) ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strtoupper, arginfo_strtoupper) ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strtolower, arginfo_strtolower) + ZEND_FE(str_increment, arginfo_str_increment) + ZEND_FE(str_decrement, arginfo_str_decrement) ZEND_FE(basename, arginfo_basename) ZEND_FE(dirname, arginfo_dirname) ZEND_FE(pathinfo, arginfo_pathinfo) diff --git a/ext/standard/string.c b/ext/standard/string.c index 335e6fd897128..c6b89897ef888 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -1192,6 +1192,118 @@ PHP_FUNCTION(strtolower) } /* }}} */ +PHP_FUNCTION(str_increment) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(str) == 0) { + zend_argument_value_error(1, "cannot be empty"); + RETURN_THROWS(); + } + if (!zend_string_only_has_ascii_alphanumeric(str)) { + zend_argument_value_error(1, "must be composed only of alphanumeric ASCII characters"); + RETURN_THROWS(); + } + + zend_string *incremented = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), /* persistent */ false); + size_t position = ZSTR_LEN(str)-1; + bool carry = false; + + do { + char c = ZSTR_VAL(incremented)[position]; + /* We know c is in ['a', 'z'], ['A', 'Z'], or ['0', '9'] range from zend_string_only_has_ascii_alphanumeric() */ + if (EXPECTED( c != 'z' && c != 'Z' && c != '9' )) { + carry = false; + ZSTR_VAL(incremented)[position]++; + } else { /* if 'z', 'Z', or '9' */ + carry = true; + if (c == '9') { + ZSTR_VAL(incremented)[position] = '0'; + } else { + ZSTR_VAL(incremented)[position] -= 25; + } + } + } while (carry && position-- > 0); + + if (UNEXPECTED(carry)) { + zend_string *tmp = zend_string_alloc(ZSTR_LEN(incremented)+1, 0); + memcpy(ZSTR_VAL(tmp) + 1, ZSTR_VAL(incremented), ZSTR_LEN(incremented)); + ZSTR_VAL(tmp)[ZSTR_LEN(incremented)+1] = '\0'; + switch (ZSTR_VAL(incremented)[0]) { + case '0': + ZSTR_VAL(tmp)[0] = '1'; + break; + default: + ZSTR_VAL(tmp)[0] = ZSTR_VAL(incremented)[0]; + break; + } + zend_string_release_ex(incremented, /* persistent */ false); + RETURN_STR(tmp); + } + RETURN_STR(incremented); +} + + +PHP_FUNCTION(str_decrement) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(str) == 0) { + zend_argument_value_error(1, "cannot be empty"); + RETURN_THROWS(); + } + if (!zend_string_only_has_ascii_alphanumeric(str)) { + zend_argument_value_error(1, "must be composed only of alphanumeric ASCII characters"); + RETURN_THROWS(); + } + if (ZSTR_LEN(str) >= 1 && ZSTR_VAL(str)[0] == '0') { + zend_argument_value_error(1, "\"%s\" is out of decrement range", ZSTR_VAL(str)); + RETURN_THROWS(); + } + + zend_string *decremented = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), /* persistent */ false); + size_t position = ZSTR_LEN(str)-1; + bool carry = false; + + do { + char c = ZSTR_VAL(decremented)[position]; + /* We know c is in ['a', 'z'], ['A', 'Z'], or ['0', '9'] range from zend_string_only_has_ascii_alphanumeric() */ + if (EXPECTED( c != 'a' && c != 'A' && c != '0' )) { + carry = false; + ZSTR_VAL(decremented)[position]--; + } else { /* if 'a', 'A', or '0' */ + carry = true; + if (c == '0') { + ZSTR_VAL(decremented)[position] = '9'; + } else { + ZSTR_VAL(decremented)[position] += 25; + } + } + } while (carry && position-- > 0); + + if (UNEXPECTED(carry || ZSTR_VAL(decremented)[0] == '0')) { + if (ZSTR_LEN(decremented) == 1) { + zend_string_release_ex(decremented, /* persistent */ false); + zend_argument_value_error(1, "\"%s\" is out of decrement range", ZSTR_VAL(str)); + RETURN_THROWS(); + } + zend_string *tmp = zend_string_alloc(ZSTR_LEN(decremented) - 1, 0); + memcpy(ZSTR_VAL(tmp), ZSTR_VAL(decremented) + 1, ZSTR_LEN(decremented) - 1); + ZSTR_VAL(tmp)[ZSTR_LEN(decremented) - 1] = '\0'; + zend_string_release_ex(decremented, /* persistent */ false); + RETURN_STR(tmp); + } + RETURN_STR(decremented); +} + #if defined(PHP_WIN32) static bool _is_basename_start(const char *start, const char *pos) { diff --git a/ext/standard/tests/array/array_combine.phpt b/ext/standard/tests/array/array_combine.phpt index b1b707aed6795..5bdf9cd28b81e 100644 --- a/ext/standard/tests/array/array_combine.phpt +++ b/ext/standard/tests/array/array_combine.phpt @@ -2,121 +2,150 @@ basic array_combine test --FILE-- --EXPECT-- -Array -( - [green] => green - [red] => red - [yellow] => yellow -) -Array -( - [green] => 1 - [red] => 2 - [yellow] => 3 -) -Array -( - [green] => 0 - [red] => 1 - [yellow] => 2 -) -Array -( - [green] => 1 - [red] => - [yellow] => -) -Array -( - [1] => green - [2] => red - [3] => yellow -) -Array -( - [1] => 1 - [2] => 2 - [3] => 3 -) -Array -( - [1] => 0 - [2] => 1 - [3] => 2 -) -Array -( - [1] => 1 - [2] => - [3] => -) -Array -( - [0] => green - [1] => red - [2] => yellow -) -Array -( - [0] => 1 - [1] => 2 - [2] => 3 -) -Array -( - [0] => 0 - [1] => 1 - [2] => 2 -) -Array -( - [0] => 1 - [1] => - [2] => -) -Array -( - [1] => green - [] => yellow -) -Array -( - [1] => 1 - [] => 3 -) -Array -( - [1] => 0 - [] => 2 -) -Array -( - [1] => 1 - [] => -) +array(3) { + ["green"]=> + string(5) "green" + ["red"]=> + string(3) "red" + ["yellow"]=> + string(6) "yellow" +} +array(3) { + ["green"]=> + string(1) "1" + ["red"]=> + string(1) "2" + ["yellow"]=> + string(1) "3" +} +array(3) { + ["green"]=> + int(0) + ["red"]=> + int(1) + ["yellow"]=> + int(2) +} +array(3) { + ["green"]=> + bool(true) + ["red"]=> + bool(false) + ["yellow"]=> + NULL +} +array(3) { + [1]=> + string(5) "green" + [2]=> + string(3) "red" + [3]=> + string(6) "yellow" +} +array(3) { + [1]=> + string(1) "1" + [2]=> + string(1) "2" + [3]=> + string(1) "3" +} +array(3) { + [1]=> + int(0) + [2]=> + int(1) + [3]=> + int(2) +} +array(3) { + [1]=> + bool(true) + [2]=> + bool(false) + [3]=> + NULL +} +array(3) { + [0]=> + string(5) "green" + [1]=> + string(3) "red" + [2]=> + string(6) "yellow" +} +array(3) { + [0]=> + string(1) "1" + [1]=> + string(1) "2" + [2]=> + string(1) "3" +} +array(3) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) +} +array(3) { + [0]=> + bool(true) + [1]=> + bool(false) + [2]=> + NULL +} +array(2) { + [1]=> + string(5) "green" + [""]=> + string(6) "yellow" +} +array(2) { + [1]=> + string(1) "1" + [""]=> + string(1) "3" +} +array(2) { + [1]=> + int(0) + [""]=> + int(2) +} +array(2) { + [1]=> + bool(true) + [""]=> + NULL +} diff --git a/ext/standard/tests/strings/str_decrement_basic.phpt b/ext/standard/tests/strings/str_decrement_basic.phpt new file mode 100644 index 0000000000000..ca02290c16661 --- /dev/null +++ b/ext/standard/tests/strings/str_decrement_basic.phpt @@ -0,0 +1,79 @@ +--TEST-- +str_decrement(): Decrementing various strings +--FILE-- + +--EXPECT-- +string(2) "Ay" +string(2) "Az" +string(2) "aY" +string(2) "aZ" +string(2) "A8" +string(2) "A9" +string(2) "a8" +string(2) "a9" +string(2) "Yz" +string(2) "Za" +string(2) "yZ" +string(2) "zA" +string(2) "Y9" +string(2) "Z0" +string(2) "y9" +string(2) "z0" +string(1) "z" +string(2) "Aa" +string(1) "Z" +string(2) "aA" +string(1) "9" +string(2) "A0" +string(1) "9" +string(2) "a0" +string(1) "9" +string(2) "10" +string(1) "Z" +string(2) "1A" +string(1) "z" +string(2) "1a" +string(2) "9z" +string(3) "10a" +string(3) "5e5" +string(3) "5e6" +string(1) "c" +string(1) "d" +string(1) "C" +string(1) "D" +string(1) "3" +string(1) "4" diff --git a/ext/standard/tests/strings/str_decrement_errors.phpt b/ext/standard/tests/strings/str_decrement_errors.phpt new file mode 100644 index 0000000000000..6f7f61e6b7b9a --- /dev/null +++ b/ext/standard/tests/strings/str_decrement_errors.phpt @@ -0,0 +1,53 @@ +--TEST-- +str_decrement(): Invalid strings to decrement should throw a ValueError +--FILE-- +getMessage(), PHP_EOL; + } +} + +?> +--EXPECT-- +str_decrement(): Argument #1 ($string) cannot be empty +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters diff --git a/ext/standard/tests/strings/str_decrement_underflow.phpt b/ext/standard/tests/strings/str_decrement_underflow.phpt new file mode 100644 index 0000000000000..ea60f0069342c --- /dev/null +++ b/ext/standard/tests/strings/str_decrement_underflow.phpt @@ -0,0 +1,32 @@ +--TEST-- +str_decrement(): Out of Range ValueErrors for strings that cannot be decremented +--FILE-- +getMessage(), PHP_EOL; + } +} + +?> +--EXPECT-- +str_decrement(): Argument #1 ($string) cannot be empty +str_decrement(): Argument #1 ($string) "0" is out of decrement range +str_decrement(): Argument #1 ($string) "a" is out of decrement range +str_decrement(): Argument #1 ($string) "A" is out of decrement range +str_decrement(): Argument #1 ($string) "00" is out of decrement range +str_decrement(): Argument #1 ($string) "0a" is out of decrement range +str_decrement(): Argument #1 ($string) "0A" is out of decrement range diff --git a/ext/standard/tests/strings/str_increment_basic.phpt b/ext/standard/tests/strings/str_increment_basic.phpt new file mode 100644 index 0000000000000..1f238f6f0dbe5 --- /dev/null +++ b/ext/standard/tests/strings/str_increment_basic.phpt @@ -0,0 +1,54 @@ +--TEST-- +str_increment(): Incrementing various strings +--FILE-- + +--EXPECT-- +string(2) "Ba" +string(2) "Az" +string(2) "bA" +string(2) "aZ" +string(2) "B0" +string(2) "A9" +string(2) "b0" +string(2) "a9" +string(3) "AAa" +string(2) "Zz" +string(3) "aaA" +string(2) "zZ" +string(3) "10a" +string(2) "9z" +string(3) "10A" +string(2) "9Z" +string(3) "5e7" +string(3) "5e6" +string(1) "e" +string(1) "d" +string(1) "E" +string(1) "D" +string(1) "5" +string(1) "4" diff --git a/ext/standard/tests/strings/str_increment_errors.phpt b/ext/standard/tests/strings/str_increment_errors.phpt new file mode 100644 index 0000000000000..41e4ac10e1078 --- /dev/null +++ b/ext/standard/tests/strings/str_increment_errors.phpt @@ -0,0 +1,53 @@ +--TEST-- +str_increment(): Invalid strings to increment should throw a ValueError +--FILE-- +getMessage(), PHP_EOL; + } +} + +?> +--EXPECT-- +str_increment(): Argument #1 ($string) cannot be empty +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters +str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters diff --git a/ext/standard/tests/strings/str_increment_polyfill.phpt b/ext/standard/tests/strings/str_increment_polyfill.phpt new file mode 100644 index 0000000000000..36ad262598f4e --- /dev/null +++ b/ext/standard/tests/strings/str_increment_polyfill.phpt @@ -0,0 +1,59 @@ +--TEST-- +Verifying that the str_increment() polyfill behaves the same +--FILE-- + 'e', + 'F' => 'E', + 'g' => 'f', + 'G' => 'F', + }; + return $s; + } + } + return ++$s; +} + +$strictlyAlphaNumeric = [ + "Az", + "aZ", + "A9", + "a9", + // Carrying values until the beginning of the string + "Zz", + "zZ", + "9z", + "9Z", + // string interpretable as a number in scientific notation + "5e6", + "5E6", + "5e9", + "5E9", + // Interned strings + "d", + "D", + "4", +]; + +foreach ($strictlyAlphaNumeric as $s) { + if (str_increment($s) !== polyfill($s)) { + var_dump("Error:", str_increment($s), polyfill($s)); + } +} + +echo "DONE"; + +?> +--EXPECT-- +DONE diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index aade7c5674f58..a8bddaac14fa3 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -939,6 +939,116 @@ PHP_METHOD(DoOperationNoCast, __construct) ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), l); } +static zend_class_entry *long_castable_no_operation_ce; +static zend_object_handlers long_castable_no_operation_object_handlers; + +static zend_object* long_castable_no_operation_object_create_ex(zend_class_entry* ce, zend_long l) { + zend_object *obj = zend_objects_new(ce); + object_properties_init(obj, ce); + obj->handlers = &long_castable_no_operation_object_handlers; + ZVAL_LONG(OBJ_PROP_NUM(obj, 0), l); + return obj; +} + +static zend_object *long_castable_no_operation_object_create(zend_class_entry *ce) +{ + return long_castable_no_operation_object_create_ex(ce, 0); +} + +static zend_result long_castable_no_operation_cast_object(zend_object *obj, zval *result, int type) +{ + if (type == IS_LONG) { + ZVAL_COPY(result, OBJ_PROP_NUM(obj, 0)); + return SUCCESS; + } + return FAILURE; +} + +PHP_METHOD(LongCastableNoOperations, __construct) +{ + zend_long l; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(l) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), l); +} + +static zend_class_entry *float_castable_no_operation_ce; +static zend_object_handlers float_castable_no_operation_object_handlers; + +static zend_object* float_castable_no_operation_object_create_ex(zend_class_entry* ce, double d) { + zend_object *obj = zend_objects_new(ce); + object_properties_init(obj, ce); + obj->handlers = &float_castable_no_operation_object_handlers; + ZVAL_DOUBLE(OBJ_PROP_NUM(obj, 0), d); + return obj; +} + +static zend_object *float_castable_no_operation_object_create(zend_class_entry *ce) +{ + return float_castable_no_operation_object_create_ex(ce, 0.0); +} + +static zend_result float_castable_no_operation_cast_object(zend_object *obj, zval *result, int type) +{ + if (type == IS_DOUBLE) { + ZVAL_COPY(result, OBJ_PROP_NUM(obj, 0)); + return SUCCESS; + } + return FAILURE; +} + +PHP_METHOD(FloatCastableNoOperations, __construct) +{ + double d; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_DOUBLE(d) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_DOUBLE(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), d); +} + +static zend_class_entry *numeric_castable_no_operation_ce; +static zend_object_handlers numeric_castable_no_operation_object_handlers; + +static zend_object* numeric_castable_no_operation_object_create_ex(zend_class_entry* ce, const zval *n) { + zend_object *obj = zend_objects_new(ce); + object_properties_init(obj, ce); + obj->handlers = &numeric_castable_no_operation_object_handlers; + ZVAL_COPY(OBJ_PROP_NUM(obj, 0), n); + return obj; +} + +static zend_object *numeric_castable_no_operation_object_create(zend_class_entry *ce) +{ + zval tmp; + ZVAL_LONG(&tmp, 0); + return numeric_castable_no_operation_object_create_ex(ce, &tmp); +} + +static zend_result numeric_castable_no_operation_cast_object(zend_object *obj, zval *result, int type) +{ + if (type == _IS_NUMBER) { + ZVAL_COPY(result, OBJ_PROP_NUM(obj, 0)); + return SUCCESS; + } + return FAILURE; +} + +PHP_METHOD(NumericCastableNoOperations, __construct) +{ + zval *n; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_NUMBER(n) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_COPY(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), n); +} + PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("zend_test.replace_zend_execute_ex", "0", PHP_INI_SYSTEM, OnUpdateBool, replace_zend_execute_ex, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.register_passes", "0", PHP_INI_SYSTEM, OnUpdateBool, register_passes, zend_zend_test_globals, zend_test_globals) @@ -1011,6 +1121,22 @@ PHP_MINIT_FUNCTION(zend_test) memcpy(&donc_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); donc_object_handlers.do_operation = donc_do_operation; + /* CastableNoOperation classes */ + long_castable_no_operation_ce = register_class_LongCastableNoOperations(); + long_castable_no_operation_ce->create_object = long_castable_no_operation_object_create; + memcpy(&long_castable_no_operation_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + long_castable_no_operation_object_handlers.cast_object = long_castable_no_operation_cast_object; + + float_castable_no_operation_ce = register_class_FloatCastableNoOperations(); + float_castable_no_operation_ce->create_object = float_castable_no_operation_object_create; + memcpy(&float_castable_no_operation_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + float_castable_no_operation_object_handlers.cast_object = float_castable_no_operation_cast_object; + + numeric_castable_no_operation_ce = register_class_NumericCastableNoOperations(); + numeric_castable_no_operation_ce->create_object = numeric_castable_no_operation_object_create; + memcpy(&numeric_castable_no_operation_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + numeric_castable_no_operation_object_handlers.cast_object = numeric_castable_no_operation_cast_object; + zend_register_functions(NULL, ext_function_legacy, NULL, EG(current_module)->type); // Loading via dl() not supported with the observer API diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 88d9f19883f88..62bdcb8bd52bf 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -132,6 +132,19 @@ final class DoOperationNoCast { public function __construct(int $val) {} } + final class LongCastableNoOperations { + private int $val; + public function __construct(int $val) {} + } + final class FloatCastableNoOperations { + private float $val; + public function __construct(float $val) {} + } + final class NumericCastableNoOperations { + private int|float $val; + public function __construct(int|float $val) {} + } + function zend_test_array_return(): array {} function zend_test_nullable_array_return(): null|array {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 6587b40e1e405..c47250ff27397 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 46e6d8be5504acb6ecbb722da4fabfcea4e8a354 */ + * Stub hash: 3c1b17bbb7ef84e036a251c685bd7fd79fe9f434 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -189,6 +189,16 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DoOperationNoCast___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, val, IS_LONG, 0) ZEND_END_ARG_INFO() +#define arginfo_class_LongCastableNoOperations___construct arginfo_class_DoOperationNoCast___construct + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_FloatCastableNoOperations___construct, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, val, IS_DOUBLE, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_NumericCastableNoOperations___construct, 0, 0, 1) + ZEND_ARG_TYPE_MASK(0, val, MAY_BE_LONG|MAY_BE_DOUBLE, NULL) +ZEND_END_ARG_INFO() + #if (PHP_VERSION_ID >= 80100) ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ZendTestNS_Foo_method, 0, 0, IS_LONG, 0) #else @@ -260,6 +270,9 @@ static ZEND_METHOD(ZendTestChildClassWithMethodWithParameterAttribute, override) static ZEND_METHOD(ZendTestForbidDynamicCall, call); static ZEND_METHOD(ZendTestForbidDynamicCall, callStatic); static ZEND_METHOD(DoOperationNoCast, __construct); +static ZEND_METHOD(LongCastableNoOperations, __construct); +static ZEND_METHOD(FloatCastableNoOperations, __construct); +static ZEND_METHOD(NumericCastableNoOperations, __construct); static ZEND_METHOD(ZendTestNS_Foo, method); static ZEND_METHOD(ZendTestNS_UnlikelyCompileError, method); static ZEND_METHOD(ZendTestNS2_Foo, method); @@ -403,6 +416,24 @@ static const zend_function_entry class_DoOperationNoCast_methods[] = { }; +static const zend_function_entry class_LongCastableNoOperations_methods[] = { + ZEND_ME(LongCastableNoOperations, __construct, arginfo_class_LongCastableNoOperations___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_FloatCastableNoOperations_methods[] = { + ZEND_ME(FloatCastableNoOperations, __construct, arginfo_class_FloatCastableNoOperations___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_NumericCastableNoOperations_methods[] = { + ZEND_ME(NumericCastableNoOperations, __construct, arginfo_class_NumericCastableNoOperations___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + static const zend_function_entry class_ZendTestNS_Foo_methods[] = { ZEND_ME(ZendTestNS_Foo, method, arginfo_class_ZendTestNS_Foo_method, ZEND_ACC_PUBLIC) ZEND_FE_END @@ -792,6 +823,57 @@ static zend_class_entry *register_class_DoOperationNoCast(void) return class_entry; } +static zend_class_entry *register_class_LongCastableNoOperations(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "LongCastableNoOperations", class_LongCastableNoOperations_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL; + + zval property_val_default_value; + ZVAL_UNDEF(&property_val_default_value); + zend_string *property_val_name = zend_string_init("val", sizeof("val") - 1, 1); + zend_declare_typed_property(class_entry, property_val_name, &property_val_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_val_name); + + return class_entry; +} + +static zend_class_entry *register_class_FloatCastableNoOperations(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "FloatCastableNoOperations", class_FloatCastableNoOperations_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL; + + zval property_val_default_value; + ZVAL_UNDEF(&property_val_default_value); + zend_string *property_val_name = zend_string_init("val", sizeof("val") - 1, 1); + zend_declare_typed_property(class_entry, property_val_name, &property_val_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_DOUBLE)); + zend_string_release(property_val_name); + + return class_entry; +} + +static zend_class_entry *register_class_NumericCastableNoOperations(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "NumericCastableNoOperations", class_NumericCastableNoOperations_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL; + + zval property_val_default_value; + ZVAL_UNDEF(&property_val_default_value); + zend_string *property_val_name = zend_string_init("val", sizeof("val") - 1, 1); + zend_declare_typed_property(class_entry, property_val_name, &property_val_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG|MAY_BE_DOUBLE)); + zend_string_release(property_val_name); + + return class_entry; +} + static zend_class_entry *register_class_ZendTestNS_Foo(void) { zend_class_entry ce, *class_entry; diff --git a/tests/lang/operators/postdec_variationStr.phpt b/tests/lang/operators/postdec_variationStr.phpt index cb452ccba9e66..25e5c646067a6 100644 --- a/tests/lang/operators/postdec_variationStr.phpt +++ b/tests/lang/operators/postdec_variationStr.phpt @@ -16,7 +16,7 @@ foreach ($strVals as $strVal) { } ?> ---EXPECT-- +--EXPECTF-- --- testing: '0' --- int(-1) --- testing: '65' --- @@ -28,20 +28,36 @@ float(0.19999999999999996) --- testing: '-7.7' --- float(-8.7) --- testing: 'abc' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(3) "abc" --- testing: '123abc' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(6) "123abc" --- testing: '123e5' --- float(12299999) --- testing: '123e5xyz' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(8) "123e5xyz" --- testing: ' 123abc' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(7) " 123abc" --- testing: '123 abc' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(7) "123 abc" --- testing: '123abc ' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(7) "123abc " --- testing: '3.4a' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(4) "3.4a" --- testing: 'a5.9' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(4) "a5.9" diff --git a/tests/lang/operators/postinc_variationStr.phpt b/tests/lang/operators/postinc_variationStr.phpt index 800b5c24552f7..a064b49835333 100644 --- a/tests/lang/operators/postinc_variationStr.phpt +++ b/tests/lang/operators/postinc_variationStr.phpt @@ -16,7 +16,7 @@ foreach ($strVals as $strVal) { } ?> ---EXPECT-- +--EXPECTF-- --- testing: '0' --- int(1) --- testing: '65' --- @@ -36,12 +36,22 @@ float(12300001) --- testing: '123e5xyz' --- string(8) "123e5xza" --- testing: ' 123abc' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(7) " 123abd" --- testing: '123 abc' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(7) "123 abd" --- testing: '123abc ' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(7) "123abc " --- testing: '3.4a' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(4) "3.4b" --- testing: 'a5.9' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(4) "a5.0" diff --git a/tests/lang/operators/predec_variationStr.phpt b/tests/lang/operators/predec_variationStr.phpt index ebbb6c9de3499..a98419690880c 100644 --- a/tests/lang/operators/predec_variationStr.phpt +++ b/tests/lang/operators/predec_variationStr.phpt @@ -15,7 +15,7 @@ foreach ($strVals as $strVal) { } ?> ---EXPECT-- +--EXPECTF-- --- testing: '0' --- int(-1) --- testing: '65' --- @@ -27,20 +27,36 @@ float(0.19999999999999996) --- testing: '-7.7' --- float(-8.7) --- testing: 'abc' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(3) "abc" --- testing: '123abc' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(6) "123abc" --- testing: '123e5' --- float(12299999) --- testing: '123e5xyz' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(8) "123e5xyz" --- testing: ' 123abc' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(7) " 123abc" --- testing: '123 abc' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(7) "123 abc" --- testing: '123abc ' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(7) "123abc " --- testing: '3.4a' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(4) "3.4a" --- testing: 'a5.9' --- + +Deprecated: Decrement on non-numeric string has no effect and is deprecated %s on line %d string(4) "a5.9" diff --git a/tests/lang/operators/preinc_variationStr.phpt b/tests/lang/operators/preinc_variationStr.phpt index abb3663889c1a..bcae316d8f9b7 100644 --- a/tests/lang/operators/preinc_variationStr.phpt +++ b/tests/lang/operators/preinc_variationStr.phpt @@ -16,7 +16,7 @@ foreach ($strVals as $strVal) { } ?> ---EXPECT-- +--EXPECTF-- --- testing: '0' --- int(1) --- testing: '65' --- @@ -36,14 +36,24 @@ float(12300001) --- testing: '123e5xyz' --- string(8) "123e5xza" --- testing: ' 123abc' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(7) " 123abd" --- testing: '123 abc' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(7) "123 abd" --- testing: '123abc ' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(7) "123abc " --- testing: '3.4a' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(4) "3.4b" --- testing: 'a5.9' --- + +Deprecated: Increment on non-alphanumeric string is deprecated in %s on line %d string(4) "a5.0" --- testing: 'z' --- string(2) "aa"