From 5f595a2b61262a7bf36945bff75007f82f4e3a51 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 7 Mar 2023 17:20:43 +0000 Subject: [PATCH 01/22] standard: range() now requires inputs of type string|int|float This is a BC break but currently passing arrays, objects, or resources doesn't throw as it should. --- ext/standard/array.c | 4 +- ext/standard/basic_functions.stub.php | 6 +- ext/standard/basic_functions_arginfo.h | 6 +- .../tests/array/range/range_variation.phpt | 619 ------------------ 4 files changed, 6 insertions(+), 629 deletions(-) delete mode 100644 ext/standard/tests/array/range/range_variation.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index 19269c36a9f8c..d765eb57d1cab 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2789,8 +2789,8 @@ PHP_FUNCTION(range) double step = 1.0; ZEND_PARSE_PARAMETERS_START(2, 3) - Z_PARAM_ZVAL(zlow) - Z_PARAM_ZVAL(zhigh) + Z_PARAM_NUMBER_OR_STR(zlow) + Z_PARAM_NUMBER_OR_STR(zhigh) Z_PARAM_OPTIONAL Z_PARAM_NUMBER(zstep) ZEND_PARSE_PARAMETERS_END(); diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index effb05ff9f982..9ce2224a78292 100755 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -1644,11 +1644,7 @@ function array_fill(int $start_index, int $count, mixed $value): array {} /** @refcount 1 */ function array_fill_keys(array $keys, mixed $value): array {} -/** - * @param string|int|float $start - * @param string|int|float $end - */ -function range($start, $end, int|float $step = 1): array {} +function range(string|int|float $start, string|int|float $end, int|float $step = 1): array {} function shuffle(array &$array): true {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 5612ee21867e8..1fb54cd02d8ef 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: 9cc9c0954bd7032d363ce9a531be621274b9a7e2 */ + * Stub hash: b0767630614e040866bd7ffdaf50dd31298a64f3 */ 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) @@ -181,8 +181,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_fill_keys, 0, 2, IS_ARRAY, ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_range, 0, 2, IS_ARRAY, 0) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) + ZEND_ARG_TYPE_MASK(0, start, MAY_BE_STRING|MAY_BE_LONG|MAY_BE_DOUBLE, NULL) + ZEND_ARG_TYPE_MASK(0, end, MAY_BE_STRING|MAY_BE_LONG|MAY_BE_DOUBLE, NULL) ZEND_ARG_TYPE_MASK(0, step, MAY_BE_LONG|MAY_BE_DOUBLE, "1") ZEND_END_ARG_INFO() diff --git a/ext/standard/tests/array/range/range_variation.phpt b/ext/standard/tests/array/range/range_variation.phpt deleted file mode 100644 index 0459634b5405d..0000000000000 --- a/ext/standard/tests/array/range/range_variation.phpt +++ /dev/null @@ -1,619 +0,0 @@ ---TEST-- -Test range() function (variation-1) ---INI-- -precision=14 ---FILE-- -getMessage(), "\n"; -} - -echo "Done\n"; -?> ---EXPECT-- -*** Testing range() with various low and high values *** --- creating an array with low = 'ABCD' and high = 'ABCD' -- -array(1) { - [0]=> - string(1) "A" -} - --- creating an array with low = 'ABCD' and high = '-10.5555' -- -array(11) { - [0]=> - float(0) - [1]=> - float(-1) - [2]=> - float(-2) - [3]=> - float(-3) - [4]=> - float(-4) - [5]=> - float(-5) - [6]=> - float(-6) - [7]=> - float(-7) - [8]=> - float(-8) - [9]=> - float(-9) - [10]=> - float(-10) -} - --- creating an array with low = 'ABCD' and high = '1' -- -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} - --- creating an array with low = 'ABCD' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = 'ABCD' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = 'ABCD' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = 'ABCD' and high = 'Array' -- -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} - --- creating an array with low = '-10.5555' and high = 'ABCD' -- -array(11) { - [0]=> - float(-10.5555) - [1]=> - float(-9.5555) - [2]=> - float(-8.5555) - [3]=> - float(-7.5555) - [4]=> - float(-6.5555) - [5]=> - float(-5.5555) - [6]=> - float(-4.5555) - [7]=> - float(-3.5555000000000003) - [8]=> - float(-2.5555000000000003) - [9]=> - float(-1.5555000000000003) - [10]=> - float(-0.5555000000000003) -} - --- creating an array with low = '-10.5555' and high = '-10.5555' -- -array(1) { - [0]=> - float(-10.5555) -} - --- creating an array with low = '-10.5555' and high = '1' -- -array(12) { - [0]=> - float(-10.5555) - [1]=> - float(-9.5555) - [2]=> - float(-8.5555) - [3]=> - float(-7.5555) - [4]=> - float(-6.5555) - [5]=> - float(-5.5555) - [6]=> - float(-4.5555) - [7]=> - float(-3.5555000000000003) - [8]=> - float(-2.5555000000000003) - [9]=> - float(-1.5555000000000003) - [10]=> - float(-0.5555000000000003) - [11]=> - float(0.4444999999999997) -} - --- creating an array with low = '-10.5555' and high = '' -- -array(11) { - [0]=> - float(-10.5555) - [1]=> - float(-9.5555) - [2]=> - float(-8.5555) - [3]=> - float(-7.5555) - [4]=> - float(-6.5555) - [5]=> - float(-5.5555) - [6]=> - float(-4.5555) - [7]=> - float(-3.5555000000000003) - [8]=> - float(-2.5555000000000003) - [9]=> - float(-1.5555000000000003) - [10]=> - float(-0.5555000000000003) -} - --- creating an array with low = '-10.5555' and high = '' -- -array(11) { - [0]=> - float(-10.5555) - [1]=> - float(-9.5555) - [2]=> - float(-8.5555) - [3]=> - float(-7.5555) - [4]=> - float(-6.5555) - [5]=> - float(-5.5555) - [6]=> - float(-4.5555) - [7]=> - float(-3.5555000000000003) - [8]=> - float(-2.5555000000000003) - [9]=> - float(-1.5555000000000003) - [10]=> - float(-0.5555000000000003) -} - --- creating an array with low = '-10.5555' and high = '' -- -array(11) { - [0]=> - float(-10.5555) - [1]=> - float(-9.5555) - [2]=> - float(-8.5555) - [3]=> - float(-7.5555) - [4]=> - float(-6.5555) - [5]=> - float(-5.5555) - [6]=> - float(-4.5555) - [7]=> - float(-3.5555000000000003) - [8]=> - float(-2.5555000000000003) - [9]=> - float(-1.5555000000000003) - [10]=> - float(-0.5555000000000003) -} - --- creating an array with low = '-10.5555' and high = 'Array' -- -array(12) { - [0]=> - float(-10.5555) - [1]=> - float(-9.5555) - [2]=> - float(-8.5555) - [3]=> - float(-7.5555) - [4]=> - float(-6.5555) - [5]=> - float(-5.5555) - [6]=> - float(-4.5555) - [7]=> - float(-3.5555000000000003) - [8]=> - float(-2.5555000000000003) - [9]=> - float(-1.5555000000000003) - [10]=> - float(-0.5555000000000003) - [11]=> - float(0.4444999999999997) -} - --- creating an array with low = '1' and high = 'ABCD' -- -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - --- creating an array with low = '1' and high = '-10.5555' -- -array(12) { - [0]=> - float(1) - [1]=> - float(0) - [2]=> - float(-1) - [3]=> - float(-2) - [4]=> - float(-3) - [5]=> - float(-4) - [6]=> - float(-5) - [7]=> - float(-6) - [8]=> - float(-7) - [9]=> - float(-8) - [10]=> - float(-9) - [11]=> - float(-10) -} - --- creating an array with low = '1' and high = '1' -- -array(1) { - [0]=> - int(1) -} - --- creating an array with low = '1' and high = '' -- -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - --- creating an array with low = '1' and high = '' -- -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - --- creating an array with low = '1' and high = '' -- -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - --- creating an array with low = '1' and high = 'Array' -- -array(1) { - [0]=> - int(1) -} - --- creating an array with low = '' and high = 'ABCD' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '-10.5555' -- -array(11) { - [0]=> - float(0) - [1]=> - float(-1) - [2]=> - float(-2) - [3]=> - float(-3) - [4]=> - float(-4) - [5]=> - float(-5) - [6]=> - float(-6) - [7]=> - float(-7) - [8]=> - float(-8) - [9]=> - float(-9) - [10]=> - float(-10) -} - --- creating an array with low = '' and high = '1' -- -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = 'Array' -- -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} - --- creating an array with low = '' and high = 'ABCD' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '-10.5555' -- -array(11) { - [0]=> - float(0) - [1]=> - float(-1) - [2]=> - float(-2) - [3]=> - float(-3) - [4]=> - float(-4) - [5]=> - float(-5) - [6]=> - float(-6) - [7]=> - float(-7) - [8]=> - float(-8) - [9]=> - float(-9) - [10]=> - float(-10) -} - --- creating an array with low = '' and high = '1' -- -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = 'Array' -- -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} - --- creating an array with low = '' and high = 'ABCD' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '-10.5555' -- -array(11) { - [0]=> - float(0) - [1]=> - float(-1) - [2]=> - float(-2) - [3]=> - float(-3) - [4]=> - float(-4) - [5]=> - float(-5) - [6]=> - float(-6) - [7]=> - float(-7) - [8]=> - float(-8) - [9]=> - float(-9) - [10]=> - float(-10) -} - --- creating an array with low = '' and high = '1' -- -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = '' -- -array(1) { - [0]=> - int(0) -} - --- creating an array with low = '' and high = 'Array' -- -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} - --- creating an array with low = 'Array' and high = 'ABCD' -- -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - --- creating an array with low = 'Array' and high = '-10.5555' -- -array(12) { - [0]=> - float(1) - [1]=> - float(0) - [2]=> - float(-1) - [3]=> - float(-2) - [4]=> - float(-3) - [5]=> - float(-4) - [6]=> - float(-5) - [7]=> - float(-6) - [8]=> - float(-7) - [9]=> - float(-8) - [10]=> - float(-9) - [11]=> - float(-10) -} - --- creating an array with low = 'Array' and high = '1' -- -array(1) { - [0]=> - int(1) -} - --- creating an array with low = 'Array' and high = '' -- -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - --- creating an array with low = 'Array' and high = '' -- -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - --- creating an array with low = 'Array' and high = '' -- -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - --- creating an array with low = 'Array' and high = 'Array' -- -array(1) { - [0]=> - int(1) -} - -*** Possible variatins with steps *** -array(5) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - [3]=> - int(4) - [4]=> - int(5) -} -range(): Argument #3 ($step) must be of type int|float, array given -Done From a8bbf40749da2b4a81368c2184a5d71e2cbd1015 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 7 Mar 2023 17:50:11 +0000 Subject: [PATCH 02/22] Split range test into numeric and string Add way more cases with strings to showcase the current madness. --- .../range_inputs_int_with_float_step.phpt | 17 ++ ...e.phpt => range_inputs_numeric_basic.phpt} | 153 +----------------- .../range/range_inputs_string_basic.phpt | 153 ++++++++++++++++++ .../range/range_inputs_string_invalid.phpt | 81 ++++++++++ 4 files changed, 253 insertions(+), 151 deletions(-) create mode 100644 ext/standard/tests/array/range/range_inputs_int_with_float_step.phpt rename ext/standard/tests/array/range/{range.phpt => range_inputs_numeric_basic.phpt} (61%) create mode 100644 ext/standard/tests/array/range/range_inputs_string_basic.phpt create mode 100644 ext/standard/tests/array/range/range_inputs_string_invalid.phpt diff --git a/ext/standard/tests/array/range/range_inputs_int_with_float_step.phpt b/ext/standard/tests/array/range/range_inputs_int_with_float_step.phpt new file mode 100644 index 0000000000000..f719a1866ab32 --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_int_with_float_step.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test range() function with integer inputs and float step +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + float(1) + [1]=> + float(3) + [2]=> + float(5) +} diff --git a/ext/standard/tests/array/range/range.phpt b/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt similarity index 61% rename from ext/standard/tests/array/range/range.phpt rename to ext/standard/tests/array/range/range_inputs_numeric_basic.phpt index c05cbfa9855d7..c2191106fe67b 100644 --- a/ext/standard/tests/array/range/range.phpt +++ b/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt @@ -1,13 +1,11 @@ --TEST-- -Test range() function +Test range() function with basic/expected numeric inputs --INI-- serialize_precision=14 --FILE-- --EXPECT-- -*** Testing range() function on basic operations *** - -- Integers as Low and High -- -- An array of elements from low to high -- array(10) { @@ -149,128 +135,11 @@ array(10) { int(1) } --- Chars as Low and High -- --- An array of elements from low to high -- -array(26) { - [0]=> - string(1) "a" - [1]=> - string(1) "b" - [2]=> - string(1) "c" - [3]=> - string(1) "d" - [4]=> - string(1) "e" - [5]=> - string(1) "f" - [6]=> - string(1) "g" - [7]=> - string(1) "h" - [8]=> - string(1) "i" - [9]=> - string(1) "j" - [10]=> - string(1) "k" - [11]=> - string(1) "l" - [12]=> - string(1) "m" - [13]=> - string(1) "n" - [14]=> - string(1) "o" - [15]=> - string(1) "p" - [16]=> - string(1) "q" - [17]=> - string(1) "r" - [18]=> - string(1) "s" - [19]=> - string(1) "t" - [20]=> - string(1) "u" - [21]=> - string(1) "v" - [22]=> - string(1) "w" - [23]=> - string(1) "x" - [24]=> - string(1) "y" - [25]=> - string(1) "z" -} - --- An array of elements from high to low -- -array(26) { - [0]=> - string(1) "z" - [1]=> - string(1) "y" - [2]=> - string(1) "x" - [3]=> - string(1) "w" - [4]=> - string(1) "v" - [5]=> - string(1) "u" - [6]=> - string(1) "t" - [7]=> - string(1) "s" - [8]=> - string(1) "r" - [9]=> - string(1) "q" - [10]=> - string(1) "p" - [11]=> - string(1) "o" - [12]=> - string(1) "n" - [13]=> - string(1) "m" - [14]=> - string(1) "l" - [15]=> - string(1) "k" - [16]=> - string(1) "j" - [17]=> - string(1) "i" - [18]=> - string(1) "h" - [19]=> - string(1) "g" - [20]=> - string(1) "f" - [21]=> - string(1) "e" - [22]=> - string(1) "d" - [23]=> - string(1) "c" - [24]=> - string(1) "b" - [25]=> - string(1) "a" -} - -- Low and High are equal -- array(1) { [0]=> int(5) } -array(1) { - [0]=> - string(1) "q" -} -- floats as Low and High -- array(6) { @@ -427,22 +296,4 @@ array(11) { [10]=> float(2) } - --- Testing basic string with step -- -array(7) { - [0]=> - string(1) "a" - [1]=> - string(1) "c" - [2]=> - string(1) "e" - [3]=> - string(1) "g" - [4]=> - string(1) "i" - [5]=> - string(1) "k" - [6]=> - string(1) "m" -} Done diff --git a/ext/standard/tests/array/range/range_inputs_string_basic.phpt b/ext/standard/tests/array/range/range_inputs_string_basic.phpt new file mode 100644 index 0000000000000..00ce49e6e4d2a --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_string_basic.phpt @@ -0,0 +1,153 @@ +--TEST-- +Test range() function with basic/expected string inputs +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECT-- +-- Chars as Low and High -- +-- An array of elements from low to high -- +array(26) { + [0]=> + string(1) "a" + [1]=> + string(1) "b" + [2]=> + string(1) "c" + [3]=> + string(1) "d" + [4]=> + string(1) "e" + [5]=> + string(1) "f" + [6]=> + string(1) "g" + [7]=> + string(1) "h" + [8]=> + string(1) "i" + [9]=> + string(1) "j" + [10]=> + string(1) "k" + [11]=> + string(1) "l" + [12]=> + string(1) "m" + [13]=> + string(1) "n" + [14]=> + string(1) "o" + [15]=> + string(1) "p" + [16]=> + string(1) "q" + [17]=> + string(1) "r" + [18]=> + string(1) "s" + [19]=> + string(1) "t" + [20]=> + string(1) "u" + [21]=> + string(1) "v" + [22]=> + string(1) "w" + [23]=> + string(1) "x" + [24]=> + string(1) "y" + [25]=> + string(1) "z" +} + +-- An array of elements from high to low -- +array(26) { + [0]=> + string(1) "z" + [1]=> + string(1) "y" + [2]=> + string(1) "x" + [3]=> + string(1) "w" + [4]=> + string(1) "v" + [5]=> + string(1) "u" + [6]=> + string(1) "t" + [7]=> + string(1) "s" + [8]=> + string(1) "r" + [9]=> + string(1) "q" + [10]=> + string(1) "p" + [11]=> + string(1) "o" + [12]=> + string(1) "n" + [13]=> + string(1) "m" + [14]=> + string(1) "l" + [15]=> + string(1) "k" + [16]=> + string(1) "j" + [17]=> + string(1) "i" + [18]=> + string(1) "h" + [19]=> + string(1) "g" + [20]=> + string(1) "f" + [21]=> + string(1) "e" + [22]=> + string(1) "d" + [23]=> + string(1) "c" + [24]=> + string(1) "b" + [25]=> + string(1) "a" +} + +-- Low and High are equal -- +array(1) { + [0]=> + string(1) "q" +} + +-- Testing basic string with step -- +array(4) { + [0]=> + string(1) "a" + [1]=> + string(1) "c" + [2]=> + string(1) "e" + [3]=> + string(1) "g" +} +Done diff --git a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt new file mode 100644 index 0000000000000..20040fac1cac2 --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt @@ -0,0 +1,81 @@ +--TEST-- +Test range() function with basic/expected string inputs +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECT-- +Range will ignore any byte after the first one +array(2) { + [0]=> + string(1) "A" + [1]=> + string(1) "B" +} +Range cannot operate on an empty string +array(1) { + [0]=> + int(0) +} +array(1) { + [0]=> + int(0) +} +Mixing numeric string and character +array(2) { + [0]=> + int(1) + [1]=> + int(0) +} +array(2) { + [0]=> + int(0) + [1]=> + int(1) +} +array(4) { + [0]=> + float(3.5) + [1]=> + float(2.5) + [2]=> + float(1.5) + [3]=> + float(0.5) +} +array(4) { + [0]=> + float(0) + [1]=> + float(1) + [2]=> + float(2) + [3]=> + float(3) +} +Fractional step cannot be used on character ranges +array(1) { + [0]=> + float(0) +} +Done From 7babd104a332b606fe27ff7b9fc94a763210e192 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 7 Mar 2023 18:16:29 +0000 Subject: [PATCH 03/22] Improve error handling for when step = 0 Have a clear error message for this case, and eliminate a bunch of useless conditions as this is now pre-checked --- ext/standard/array.c | 60 ++++---- .../tests/array/range/range_errors.phpt | 134 ------------------ .../tests/array/range/range_step_errors.phpt | 73 ++++++++++ 3 files changed, 102 insertions(+), 165 deletions(-) delete mode 100644 ext/standard/tests/array/range/range_errors.phpt create mode 100644 ext/standard/tests/array/range/range_step_errors.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index d765eb57d1cab..276191362a6b7 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2757,8 +2757,8 @@ PHP_FUNCTION(array_fill_keys) } /* }}} */ -#define RANGE_CHECK_DOUBLE_INIT_ARRAY(start, end) do { \ - double __calc_size = ((start - end) / step) + 1; \ +#define RANGE_CHECK_DOUBLE_INIT_ARRAY(start, end, _step) do { \ + double __calc_size = ((start - end) / (_step)) + 1; \ if (__calc_size >= (double)HT_MAX_SIZE) { \ zend_value_error(\ "The supplied range exceeds the maximum array size: start=%0.0f end=%0.0f", end, start); \ @@ -2784,24 +2784,31 @@ PHP_FUNCTION(array_fill_keys) /* {{{ Create an array containing the range of integers or characters from low to high (inclusive) */ PHP_FUNCTION(range) { - zval *zlow, *zhigh, *zstep = NULL, tmp; - int err = 0, is_step_double = 0; - double step = 1.0; + zval *zlow, *zhigh, *user_step = NULL, tmp; + bool err = 0, is_step_double = false; + double step_double = 1.0; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_NUMBER_OR_STR(zlow) Z_PARAM_NUMBER_OR_STR(zhigh) Z_PARAM_OPTIONAL - Z_PARAM_NUMBER(zstep) + Z_PARAM_NUMBER(user_step) ZEND_PARSE_PARAMETERS_END(); - if (zstep) { - is_step_double = Z_TYPE_P(zstep) == IS_DOUBLE; - step = zval_get_double(zstep); - + if (user_step) { + if (UNEXPECTED(Z_TYPE_P(user_step) == IS_DOUBLE)) { + step_double = Z_DVAL_P(user_step); + is_step_double = true; + } else { + step_double = (double) Z_LVAL_P(user_step); + } + if (step_double == 0.0) { + zend_argument_value_error(3, "cannot be 0"); + RETURN_THROWS(); + } /* We only want positive step values. */ - if (step < 0.0) { - step *= -1; + if (step_double < 0.0) { + step_double *= -1; } } @@ -2809,7 +2816,7 @@ PHP_FUNCTION(range) if (Z_TYPE_P(zlow) == IS_STRING && Z_TYPE_P(zhigh) == IS_STRING && Z_STRLEN_P(zlow) >= 1 && Z_STRLEN_P(zhigh) >= 1) { int type1, type2; unsigned char low, high; - zend_long lstep = (zend_long) step; + zend_long lstep = (zend_long) step_double; type1 = is_numeric_string(Z_STRVAL_P(zlow), Z_STRLEN_P(zlow), NULL, NULL, 0); type2 = is_numeric_string(Z_STRVAL_P(zhigh), Z_STRLEN_P(zhigh), NULL, NULL, 0); @@ -2824,7 +2831,7 @@ PHP_FUNCTION(range) high = (unsigned char)Z_STRVAL_P(zhigh)[0]; if (low > high) { /* Negative Steps */ - if (low - high < lstep || lstep <= 0) { + if (low - high < lstep) { err = 1; goto err; } @@ -2841,7 +2848,7 @@ PHP_FUNCTION(range) } } ZEND_HASH_FILL_END(); } else if (high > low) { /* Positive Steps */ - if (high - low < lstep || lstep <= 0) { + if (high - low < lstep) { err = 1; goto err; } @@ -2874,29 +2881,29 @@ PHP_FUNCTION(range) } if (low > high) { /* Negative steps */ - if (low - high < step || step <= 0) { + if (low - high < step_double) { err = 1; goto err; } - RANGE_CHECK_DOUBLE_INIT_ARRAY(low, high); + RANGE_CHECK_DOUBLE_INIT_ARRAY(low, high, step_double); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { - for (i = 0, element = low; i < size && element >= high; ++i, element = low - (i * step)) { + for (i = 0, element = low; i < size && element >= high; ++i, element = low - (i * step_double)) { ZEND_HASH_FILL_SET_DOUBLE(element); ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); } else if (high > low) { /* Positive steps */ - if (high - low < step || step <= 0) { + if (high - low < step_double) { err = 1; goto err; } - RANGE_CHECK_DOUBLE_INIT_ARRAY(high, low); + RANGE_CHECK_DOUBLE_INIT_ARRAY(high, low, step_double); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { - for (i = 0, element = low; i < size && element <= high; ++i, element = low + (i * step)) { + for (i = 0, element = low; i < size && element <= high; ++i, element = low + (i * step_double)) { ZEND_HASH_FILL_SET_DOUBLE(element); ZEND_HASH_FILL_NEXT(); } @@ -2915,16 +2922,7 @@ PHP_FUNCTION(range) low = zval_get_long(zlow); high = zval_get_long(zhigh); - if (step <= 0) { - err = 1; - goto err; - } - - lstep = (zend_ulong)step; - if (step <= 0) { - err = 1; - goto err; - } + lstep = (zend_ulong)step_double; if (low > high) { /* Negative steps */ if ((zend_ulong)low - high < lstep) { diff --git a/ext/standard/tests/array/range/range_errors.phpt b/ext/standard/tests/array/range/range_errors.phpt deleted file mode 100644 index e8bdf376e3c36..0000000000000 --- a/ext/standard/tests/array/range/range_errors.phpt +++ /dev/null @@ -1,134 +0,0 @@ ---TEST-- -Test range() function (errors) ---INI-- -precision=14 ---FILE-- -getMessage(), "\n"; -} - -try { - var_dump( range("a", "b", 0) ); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -echo "\n\n-- Testing ( (low > high) && (step = 0) ) --\n"; -try { - var_dump( range(2, 1, 0) ); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -try { - var_dump( range("b", "a", 0) ); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -echo "\n\n-- Testing ( (low < high) && (high-low < step) ) --\n"; -try { - var_dump( range(1.0, 7.0, 6.5) ); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -echo "\n\n-- Testing ( (low > high) && (low-high < step) ) --\n"; -try { - var_dump( range(7.0, 1.0, 6.5) ); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -echo "\n\n-- Testing ( (low < high) && (high-low < step) ) for characters --\n"; -try { - var_dump(range('a', 'z', 100)); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -echo "\n\n-- Testing ( (low > high) && (low-high < step) ) for characters --\n"; -try { - var_dump(range('z', 'a', 100)); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -echo "\n-- Testing other conditions --\n"; -try { - var_dump( range(-1, -2, 2) ); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -try { - var_dump( range("a", "j", "z") ); -} catch (\TypeError $e) { - echo $e->getMessage(), "\n"; -} - -try { - var_dump( range(0, 1, "140962482048819216326.24") ); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} - -echo "\n-- Testing Invalid steps --\n"; -$step_arr = array( "string", NULL, FALSE, "", "\0" ); - -foreach( $step_arr as $step ) { - try { - var_dump( range( 1, 5, $step ) ); - } catch (\TypeError | \ValueError $e) { - echo $e->getMessage(), "\n"; - } -} -?> ---EXPECTF-- -*** Testing error conditions *** - --- Testing ( (low < high) && (step = 0) ) -- -range(): Argument #3 ($step) must not exceed the specified range -range(): Argument #3 ($step) must not exceed the specified range - - --- Testing ( (low > high) && (step = 0) ) -- -range(): Argument #3 ($step) must not exceed the specified range -range(): Argument #3 ($step) must not exceed the specified range - - --- Testing ( (low < high) && (high-low < step) ) -- -range(): Argument #3 ($step) must not exceed the specified range - - --- Testing ( (low > high) && (low-high < step) ) -- -range(): Argument #3 ($step) must not exceed the specified range - - --- Testing ( (low < high) && (high-low < step) ) for characters -- -range(): Argument #3 ($step) must not exceed the specified range - - --- Testing ( (low > high) && (low-high < step) ) for characters -- -range(): Argument #3 ($step) must not exceed the specified range - --- Testing other conditions -- -range(): Argument #3 ($step) must not exceed the specified range -range(): Argument #3 ($step) must be of type int|float, string given -range(): Argument #3 ($step) must not exceed the specified range - --- Testing Invalid steps -- -range(): Argument #3 ($step) must be of type int|float, string given - -Deprecated: range(): Passing null to parameter #3 ($step) of type int|float is deprecated in %s on line %d -range(): Argument #3 ($step) must not exceed the specified range -range(): Argument #3 ($step) must not exceed the specified range -range(): Argument #3 ($step) must be of type int|float, string given -range(): Argument #3 ($step) must be of type int|float, string given diff --git a/ext/standard/tests/array/range/range_step_errors.phpt b/ext/standard/tests/array/range/range_step_errors.phpt new file mode 100644 index 0000000000000..fcf9dee04e613 --- /dev/null +++ b/ext/standard/tests/array/range/range_step_errors.phpt @@ -0,0 +1,73 @@ +--TEST-- +range() programattic errors for the $step parameter +--INI-- +precision=14 +--FILE-- +getMessage(), "\n"; +} +try { + var_dump( range(1, 7, 0) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump( range('A', 'H', 0) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump( range('A', 'H', 0.0) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "Step must be within the range of input parameters\n"; +echo "-- Testing ( (low < high) && (high-low < step) ) --\n"; +try { + var_dump( range(1.0, 7.0, 6.5) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "-- Testing ( (low > high) && (low-high < step) ) --\n"; +try { + var_dump( range(7.0, 1.0, 6.5) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "-- Testing ( (low < high) && (high-low < step) ) for characters --\n"; +try { + var_dump(range('a', 'z', 100)); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "-- Testing ( (low > high) && (low-high < step) ) for characters --\n"; +try { + var_dump(range('z', 'a', 100)); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Step cannot be 0 +range(): Argument #3 ($step) cannot be 0 +range(): Argument #3 ($step) cannot be 0 +range(): Argument #3 ($step) cannot be 0 +range(): Argument #3 ($step) cannot be 0 +Step must be within the range of input parameters +-- Testing ( (low < high) && (high-low < step) ) -- +range(): Argument #3 ($step) must not exceed the specified range +-- Testing ( (low > high) && (low-high < step) ) -- +range(): Argument #3 ($step) must not exceed the specified range +-- Testing ( (low < high) && (high-low < step) ) for characters -- +range(): Argument #3 ($step) must not exceed the specified range +-- Testing ( (low > high) && (low-high < step) ) for characters -- +range(): Argument #3 ($step) must not exceed the specified range From 5cb022566b508030140f6f2224a3956dc4b7c7e5 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 7 Mar 2023 18:37:20 +0000 Subject: [PATCH 04/22] Store long step and see if float step is compatible with long This changes the values of e.g. range(1, 5, 2.0); from being floats to ints. --- ext/standard/array.c | 60 +++++++++++-------- .../range_inputs_int_with_float_step.phpt | 6 +- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 276191362a6b7..4c5018093215c 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2769,8 +2769,8 @@ PHP_FUNCTION(array_fill_keys) zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); \ } while (0) -#define RANGE_CHECK_LONG_INIT_ARRAY(start, end) do { \ - zend_ulong __calc_size = ((zend_ulong) start - end) / lstep; \ +#define RANGE_CHECK_LONG_INIT_ARRAY(start, end, _step) do { \ + zend_ulong __calc_size = ((zend_ulong) start - end) / (_step); \ if (__calc_size >= HT_MAX_SIZE - 1) { \ zend_value_error(\ "The supplied range exceeds the maximum array size: start=" ZEND_LONG_FMT " end=" ZEND_LONG_FMT, end, start); \ @@ -2787,6 +2787,7 @@ PHP_FUNCTION(range) zval *zlow, *zhigh, *user_step = NULL, tmp; bool err = 0, is_step_double = false; double step_double = 1.0; + zend_long step = 1; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_NUMBER_OR_STR(zlow) @@ -2798,25 +2799,32 @@ PHP_FUNCTION(range) if (user_step) { if (UNEXPECTED(Z_TYPE_P(user_step) == IS_DOUBLE)) { step_double = Z_DVAL_P(user_step); - is_step_double = true; + /* We only want positive step values. */ + if (step_double < 0.0) { + step_double *= -1; + } + step = zend_dval_to_lval(step_double); + if (!zend_is_long_compatible(step_double, step)) { + is_step_double = true; + } } else { - step_double = (double) Z_LVAL_P(user_step); + step = Z_LVAL_P(user_step); + /* We only want positive step values. */ + if (step < 0) { + step *= -1; + } + step_double = (double) step; } if (step_double == 0.0) { zend_argument_value_error(3, "cannot be 0"); RETURN_THROWS(); } - /* We only want positive step values. */ - if (step_double < 0.0) { - step_double *= -1; - } } /* If the range is given as strings, generate an array of characters. */ if (Z_TYPE_P(zlow) == IS_STRING && Z_TYPE_P(zhigh) == IS_STRING && Z_STRLEN_P(zlow) >= 1 && Z_STRLEN_P(zhigh) >= 1) { int type1, type2; unsigned char low, high; - zend_long lstep = (zend_long) step_double; type1 = is_numeric_string(Z_STRVAL_P(zlow), Z_STRLEN_P(zlow), NULL, NULL, 0); type2 = is_numeric_string(Z_STRVAL_P(zhigh), Z_STRLEN_P(zhigh), NULL, NULL, 0); @@ -2831,34 +2839,34 @@ PHP_FUNCTION(range) high = (unsigned char)Z_STRVAL_P(zhigh)[0]; if (low > high) { /* Negative Steps */ - if (low - high < lstep) { + if (low - high < step) { err = 1; goto err; } /* Initialize the return_value as an array. */ - array_init_size(return_value, (uint32_t)(((low - high) / lstep) + 1)); + array_init_size(return_value, (uint32_t)(((low - high) / step) + 1)); zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { - for (; low >= high; low -= (unsigned int)lstep) { + for (; low >= high; low -= (unsigned int)step) { ZEND_HASH_FILL_SET_INTERNED_STR(ZSTR_CHAR(low)); ZEND_HASH_FILL_NEXT(); - if (((signed int)low - lstep) < 0) { + if (((signed int)low - step) < 0) { break; } } } ZEND_HASH_FILL_END(); } else if (high > low) { /* Positive Steps */ - if (high - low < lstep) { + if (high - low < step) { err = 1; goto err; } - array_init_size(return_value, (uint32_t)(((high - low) / lstep) + 1)); + array_init_size(return_value, (uint32_t)(((high - low) / step) + 1)); zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { - for (; low <= high; low += (unsigned int)lstep) { + for (; low <= high; low += (unsigned int)step) { ZEND_HASH_FILL_SET_INTERNED_STR(ZSTR_CHAR(low)); ZEND_HASH_FILL_NEXT(); - if (((signed int)low + lstep) > 255) { + if (((signed int)low + step) > 255) { break; } } @@ -2915,40 +2923,40 @@ PHP_FUNCTION(range) } } else { zend_long low, high; - /* lstep is a zend_ulong so that comparisons to it don't overflow, i.e. low - high < lstep */ - zend_ulong lstep; + /* unsigned_step is a zend_ulong so that comparisons to it don't overflow, i.e. low - high < lstep */ + zend_ulong unsigned_step; uint32_t i, size; long_str: low = zval_get_long(zlow); high = zval_get_long(zhigh); - lstep = (zend_ulong)step_double; + unsigned_step = (zend_ulong)step; if (low > high) { /* Negative steps */ - if ((zend_ulong)low - high < lstep) { + if ((zend_ulong)low - high < unsigned_step) { err = 1; goto err; } - RANGE_CHECK_LONG_INIT_ARRAY(low, high); + RANGE_CHECK_LONG_INIT_ARRAY(low, high, unsigned_step); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { for (i = 0; i < size; ++i) { - ZEND_HASH_FILL_SET_LONG(low - (i * lstep)); + ZEND_HASH_FILL_SET_LONG(low - (i * unsigned_step)); ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); } else if (high > low) { /* Positive steps */ - if ((zend_ulong)high - low < lstep) { + if ((zend_ulong)high - low < unsigned_step) { err = 1; goto err; } - RANGE_CHECK_LONG_INIT_ARRAY(high, low); + RANGE_CHECK_LONG_INIT_ARRAY(high, low, unsigned_step); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { for (i = 0; i < size; ++i) { - ZEND_HASH_FILL_SET_LONG(low + (i * lstep)); + ZEND_HASH_FILL_SET_LONG(low + (i * unsigned_step)); ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); diff --git a/ext/standard/tests/array/range/range_inputs_int_with_float_step.phpt b/ext/standard/tests/array/range/range_inputs_int_with_float_step.phpt index f719a1866ab32..2d479ac49fde7 100644 --- a/ext/standard/tests/array/range/range_inputs_int_with_float_step.phpt +++ b/ext/standard/tests/array/range/range_inputs_int_with_float_step.phpt @@ -9,9 +9,9 @@ var_dump( range(1, 5, 2.0) ); --EXPECT-- array(3) { [0]=> - float(1) + int(1) [1]=> - float(3) + int(3) [2]=> - float(5) + int(5) } From a866cd9ffe8db8bea6235a451f50f6c27416e489 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 7 Mar 2023 18:52:13 +0000 Subject: [PATCH 05/22] Test for NAN values --- .../range/range_inputs_float_NAN_values.phpt | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt diff --git a/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt new file mode 100644 index 0000000000000..bf4891dc9fd98 --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt @@ -0,0 +1,110 @@ +--TEST-- +Test range() function with integer inputs and float step +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECT-- +float(NAN) +float(NAN) +float(NAN) +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, 5.5); +array(1) { + [0]=> + float(NAN) +} +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, 5.5); +array(1) { + [0]=> + float(NAN) +} +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, NAN); +array(1) { + [0]=> + float(NAN) +} +range(NAN, 5.5); +array(1) { + [0]=> + float(NAN) +} +range(5.5, NAN); +array(1) { + [0]=> + float(5.5) +} +range(5.5, NAN); +array(1) { + [0]=> + float(5.5) +} +range(5.5, NAN); +array(1) { + [0]=> + float(5.5) +} +range(5.5, 5.5); +array(1) { + [0]=> + float(5.5) +} From 7ccc3222f23e4044f0e4c0b7c50c0cf8bdba1db7 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 8 Mar 2023 16:11:08 +0000 Subject: [PATCH 06/22] Throw ValueError for INF or NAN step --- ext/standard/array.c | 10 +++++ .../tests/array/range/range_step_errors.phpt | 40 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/ext/standard/array.c b/ext/standard/array.c index 4c5018093215c..fd3e440c1b5ba 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2799,6 +2799,16 @@ PHP_FUNCTION(range) if (user_step) { if (UNEXPECTED(Z_TYPE_P(user_step) == IS_DOUBLE)) { step_double = Z_DVAL_P(user_step); + + if (zend_isinf(step_double)) { + zend_argument_value_error(3, "must be a finite number, INF provided"); + RETURN_THROWS(); + } + if (zend_isnan(step_double)) { + zend_argument_value_error(3, "must be a finite number, NAN provided"); + RETURN_THROWS(); + } + /* We only want positive step values. */ if (step_double < 0.0) { step_double *= -1; diff --git a/ext/standard/tests/array/range/range_step_errors.phpt b/ext/standard/tests/array/range/range_step_errors.phpt index fcf9dee04e613..35a5ecf8a83cd 100644 --- a/ext/standard/tests/array/range/range_step_errors.phpt +++ b/ext/standard/tests/array/range/range_step_errors.phpt @@ -25,6 +25,38 @@ try { } catch (\ValueError $e) { echo $e->getMessage(), "\n"; } +echo "Step cannot be INF\n"; +try { + var_dump( range(1.0, 7.0, 10.0**400) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump( range(1, 7, 10.0**400) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump( range('A', 'H', 10.0**400) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +echo "Step cannot be NAN\n"; +try { + var_dump( range(1.0, 7.0, fdiv(0, 0)) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump( range(1, 7, fdiv(0, 0)) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump( range('A', 'H', fdiv(0, 0)) ); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} echo "Step must be within the range of input parameters\n"; echo "-- Testing ( (low < high) && (high-low < step) ) --\n"; @@ -62,6 +94,14 @@ range(): Argument #3 ($step) cannot be 0 range(): Argument #3 ($step) cannot be 0 range(): Argument #3 ($step) cannot be 0 range(): Argument #3 ($step) cannot be 0 +Step cannot be INF +range(): Argument #3 ($step) must be a finite number, INF provided +range(): Argument #3 ($step) must be a finite number, INF provided +range(): Argument #3 ($step) must be a finite number, INF provided +Step cannot be NAN +range(): Argument #3 ($step) must be a finite number, NAN provided +range(): Argument #3 ($step) must be a finite number, NAN provided +range(): Argument #3 ($step) must be a finite number, NAN provided Step must be within the range of input parameters -- Testing ( (low < high) && (high-low < step) ) -- range(): Argument #3 ($step) must not exceed the specified range From a67625567efd69b3faa850f50c4f481fae51010b Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 8 Mar 2023 16:23:12 +0000 Subject: [PATCH 07/22] Add test with slightly unusual string inputs --- .../range/range_inputs_string_variations.phpt | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 ext/standard/tests/array/range/range_inputs_string_variations.phpt diff --git a/ext/standard/tests/array/range/range_inputs_string_variations.phpt b/ext/standard/tests/array/range/range_inputs_string_variations.phpt new file mode 100644 index 0000000000000..27dd535fe5d80 --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_string_variations.phpt @@ -0,0 +1,148 @@ +--TEST-- +Test range() function with unexpected string input variations or unusual step. +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECT-- +int compatible float as step +array(4) { + [0]=> + string(1) "a" + [1]=> + string(1) "c" + [2]=> + string(1) "e" + [3]=> + string(1) "g" +} +A to z range() +array(58) { + [0]=> + string(1) "A" + [1]=> + string(1) "B" + [2]=> + string(1) "C" + [3]=> + string(1) "D" + [4]=> + string(1) "E" + [5]=> + string(1) "F" + [6]=> + string(1) "G" + [7]=> + string(1) "H" + [8]=> + string(1) "I" + [9]=> + string(1) "J" + [10]=> + string(1) "K" + [11]=> + string(1) "L" + [12]=> + string(1) "M" + [13]=> + string(1) "N" + [14]=> + string(1) "O" + [15]=> + string(1) "P" + [16]=> + string(1) "Q" + [17]=> + string(1) "R" + [18]=> + string(1) "S" + [19]=> + string(1) "T" + [20]=> + string(1) "U" + [21]=> + string(1) "V" + [22]=> + string(1) "W" + [23]=> + string(1) "X" + [24]=> + string(1) "Y" + [25]=> + string(1) "Z" + [26]=> + string(1) "[" + [27]=> + string(1) "\" + [28]=> + string(1) "]" + [29]=> + string(1) "^" + [30]=> + string(1) "_" + [31]=> + string(1) "`" + [32]=> + string(1) "a" + [33]=> + string(1) "b" + [34]=> + string(1) "c" + [35]=> + string(1) "d" + [36]=> + string(1) "e" + [37]=> + string(1) "f" + [38]=> + string(1) "g" + [39]=> + string(1) "h" + [40]=> + string(1) "i" + [41]=> + string(1) "j" + [42]=> + string(1) "k" + [43]=> + string(1) "l" + [44]=> + string(1) "m" + [45]=> + string(1) "n" + [46]=> + string(1) "o" + [47]=> + string(1) "p" + [48]=> + string(1) "q" + [49]=> + string(1) "r" + [50]=> + string(1) "s" + [51]=> + string(1) "t" + [52]=> + string(1) "u" + [53]=> + string(1) "v" + [54]=> + string(1) "w" + [55]=> + string(1) "x" + [56]=> + string(1) "y" + [57]=> + string(1) "z" +} +Done From 019b692919698fef3ca9d3e9a2faa98d7cf8a509 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 8 Mar 2023 16:31:14 +0000 Subject: [PATCH 08/22] Rename variables --- ext/standard/array.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index fd3e440c1b5ba..f337b38c874b0 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2784,14 +2784,14 @@ PHP_FUNCTION(array_fill_keys) /* {{{ Create an array containing the range of integers or characters from low to high (inclusive) */ PHP_FUNCTION(range) { - zval *zlow, *zhigh, *user_step = NULL, tmp; + zval *user_start, *user_end, *user_step = NULL, tmp; bool err = 0, is_step_double = false; double step_double = 1.0; zend_long step = 1; ZEND_PARSE_PARAMETERS_START(2, 3) - Z_PARAM_NUMBER_OR_STR(zlow) - Z_PARAM_NUMBER_OR_STR(zhigh) + Z_PARAM_NUMBER_OR_STR(user_start) + Z_PARAM_NUMBER_OR_STR(user_end) Z_PARAM_OPTIONAL Z_PARAM_NUMBER(user_step) ZEND_PARSE_PARAMETERS_END(); @@ -2832,12 +2832,12 @@ PHP_FUNCTION(range) } /* If the range is given as strings, generate an array of characters. */ - if (Z_TYPE_P(zlow) == IS_STRING && Z_TYPE_P(zhigh) == IS_STRING && Z_STRLEN_P(zlow) >= 1 && Z_STRLEN_P(zhigh) >= 1) { + if (Z_TYPE_P(user_start) == IS_STRING && Z_TYPE_P(user_end) == IS_STRING && Z_STRLEN_P(user_start) >= 1 && Z_STRLEN_P(user_end) >= 1) { int type1, type2; unsigned char low, high; - type1 = is_numeric_string(Z_STRVAL_P(zlow), Z_STRLEN_P(zlow), NULL, NULL, 0); - type2 = is_numeric_string(Z_STRVAL_P(zhigh), Z_STRLEN_P(zhigh), NULL, NULL, 0); + type1 = is_numeric_string(Z_STRVAL_P(user_start), Z_STRLEN_P(user_start), NULL, NULL, 0); + type2 = is_numeric_string(Z_STRVAL_P(user_end), Z_STRLEN_P(user_end), NULL, NULL, 0); if (type1 == IS_DOUBLE || type2 == IS_DOUBLE || is_step_double) { goto double_str; @@ -2845,8 +2845,8 @@ PHP_FUNCTION(range) goto long_str; } - low = (unsigned char)Z_STRVAL_P(zlow)[0]; - high = (unsigned char)Z_STRVAL_P(zhigh)[0]; + low = (unsigned char)Z_STRVAL_P(user_start)[0]; + high = (unsigned char)Z_STRVAL_P(user_end)[0]; if (low > high) { /* Negative Steps */ if (low - high < step) { @@ -2886,12 +2886,12 @@ PHP_FUNCTION(range) ZVAL_CHAR(&tmp, low); zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); } - } else if (Z_TYPE_P(zlow) == IS_DOUBLE || Z_TYPE_P(zhigh) == IS_DOUBLE || is_step_double) { + } else if (Z_TYPE_P(user_start) == IS_DOUBLE || Z_TYPE_P(user_end) == IS_DOUBLE || is_step_double) { double low, high, element; uint32_t i, size; double_str: - low = zval_get_double(zlow); - high = zval_get_double(zhigh); + low = zval_get_double(user_start); + high = zval_get_double(user_end); if (zend_isinf(high) || zend_isinf(low)) { zend_value_error("Invalid range supplied: start=%0.0f end=%0.0f", low, high); @@ -2937,8 +2937,8 @@ PHP_FUNCTION(range) zend_ulong unsigned_step; uint32_t i, size; long_str: - low = zval_get_long(zlow); - high = zval_get_long(zhigh); + low = zval_get_long(user_start); + high = zval_get_long(user_end); unsigned_step = (zend_ulong)step; From df3f9b2a2576acb959f9db5a64c421d328ff691e Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 8 Mar 2023 17:54:30 +0000 Subject: [PATCH 09/22] Add warnings when doing something dodgy with range() --- ext/standard/array.c | 171 +++++++++++++----- ext/standard/tests/array/range/bug32021.phpt | 5 +- .../tests/array/range/range_bug70239_0.phpt | 2 +- .../tests/array/range/range_bug70239_1.phpt | 2 +- .../range/range_inputs_float_NAN_values.phpt | 81 ++------- .../range/range_inputs_string_invalid.phpt | 24 ++- 6 files changed, 173 insertions(+), 112 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index f337b38c874b0..502b99a03737f 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2781,6 +2781,60 @@ PHP_FUNCTION(array_fill_keys) zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); \ } while (0) +static uint8_t php_range_process_input(const zval *input, uint32_t arg_num, zend_long /* restrict */ *lval, double /* restrict */ *dval) +{ + switch (Z_TYPE_P(input)) { + case IS_LONG: + *lval = Z_LVAL_P(input); + *dval = (double) Z_LVAL_P(input); + return IS_LONG; + case IS_DOUBLE: + *dval = Z_DVAL_P(input); + check_dval_value: + if (zend_isinf(*dval)) { + zend_argument_value_error(arg_num, "must be a finite number, INF provided"); + return 0; + } + if (zend_isnan(*dval)) { + zend_argument_value_error(arg_num, "must be a finite number, NAN provided"); + return 0; + } + return IS_DOUBLE; + case IS_STRING: { + if (Z_STRLEN_P(input) == 0) { + const char *arg_name = get_active_function_arg_name(arg_num); + php_error_docref(NULL, E_WARNING, "Argument #%d ($%s) must not be empty, casted to 0", arg_num, arg_name); + if (UNEXPECTED(EG(exception))) { + return 0; + } + *lval = 0; + *dval = 0.0; + return IS_LONG; + } + uint8_t type = is_numeric_str_function(Z_STR_P(input), lval, dval); + if (type == IS_DOUBLE) { + goto check_dval_value; + } + if (type == IS_LONG) { + *dval = (double) *lval; + return IS_LONG; + } + if (Z_STRLEN_P(input) != 1) { + const char *arg_name = get_active_function_arg_name(arg_num); + php_error_docref(NULL, E_WARNING, "Argument #%d ($%s) must be a single byte, subsequent bytes are ignored", arg_num, arg_name); + if (UNEXPECTED(EG(exception))) { + return 0; + } + } + /* Set fall back values to 0 in case the other argument is not a string */ + *lval = 0; + *dval = 0.0; + return IS_STRING; + } + EMPTY_SWITCH_DEFAULT_CASE(); + } +} + /* {{{ Create an array containing the range of integers or characters from low to high (inclusive) */ PHP_FUNCTION(range) { @@ -2831,22 +2885,54 @@ PHP_FUNCTION(range) } } - /* If the range is given as strings, generate an array of characters. */ - if (Z_TYPE_P(user_start) == IS_STRING && Z_TYPE_P(user_end) == IS_STRING && Z_STRLEN_P(user_start) >= 1 && Z_STRLEN_P(user_end) >= 1) { - int type1, type2; - unsigned char low, high; + uint8_t start_type; + double start_double; + zend_long start_long; + uint8_t end_type; + double end_double; + zend_long end_long; - type1 = is_numeric_string(Z_STRVAL_P(user_start), Z_STRLEN_P(user_start), NULL, NULL, 0); - type2 = is_numeric_string(Z_STRVAL_P(user_end), Z_STRLEN_P(user_end), NULL, NULL, 0); + start_type = php_range_process_input(user_start, 1, &start_long, &start_double); + if (start_type == 0) { + RETURN_THROWS(); + } + end_type = php_range_process_input(user_end, 2, &end_long, &end_double); + if (end_type == 0) { + RETURN_THROWS(); + } + + /* If the range is given as strings, generate an array of characters. */ + if (start_type == IS_STRING || end_type == IS_STRING) { + if (UNEXPECTED(start_type != end_type)) { + if (start_type != IS_STRING) { + php_error_docref(NULL, E_WARNING, "Argument #1 ($start) must be a string if argument #2 ($end)" + " is a string, argument #2 ($end) converted to 0"); + end_type = IS_LONG; + } else { + php_error_docref(NULL, E_WARNING, "Argument #2 ($end) must be a string if argument #1 ($start)" + " is a string, argument #1 ($start) converted to 0"); + start_type = IS_LONG; + } + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + goto handle_numeric_inputs; + } - if (type1 == IS_DOUBLE || type2 == IS_DOUBLE || is_step_double) { - goto double_str; - } else if (type1 == IS_LONG || type2 == IS_LONG) { - goto long_str; + if (is_step_double) { + php_error_docref(NULL, E_WARNING, "Argument #3 ($step) must be of type int when generating an array" + " of characters, inputs converted to 0"); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + end_type = IS_LONG; + start_type = IS_LONG; + goto handle_numeric_inputs; } - low = (unsigned char)Z_STRVAL_P(user_start)[0]; - high = (unsigned char)Z_STRVAL_P(user_end)[0]; + /* Generate array of characters */ + unsigned char low = (unsigned char)Z_STRVAL_P(user_start)[0]; + unsigned char high = (unsigned char)Z_STRVAL_P(user_end)[0]; if (low > high) { /* Negative Steps */ if (low - high < step) { @@ -2886,93 +2972,84 @@ PHP_FUNCTION(range) ZVAL_CHAR(&tmp, low); zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); } - } else if (Z_TYPE_P(user_start) == IS_DOUBLE || Z_TYPE_P(user_end) == IS_DOUBLE || is_step_double) { - double low, high, element; - uint32_t i, size; -double_str: - low = zval_get_double(user_start); - high = zval_get_double(user_end); + return; + } - if (zend_isinf(high) || zend_isinf(low)) { - zend_value_error("Invalid range supplied: start=%0.0f end=%0.0f", low, high); - RETURN_THROWS(); - } + handle_numeric_inputs: + if (start_type == IS_DOUBLE || end_type == IS_DOUBLE || is_step_double) { + double element; + uint32_t i, size; - if (low > high) { /* Negative steps */ - if (low - high < step_double) { + if (start_double > end_double) { /* Negative steps */ + if (start_double - end_double < step_double) { err = 1; goto err; } - RANGE_CHECK_DOUBLE_INIT_ARRAY(low, high, step_double); + RANGE_CHECK_DOUBLE_INIT_ARRAY(start_double, end_double, step_double); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { - for (i = 0, element = low; i < size && element >= high; ++i, element = low - (i * step_double)) { + for (i = 0, element = start_double; i < size && element >= end_double; ++i, element = start_double - (i * step_double)) { ZEND_HASH_FILL_SET_DOUBLE(element); ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); - } else if (high > low) { /* Positive steps */ - if (high - low < step_double) { + } else if (end_double > start_double) { /* Positive steps */ + if (end_double - start_double < step_double) { err = 1; goto err; } - RANGE_CHECK_DOUBLE_INIT_ARRAY(high, low, step_double); + RANGE_CHECK_DOUBLE_INIT_ARRAY(end_double, start_double, step_double); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { - for (i = 0, element = low; i < size && element <= high; ++i, element = low + (i * step_double)) { + for (i = 0, element = start_double; i < size && element <= end_double; ++i, element = start_double + (i * step_double)) { ZEND_HASH_FILL_SET_DOUBLE(element); ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); } else { array_init(return_value); - ZVAL_DOUBLE(&tmp, low); + ZVAL_DOUBLE(&tmp, start_double); zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); } } else { - zend_long low, high; + ZEND_ASSERT(start_type == IS_LONG && end_type == IS_LONG && !is_step_double); /* unsigned_step is a zend_ulong so that comparisons to it don't overflow, i.e. low - high < lstep */ - zend_ulong unsigned_step; + zend_ulong unsigned_step= (zend_ulong)step; uint32_t i, size; -long_str: - low = zval_get_long(user_start); - high = zval_get_long(user_end); - - unsigned_step = (zend_ulong)step; - if (low > high) { /* Negative steps */ - if ((zend_ulong)low - high < unsigned_step) { + if (start_long > end_long) { /* Negative steps */ + if ((zend_ulong)start_long - end_long < unsigned_step) { err = 1; goto err; } - RANGE_CHECK_LONG_INIT_ARRAY(low, high, unsigned_step); + RANGE_CHECK_LONG_INIT_ARRAY(start_long, end_long, unsigned_step); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { for (i = 0; i < size; ++i) { - ZEND_HASH_FILL_SET_LONG(low - (i * unsigned_step)); + ZEND_HASH_FILL_SET_LONG(start_long - (i * unsigned_step)); ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); - } else if (high > low) { /* Positive steps */ - if ((zend_ulong)high - low < unsigned_step) { + } else if (end_long > start_long) { /* Positive steps */ + if ((zend_ulong)end_long - start_long < unsigned_step) { err = 1; goto err; } - RANGE_CHECK_LONG_INIT_ARRAY(high, low, unsigned_step); + RANGE_CHECK_LONG_INIT_ARRAY(end_long, start_long, unsigned_step); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { for (i = 0; i < size; ++i) { - ZEND_HASH_FILL_SET_LONG(low + (i * unsigned_step)); + ZEND_HASH_FILL_SET_LONG(start_long + (i * unsigned_step)); ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); } else { array_init(return_value); - ZVAL_LONG(&tmp, low); + ZVAL_LONG(&tmp, start_long); zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); } } diff --git a/ext/standard/tests/array/range/bug32021.phpt b/ext/standard/tests/array/range/bug32021.phpt index 5d3b421ab468f..949e522baf1bc 100644 --- a/ext/standard/tests/array/range/bug32021.phpt +++ b/ext/standard/tests/array/range/bug32021.phpt @@ -6,7 +6,10 @@ $foo = range('', 'z'); var_dump($foo); ?> ALIVE ---EXPECT-- +--EXPECTF-- +Warning: range(): Argument #1 ($start) must not be empty, casted to 0 in %s on line %d + +Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d array(1) { [0]=> int(0) diff --git a/ext/standard/tests/array/range/range_bug70239_0.phpt b/ext/standard/tests/array/range/range_bug70239_0.phpt index c5e56462c52eb..52f046a7e473d 100644 --- a/ext/standard/tests/array/range/range_bug70239_0.phpt +++ b/ext/standard/tests/array/range/range_bug70239_0.phpt @@ -9,4 +9,4 @@ try { } ?> --EXPECT-- -Invalid range supplied: start=0 end=inf +range(): Argument #2 ($end) must be a finite number, INF provided diff --git a/ext/standard/tests/array/range/range_bug70239_1.phpt b/ext/standard/tests/array/range/range_bug70239_1.phpt index cfaa1a51fc586..681d594a74dc9 100644 --- a/ext/standard/tests/array/range/range_bug70239_1.phpt +++ b/ext/standard/tests/array/range/range_bug70239_1.phpt @@ -9,4 +9,4 @@ try { } ?> --EXPECT-- -Invalid range supplied: start=inf end=inf +range(): Argument #1 ($start) must be a finite number, INF provided diff --git a/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt index bf4891dc9fd98..96ae0788b27a9 100644 --- a/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt +++ b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt @@ -19,7 +19,11 @@ $fs = [$f1, $f2, $f3, 5.5]; foreach ($fs as $s) { foreach ($fs as $e) { echo "range($s, $e);\n"; - var_dump( range($s, $e) ); + try { + var_dump( range($s, $e) ); + } catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; + } } } @@ -29,80 +33,35 @@ float(NAN) float(NAN) float(NAN) range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, 5.5); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, 5.5); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, NAN); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(NAN, 5.5); -array(1) { - [0]=> - float(NAN) -} +range(): Argument #1 ($start) must be a finite number, NAN provided range(5.5, NAN); -array(1) { - [0]=> - float(5.5) -} +range(): Argument #2 ($end) must be a finite number, NAN provided range(5.5, NAN); -array(1) { - [0]=> - float(5.5) -} +range(): Argument #2 ($end) must be a finite number, NAN provided range(5.5, NAN); -array(1) { - [0]=> - float(5.5) -} +range(): Argument #2 ($end) must be a finite number, NAN provided range(5.5, 5.5); array(1) { [0]=> diff --git a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt index 20040fac1cac2..d63f5c0ca58af 100644 --- a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt +++ b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt @@ -23,8 +23,12 @@ var_dump( range("A", "H", 2.6) ); // Because step is fractional it tries to inte echo "Done\n"; ?> ---EXPECT-- +--EXPECTF-- Range will ignore any byte after the first one + +Warning: range(): Argument #1 ($start) must be a single byte, subsequent bytes are ignored in %s on line %d + +Warning: range(): Argument #2 ($end) must be a single byte, subsequent bytes are ignored in %s on line %d array(2) { [0]=> string(1) "A" @@ -32,27 +36,41 @@ array(2) { string(1) "B" } Range cannot operate on an empty string + +Warning: range(): Argument #2 ($end) must not be empty, casted to 0 in %s on line %d + +Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d array(1) { [0]=> int(0) } + +Warning: range(): Argument #1 ($start) must not be empty, casted to 0 in %s on line %d + +Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d array(1) { [0]=> int(0) } Mixing numeric string and character + +Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d array(2) { [0]=> int(1) [1]=> int(0) } + +Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d array(2) { [0]=> int(0) [1]=> int(1) } + +Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d array(4) { [0]=> float(3.5) @@ -63,6 +81,8 @@ array(4) { [3]=> float(0.5) } + +Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d array(4) { [0]=> float(0) @@ -74,6 +94,8 @@ array(4) { float(3) } Fractional step cannot be used on character ranges + +Warning: range(): Argument #3 ($step) must be of type int when generating an array of characters, inputs converted to 0 in %s on line %d array(1) { [0]=> float(0) From 45222df336f31aadaef796dc90f734bec67871db Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 10 Mar 2023 15:06:44 +0000 Subject: [PATCH 10/22] Improve range exceed array size error message The start and end value are not necessarily the issue, it only is when the step parameter does to small increments --- ext/standard/array.c | 4 ++-- .../tests/array/range/range_bug70239_2.phpt | 2 +- .../tests/array/range/range_bug70239_3.phpt | 2 +- ...nge_inputs_float_small_step_exhaustion.phpt | 18 ++++++++++++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 ext/standard/tests/array/range/range_inputs_float_small_step_exhaustion.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index 502b99a03737f..1658f6d59089e 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2761,7 +2761,7 @@ PHP_FUNCTION(array_fill_keys) double __calc_size = ((start - end) / (_step)) + 1; \ if (__calc_size >= (double)HT_MAX_SIZE) { \ zend_value_error(\ - "The supplied range exceeds the maximum array size: start=%0.0f end=%0.0f", end, start); \ + "The supplied range exceeds the maximum array size: start=%0.1f end=%0.1f step=%0.1f", end, start, (_step)); \ RETURN_THROWS(); \ } \ size = (uint32_t)_php_math_round(__calc_size, 0, PHP_ROUND_HALF_UP); \ @@ -2773,7 +2773,7 @@ PHP_FUNCTION(array_fill_keys) zend_ulong __calc_size = ((zend_ulong) start - end) / (_step); \ if (__calc_size >= HT_MAX_SIZE - 1) { \ zend_value_error(\ - "The supplied range exceeds the maximum array size: start=" ZEND_LONG_FMT " end=" ZEND_LONG_FMT, end, start); \ + "The supplied range exceeds the maximum array size: start=" ZEND_LONG_FMT " end=" ZEND_LONG_FMT " step=" ZEND_LONG_FMT, end, start, (_step)); \ RETURN_THROWS(); \ } \ size = (uint32_t)(__calc_size + 1); \ diff --git a/ext/standard/tests/array/range/range_bug70239_2.phpt b/ext/standard/tests/array/range/range_bug70239_2.phpt index 1ccf8d0373bac..a4ec3c5563512 100644 --- a/ext/standard/tests/array/range/range_bug70239_2.phpt +++ b/ext/standard/tests/array/range/range_bug70239_2.phpt @@ -9,4 +9,4 @@ try { } ?> --EXPECTF-- -The supplied range exceeds the maximum array size: start=0 end=%d +The supplied range exceeds the maximum array size: start=0 end=%d step=1 diff --git a/ext/standard/tests/array/range/range_bug70239_3.phpt b/ext/standard/tests/array/range/range_bug70239_3.phpt index 5d50db4f4a2b8..7099eb1e98cfd 100644 --- a/ext/standard/tests/array/range/range_bug70239_3.phpt +++ b/ext/standard/tests/array/range/range_bug70239_3.phpt @@ -9,4 +9,4 @@ try { } ?> --EXPECTF-- -The supplied range exceeds the maximum array size: start=-%d end=0 +The supplied range exceeds the maximum array size: start=-%d end=0 step=1 diff --git a/ext/standard/tests/array/range/range_inputs_float_small_step_exhaustion.phpt b/ext/standard/tests/array/range/range_inputs_float_small_step_exhaustion.phpt new file mode 100644 index 0000000000000..b00b3142627cc --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_float_small_step_exhaustion.phpt @@ -0,0 +1,18 @@ +--TEST-- +Creating a range that exceeds the maximum array size +--FILE-- +getMessage(), \PHP_EOL; +} +try { + var_dump(range(PHP_INT_MIN, PHP_INT_MAX, 1)); +} catch (\ValueError $e) { + echo $e->getMessage(), \PHP_EOL; +} +?> +--EXPECTF-- +The supplied range exceeds the maximum array size: start=0.0 end=100000000000.0 step=0.1 +The supplied range exceeds the maximum array size: start=-%d end=%d step=1 From f91196847465cda3d9f435fc4ef46ed40035ef04 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 10 Mar 2023 15:13:16 +0000 Subject: [PATCH 11/22] Improve error message about step parameter being out of range --- ext/standard/array.c | 2 +- ext/standard/tests/array/range/range_step_errors.phpt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 1658f6d59089e..8dd4df9be3f04 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -3055,7 +3055,7 @@ PHP_FUNCTION(range) } err: if (err) { - zend_argument_value_error(3, "must not exceed the specified range"); + zend_argument_value_error(3, "must be less than the range spanned by argument #1 ($start) and argument #2 ($end)"); RETURN_THROWS(); } } diff --git a/ext/standard/tests/array/range/range_step_errors.phpt b/ext/standard/tests/array/range/range_step_errors.phpt index 35a5ecf8a83cd..df9d80d7212c9 100644 --- a/ext/standard/tests/array/range/range_step_errors.phpt +++ b/ext/standard/tests/array/range/range_step_errors.phpt @@ -104,10 +104,10 @@ range(): Argument #3 ($step) must be a finite number, NAN provided range(): Argument #3 ($step) must be a finite number, NAN provided Step must be within the range of input parameters -- Testing ( (low < high) && (high-low < step) ) -- -range(): Argument #3 ($step) must not exceed the specified range +range(): Argument #3 ($step) must be less than the range spanned by argument #1 ($start) and argument #2 ($end) -- Testing ( (low > high) && (low-high < step) ) -- -range(): Argument #3 ($step) must not exceed the specified range +range(): Argument #3 ($step) must be less than the range spanned by argument #1 ($start) and argument #2 ($end) -- Testing ( (low < high) && (high-low < step) ) for characters -- -range(): Argument #3 ($step) must not exceed the specified range +range(): Argument #3 ($step) must be less than the range spanned by argument #1 ($start) and argument #2 ($end) -- Testing ( (low > high) && (low-high < step) ) for characters -- -range(): Argument #3 ($step) must not exceed the specified range +range(): Argument #3 ($step) must be less than the range spanned by argument #1 ($start) and argument #2 ($end) From e635c9b401ae9838112babfbcc6b867a7e0b81fa Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 10 Mar 2023 15:19:07 +0000 Subject: [PATCH 12/22] Add warning for negative steps --- ext/standard/array.c | 8 +++ .../tests/array/range/range_step_errors.phpt | 63 ++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 8dd4df9be3f04..f61a92afb2923 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2865,6 +2865,10 @@ PHP_FUNCTION(range) /* We only want positive step values. */ if (step_double < 0.0) { + php_error_docref(NULL, E_WARNING, "Argument #3 ($step) must be greater than 0, $step multiplied by -1"); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } step_double *= -1; } step = zend_dval_to_lval(step_double); @@ -2875,6 +2879,10 @@ PHP_FUNCTION(range) step = Z_LVAL_P(user_step); /* We only want positive step values. */ if (step < 0) { + php_error_docref(NULL, E_WARNING, "Argument #3 ($step) must be greater than 0, $step multiplied by -1"); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } step *= -1; } step_double = (double) step; diff --git a/ext/standard/tests/array/range/range_step_errors.phpt b/ext/standard/tests/array/range/range_step_errors.phpt index df9d80d7212c9..adde0505a57a6 100644 --- a/ext/standard/tests/array/range/range_step_errors.phpt +++ b/ext/standard/tests/array/range/range_step_errors.phpt @@ -87,8 +87,30 @@ try { echo $e->getMessage(), "\n"; } +echo "Step must not be negative\n"; +try { + var_dump(range('a', 'c', -1)); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(range(1, 3, -1)); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(range('a', 'c', -1)); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(range(1.5, 3.5, -1.5)); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} + ?> ---EXPECT-- +--EXPECTF-- Step cannot be 0 range(): Argument #3 ($step) cannot be 0 range(): Argument #3 ($step) cannot be 0 @@ -111,3 +133,42 @@ range(): Argument #3 ($step) must be less than the range spanned by argument #1 range(): Argument #3 ($step) must be less than the range spanned by argument #1 ($start) and argument #2 ($end) -- Testing ( (low > high) && (low-high < step) ) for characters -- range(): Argument #3 ($step) must be less than the range spanned by argument #1 ($start) and argument #2 ($end) +Step must not be negative + +Warning: range(): Argument #3 ($step) must be greater than 0, $step multiplied by -1 in %s on line %d +array(3) { + [0]=> + string(1) "a" + [1]=> + string(1) "b" + [2]=> + string(1) "c" +} + +Warning: range(): Argument #3 ($step) must be greater than 0, $step multiplied by -1 in %s on line %d +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} + +Warning: range(): Argument #3 ($step) must be greater than 0, $step multiplied by -1 in %s on line %d +array(3) { + [0]=> + string(1) "a" + [1]=> + string(1) "b" + [2]=> + string(1) "c" +} + +Warning: range(): Argument #3 ($step) must be greater than 0, $step multiplied by -1 in %s on line %d +array(2) { + [0]=> + float(1.5) + [1]=> + float(3) +} From afa8e5bbdc31bfc598cae73eb785f44823bb1c93 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 22 Mar 2023 04:27:28 +0000 Subject: [PATCH 13/22] Make empty strings an error Analysis of OSS packages shows this never happens --- ext/standard/array.c | 10 ++----- ext/standard/tests/array/range/bug32021.phpt | 17 ----------- .../range/range_inputs_string_invalid.phpt | 30 ++++++++----------- 3 files changed, 14 insertions(+), 43 deletions(-) delete mode 100644 ext/standard/tests/array/range/bug32021.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index f61a92afb2923..6a5ec619a75e9 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2802,14 +2802,8 @@ static uint8_t php_range_process_input(const zval *input, uint32_t arg_num, zend return IS_DOUBLE; case IS_STRING: { if (Z_STRLEN_P(input) == 0) { - const char *arg_name = get_active_function_arg_name(arg_num); - php_error_docref(NULL, E_WARNING, "Argument #%d ($%s) must not be empty, casted to 0", arg_num, arg_name); - if (UNEXPECTED(EG(exception))) { - return 0; - } - *lval = 0; - *dval = 0.0; - return IS_LONG; + zend_argument_value_error(arg_num, "must not be empty"); + return 0; } uint8_t type = is_numeric_str_function(Z_STR_P(input), lval, dval); if (type == IS_DOUBLE) { diff --git a/ext/standard/tests/array/range/bug32021.phpt b/ext/standard/tests/array/range/bug32021.phpt deleted file mode 100644 index 949e522baf1bc..0000000000000 --- a/ext/standard/tests/array/range/bug32021.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -Bug #32021 (Crash caused by range('', 'z')) ---FILE-- - -ALIVE ---EXPECTF-- -Warning: range(): Argument #1 ($start) must not be empty, casted to 0 in %s on line %d - -Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d -array(1) { - [0]=> - int(0) -} -ALIVE diff --git a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt index d63f5c0ca58af..0cc95467e1dd2 100644 --- a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt +++ b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt @@ -9,8 +9,16 @@ echo "Range will ignore any byte after the first one\n"; var_dump( range("AA", "BB") ); echo "Range cannot operate on an empty string\n"; -var_dump( range("Z", "") ); // Both strings are cast to int, i.e. 0 -var_dump( range("", "Z") ); // Both strings are cast to int, i.e. 0 +try { + var_dump( range("Z", "") ); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump( range("", "Z") ); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} echo "Mixing numeric string and character\n"; var_dump( range("1", "A") ); // The char is cast to an int, i.e. 0 @@ -36,22 +44,8 @@ array(2) { string(1) "B" } Range cannot operate on an empty string - -Warning: range(): Argument #2 ($end) must not be empty, casted to 0 in %s on line %d - -Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d -array(1) { - [0]=> - int(0) -} - -Warning: range(): Argument #1 ($start) must not be empty, casted to 0 in %s on line %d - -Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d -array(1) { - [0]=> - int(0) -} +range(): Argument #2 ($end) must not be empty +range(): Argument #1 ($start) must not be empty Mixing numeric string and character Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d From a5d69d537de001817dc738704ea02cc1bfa080e7 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 28 Mar 2023 14:26:07 +0100 Subject: [PATCH 14/22] Add test with null --- .../range/range_inputs_null_variations.phpt | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 ext/standard/tests/array/range/range_inputs_null_variations.phpt diff --git a/ext/standard/tests/array/range/range_inputs_null_variations.phpt b/ext/standard/tests/array/range/range_inputs_null_variations.phpt new file mode 100644 index 0000000000000..7e61931603e13 --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_null_variations.phpt @@ -0,0 +1,102 @@ +--TEST-- +Test range() function with null as argument. +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECTF-- +null with int boundary + +Deprecated: range(): Passing null to parameter #1 ($start) of type string|int|float is deprecated in %s on line %d +array(6) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) + [3]=> + int(3) + [4]=> + int(4) + [5]=> + int(5) +} + +Deprecated: range(): Passing null to parameter #2 ($end) of type string|int|float is deprecated in %s on line %d +array(6) { + [0]=> + int(5) + [1]=> + int(4) + [2]=> + int(3) + [3]=> + int(2) + [4]=> + int(1) + [5]=> + int(0) +} +null with float boundary + +Deprecated: range(): Passing null to parameter #1 ($start) of type string|int|float is deprecated in %s on line %d +array(5) { + [0]=> + float(0) + [1]=> + float(1) + [2]=> + float(2) + [3]=> + float(3) + [4]=> + float(4) +} + +Deprecated: range(): Passing null to parameter #2 ($end) of type string|int|float is deprecated in %s on line %d +array(5) { + [0]=> + float(4.5) + [1]=> + float(3.5) + [2]=> + float(2.5) + [3]=> + float(1.5) + [4]=> + float(0.5) +} +null with string boundary + +Deprecated: range(): Passing null to parameter #1 ($start) of type string|int|float is deprecated in %s on line %d + +Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d +array(1) { + [0]=> + int(0) +} + +Deprecated: range(): Passing null to parameter #2 ($end) of type string|int|float is deprecated in %s on line %d + +Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d +array(1) { + [0]=> + int(0) +} +Done From 7890ad2d775f2f8681c64934c4fe4d1b53a7935e Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 30 Mar 2023 13:59:47 +0100 Subject: [PATCH 15/22] Revert "Make empty strings an error" This reverts commit 75c76d61cb4e0370d6c8762dd2e2384c63bcd3a0. --- ext/standard/array.c | 10 +++++-- ext/standard/tests/array/range/bug32021.phpt | 17 +++++++++++ .../range/range_inputs_string_invalid.phpt | 30 +++++++++++-------- 3 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 ext/standard/tests/array/range/bug32021.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index 6a5ec619a75e9..f61a92afb2923 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2802,8 +2802,14 @@ static uint8_t php_range_process_input(const zval *input, uint32_t arg_num, zend return IS_DOUBLE; case IS_STRING: { if (Z_STRLEN_P(input) == 0) { - zend_argument_value_error(arg_num, "must not be empty"); - return 0; + const char *arg_name = get_active_function_arg_name(arg_num); + php_error_docref(NULL, E_WARNING, "Argument #%d ($%s) must not be empty, casted to 0", arg_num, arg_name); + if (UNEXPECTED(EG(exception))) { + return 0; + } + *lval = 0; + *dval = 0.0; + return IS_LONG; } uint8_t type = is_numeric_str_function(Z_STR_P(input), lval, dval); if (type == IS_DOUBLE) { diff --git a/ext/standard/tests/array/range/bug32021.phpt b/ext/standard/tests/array/range/bug32021.phpt new file mode 100644 index 0000000000000..949e522baf1bc --- /dev/null +++ b/ext/standard/tests/array/range/bug32021.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #32021 (Crash caused by range('', 'z')) +--FILE-- + +ALIVE +--EXPECTF-- +Warning: range(): Argument #1 ($start) must not be empty, casted to 0 in %s on line %d + +Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d +array(1) { + [0]=> + int(0) +} +ALIVE diff --git a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt index 0cc95467e1dd2..d63f5c0ca58af 100644 --- a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt +++ b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt @@ -9,16 +9,8 @@ echo "Range will ignore any byte after the first one\n"; var_dump( range("AA", "BB") ); echo "Range cannot operate on an empty string\n"; -try { - var_dump( range("Z", "") ); -} catch (ValueError $e) { - echo $e->getMessage(), "\n"; -} -try { - var_dump( range("", "Z") ); -} catch (ValueError $e) { - echo $e->getMessage(), "\n"; -} +var_dump( range("Z", "") ); // Both strings are cast to int, i.e. 0 +var_dump( range("", "Z") ); // Both strings are cast to int, i.e. 0 echo "Mixing numeric string and character\n"; var_dump( range("1", "A") ); // The char is cast to an int, i.e. 0 @@ -44,8 +36,22 @@ array(2) { string(1) "B" } Range cannot operate on an empty string -range(): Argument #2 ($end) must not be empty -range(): Argument #1 ($start) must not be empty + +Warning: range(): Argument #2 ($end) must not be empty, casted to 0 in %s on line %d + +Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d +array(1) { + [0]=> + int(0) +} + +Warning: range(): Argument #1 ($start) must not be empty, casted to 0 in %s on line %d + +Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d +array(1) { + [0]=> + int(0) +} Mixing numeric string and character Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d From b92f92f49ef0dfc39cd0be92c83a014cb9d443f1 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 30 Mar 2023 14:05:13 +0100 Subject: [PATCH 16/22] Remove unneessary variable and clean up goto boundary error --- ext/standard/array.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index f61a92afb2923..04c7f7963fcf6 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2839,7 +2839,7 @@ static uint8_t php_range_process_input(const zval *input, uint32_t arg_num, zend PHP_FUNCTION(range) { zval *user_start, *user_end, *user_step = NULL, tmp; - bool err = 0, is_step_double = false; + bool is_step_double = false; double step_double = 1.0; zend_long step = 1; @@ -2944,8 +2944,7 @@ PHP_FUNCTION(range) if (low > high) { /* Negative Steps */ if (low - high < step) { - err = 1; - goto err; + goto boundary_error; } /* Initialize the return_value as an array. */ array_init_size(return_value, (uint32_t)(((low - high) / step) + 1)); @@ -2961,8 +2960,7 @@ PHP_FUNCTION(range) } ZEND_HASH_FILL_END(); } else if (high > low) { /* Positive Steps */ if (high - low < step) { - err = 1; - goto err; + goto boundary_error; } array_init_size(return_value, (uint32_t)(((high - low) / step) + 1)); zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); @@ -2990,8 +2988,7 @@ PHP_FUNCTION(range) if (start_double > end_double) { /* Negative steps */ if (start_double - end_double < step_double) { - err = 1; - goto err; + goto boundary_error; } RANGE_CHECK_DOUBLE_INIT_ARRAY(start_double, end_double, step_double); @@ -3004,8 +3001,7 @@ PHP_FUNCTION(range) } ZEND_HASH_FILL_END(); } else if (end_double > start_double) { /* Positive steps */ if (end_double - start_double < step_double) { - err = 1; - goto err; + goto boundary_error; } RANGE_CHECK_DOUBLE_INIT_ARRAY(end_double, start_double, step_double); @@ -3029,8 +3025,7 @@ PHP_FUNCTION(range) if (start_long > end_long) { /* Negative steps */ if ((zend_ulong)start_long - end_long < unsigned_step) { - err = 1; - goto err; + goto boundary_error; } RANGE_CHECK_LONG_INIT_ARRAY(start_long, end_long, unsigned_step); @@ -3043,8 +3038,7 @@ PHP_FUNCTION(range) } ZEND_HASH_FILL_END(); } else if (end_long > start_long) { /* Positive steps */ if ((zend_ulong)end_long - start_long < unsigned_step) { - err = 1; - goto err; + goto boundary_error; } RANGE_CHECK_LONG_INIT_ARRAY(end_long, start_long, unsigned_step); @@ -3061,11 +3055,11 @@ PHP_FUNCTION(range) zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); } } -err: - if (err) { - zend_argument_value_error(3, "must be less than the range spanned by argument #1 ($start) and argument #2 ($end)"); - RETURN_THROWS(); - } + return; + +boundary_error: + zend_argument_value_error(3, "must be less than the range spanned by argument #1 ($start) and argument #2 ($end)"); + RETURN_THROWS(); } /* }}} */ From c5f08d2ac3fdb0a3d71887e7d606c367095a077c Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 30 Mar 2023 14:20:54 +0100 Subject: [PATCH 17/22] Allow step to be negative for decreasing ranges --- ext/standard/array.c | 39 +++++++++------ ...range_negative_step_decreasing_ranges.phpt | 33 ++++++++++++ .../tests/array/range/range_step_errors.phpt | 50 ++----------------- 3 files changed, 63 insertions(+), 59 deletions(-) create mode 100644 ext/standard/tests/array/range/range_negative_step_decreasing_ranges.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index 04c7f7963fcf6..a572937bf7286 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2840,6 +2840,7 @@ PHP_FUNCTION(range) { zval *user_start, *user_end, *user_step = NULL, tmp; bool is_step_double = false; + bool is_step_negative = false; double step_double = 1.0; zend_long step = 1; @@ -2865,10 +2866,7 @@ PHP_FUNCTION(range) /* We only want positive step values. */ if (step_double < 0.0) { - php_error_docref(NULL, E_WARNING, "Argument #3 ($step) must be greater than 0, $step multiplied by -1"); - if (UNEXPECTED(EG(exception))) { - RETURN_THROWS(); - } + is_step_negative = true; step_double *= -1; } step = zend_dval_to_lval(step_double); @@ -2879,10 +2877,7 @@ PHP_FUNCTION(range) step = Z_LVAL_P(user_step); /* We only want positive step values. */ if (step < 0) { - php_error_docref(NULL, E_WARNING, "Argument #3 ($step) must be greater than 0, $step multiplied by -1"); - if (UNEXPECTED(EG(exception))) { - RETURN_THROWS(); - } + is_step_negative = true; step *= -1; } step_double = (double) step; @@ -2942,7 +2937,8 @@ PHP_FUNCTION(range) unsigned char low = (unsigned char)Z_STRVAL_P(user_start)[0]; unsigned char high = (unsigned char)Z_STRVAL_P(user_end)[0]; - if (low > high) { /* Negative Steps */ + /* Decreasing char range */ + if (low > high) { if (low - high < step) { goto boundary_error; } @@ -2958,7 +2954,10 @@ PHP_FUNCTION(range) } } } ZEND_HASH_FILL_END(); - } else if (high > low) { /* Positive Steps */ + } else if (high > low) { /* Increasing char range */ + if (is_step_negative) { + goto negative_step_error; + } if (high - low < step) { goto boundary_error; } @@ -2986,7 +2985,8 @@ PHP_FUNCTION(range) double element; uint32_t i, size; - if (start_double > end_double) { /* Negative steps */ + /* Decreasing float range */ + if (start_double > end_double) { if (start_double - end_double < step_double) { goto boundary_error; } @@ -2999,7 +2999,10 @@ PHP_FUNCTION(range) ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); - } else if (end_double > start_double) { /* Positive steps */ + } else if (end_double > start_double) { /* Increasing float range */ + if (is_step_negative) { + goto negative_step_error; + } if (end_double - start_double < step_double) { goto boundary_error; } @@ -3023,7 +3026,8 @@ PHP_FUNCTION(range) zend_ulong unsigned_step= (zend_ulong)step; uint32_t i, size; - if (start_long > end_long) { /* Negative steps */ + /* Decreasing int range */ + if (start_long > end_long) { if ((zend_ulong)start_long - end_long < unsigned_step) { goto boundary_error; } @@ -3036,7 +3040,10 @@ PHP_FUNCTION(range) ZEND_HASH_FILL_NEXT(); } } ZEND_HASH_FILL_END(); - } else if (end_long > start_long) { /* Positive steps */ + } else if (end_long > start_long) { /* Increasing int range */ + if (is_step_negative) { + goto negative_step_error; + } if ((zend_ulong)end_long - start_long < unsigned_step) { goto boundary_error; } @@ -3057,6 +3064,10 @@ PHP_FUNCTION(range) } return; +negative_step_error: + zend_argument_value_error(3, "must be greater than 0 for increasing ranges"); + RETURN_THROWS(); + boundary_error: zend_argument_value_error(3, "must be less than the range spanned by argument #1 ($start) and argument #2 ($end)"); RETURN_THROWS(); diff --git a/ext/standard/tests/array/range/range_negative_step_decreasing_ranges.phpt b/ext/standard/tests/array/range/range_negative_step_decreasing_ranges.phpt new file mode 100644 index 0000000000000..6a9ff3a7aeba9 --- /dev/null +++ b/ext/standard/tests/array/range/range_negative_step_decreasing_ranges.phpt @@ -0,0 +1,33 @@ +--TEST-- +range() allows $step parameter to be negative for decreasing ranges +--INI-- +precision=14 +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + string(1) "c" + [1]=> + string(1) "b" + [2]=> + string(1) "a" +} +array(3) { + [0]=> + int(3) + [1]=> + int(2) + [2]=> + int(1) +} +array(2) { + [0]=> + float(3.5) + [1]=> + float(2) +} diff --git a/ext/standard/tests/array/range/range_step_errors.phpt b/ext/standard/tests/array/range/range_step_errors.phpt index adde0505a57a6..e950c527863e2 100644 --- a/ext/standard/tests/array/range/range_step_errors.phpt +++ b/ext/standard/tests/array/range/range_step_errors.phpt @@ -87,7 +87,7 @@ try { echo $e->getMessage(), "\n"; } -echo "Step must not be negative\n"; +echo "Step must not be negative for increasing ranges\n"; try { var_dump(range('a', 'c', -1)); } catch (\ValueError $e) { @@ -98,11 +98,6 @@ try { } catch (\ValueError $e) { echo $e->getMessage(), "\n"; } -try { - var_dump(range('a', 'c', -1)); -} catch (\ValueError $e) { - echo $e->getMessage(), "\n"; -} try { var_dump(range(1.5, 3.5, -1.5)); } catch (\ValueError $e) { @@ -133,42 +128,7 @@ range(): Argument #3 ($step) must be less than the range spanned by argument #1 range(): Argument #3 ($step) must be less than the range spanned by argument #1 ($start) and argument #2 ($end) -- Testing ( (low > high) && (low-high < step) ) for characters -- range(): Argument #3 ($step) must be less than the range spanned by argument #1 ($start) and argument #2 ($end) -Step must not be negative - -Warning: range(): Argument #3 ($step) must be greater than 0, $step multiplied by -1 in %s on line %d -array(3) { - [0]=> - string(1) "a" - [1]=> - string(1) "b" - [2]=> - string(1) "c" -} - -Warning: range(): Argument #3 ($step) must be greater than 0, $step multiplied by -1 in %s on line %d -array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) -} - -Warning: range(): Argument #3 ($step) must be greater than 0, $step multiplied by -1 in %s on line %d -array(3) { - [0]=> - string(1) "a" - [1]=> - string(1) "b" - [2]=> - string(1) "c" -} - -Warning: range(): Argument #3 ($step) must be greater than 0, $step multiplied by -1 in %s on line %d -array(2) { - [0]=> - float(1.5) - [1]=> - float(3) -} +Step must not be negative for increasing ranges +range(): Argument #3 ($step) must be greater than 0 for increasing ranges +range(): Argument #3 ($step) must be greater than 0 for increasing ranges +range(): Argument #3 ($step) must be greater than 0 for increasing ranges From 93f8913a3a589cf18cdb7ea217f81c9adba31b96 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 16 May 2023 12:12:41 +0100 Subject: [PATCH 18/22] Suggestions test --- .../array/range/range_inputs_float_NAN_values.phpt | 2 +- .../array/range/range_inputs_null_variations.phpt | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt index 96ae0788b27a9..03ed069fd893a 100644 --- a/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt +++ b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt @@ -1,5 +1,5 @@ --TEST-- -Test range() function with integer inputs and float step +Test range() function with non finite numbers --INI-- serialize_precision=14 --FILE-- diff --git a/ext/standard/tests/array/range/range_inputs_null_variations.phpt b/ext/standard/tests/array/range/range_inputs_null_variations.phpt index 7e61931603e13..d4ead2e405bc4 100644 --- a/ext/standard/tests/array/range/range_inputs_null_variations.phpt +++ b/ext/standard/tests/array/range/range_inputs_null_variations.phpt @@ -4,6 +4,8 @@ Test range() function with null as argument. serialize_precision=14 --FILE-- --EXPECTF-- +range(null, null) + +Deprecated: range(): Passing null to parameter #1 ($start) of type string|int|float is deprecated in %s on line %d + +Deprecated: range(): Passing null to parameter #2 ($end) of type string|int|float is deprecated in %s on line %d +array(1) { + [0]=> + int(0) +} null with int boundary Deprecated: range(): Passing null to parameter #1 ($start) of type string|int|float is deprecated in %s on line %d From bef2e326b4bbb591134f70551d270f4aa5a7a39a Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 16 May 2023 13:04:08 +0100 Subject: [PATCH 19/22] Handle digit strings as strings and not ints --- ext/standard/array.c | 48 ++++++--- .../range/range_inputs_numeric_basic.phpt | 25 ----- .../range/range_inputs_string_digits.phpt | 97 +++++++++++++++++++ ...range_inputs_string_digits_float_step.phpt | 33 +++++++ .../range/range_inputs_string_invalid.phpt | 24 +---- 5 files changed, 170 insertions(+), 57 deletions(-) create mode 100644 ext/standard/tests/array/range/range_inputs_string_digits.phpt create mode 100644 ext/standard/tests/array/range/range_inputs_string_digits_float_step.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index a572937bf7286..489aae1b3eda5 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2781,6 +2781,12 @@ PHP_FUNCTION(array_fill_keys) zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); \ } while (0) +/* Process input for the range() function + * 0 on exceptions + * IS_LONG if only interpretable as int + * IS_DOUBLE if only interpretable as float + * IS_STRING if only interpretable as string + * IS_ARRAY (as IS_LONG < IS_STRING < IS_ARRAY) for ambiguity of single byte strings which contains a digit */ static uint8_t php_range_process_input(const zval *input, uint32_t arg_num, zend_long /* restrict */ *lval, double /* restrict */ *dval) { switch (Z_TYPE_P(input)) { @@ -2801,6 +2807,14 @@ static uint8_t php_range_process_input(const zval *input, uint32_t arg_num, zend } return IS_DOUBLE; case IS_STRING: { + /* Process strings: + * - Empty strings are converted to 0 with a diagnostic + * - Check if string is numeric and store the values in passed pointer + * - If numeric float, this means it cannot be a numeric string with only one byte GOTO IS_DOUBLE + * - If numeric int, check it is one byte or not + * - If it one byte, return IS_ARRAY as IS_LONG < IS_STRING < IS_ARRAY + * - If not should only be interpreted as int, return IS_LONG; + * - Otherwise is a string and return IS_STRING */ if (Z_STRLEN_P(input) == 0) { const char *arg_name = get_active_function_arg_name(arg_num); php_error_docref(NULL, E_WARNING, "Argument #%d ($%s) must not be empty, casted to 0", arg_num, arg_name); @@ -2817,7 +2831,11 @@ static uint8_t php_range_process_input(const zval *input, uint32_t arg_num, zend } if (type == IS_LONG) { *dval = (double) *lval; - return IS_LONG; + if (Z_STRLEN_P(input) == 1) { + return IS_ARRAY; + } else { + return IS_LONG; + } } if (Z_STRLEN_P(input) != 1) { const char *arg_name = get_active_function_arg_name(arg_num); @@ -2905,15 +2923,20 @@ PHP_FUNCTION(range) } /* If the range is given as strings, generate an array of characters. */ - if (start_type == IS_STRING || end_type == IS_STRING) { - if (UNEXPECTED(start_type != end_type)) { - if (start_type != IS_STRING) { - php_error_docref(NULL, E_WARNING, "Argument #1 ($start) must be a string if argument #2 ($end)" - " is a string, argument #2 ($end) converted to 0"); + if (start_type >= IS_STRING || end_type >= IS_STRING) { + /* If one of the inputs is NOT a string */ + if (UNEXPECTED(start_type + end_type < 2*IS_STRING)) { + if (start_type < IS_STRING) { + if (end_type != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "Argument #1 ($start) must be a string if" + " argument #2 ($end) is a string, argument #2 ($end) converted to 0"); + } end_type = IS_LONG; - } else { - php_error_docref(NULL, E_WARNING, "Argument #2 ($end) must be a string if argument #1 ($start)" - " is a string, argument #1 ($start) converted to 0"); + } else if (end_type < IS_STRING) { + if (start_type != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "Argument #2 ($end) must be a string if" + " argument #1 ($start) is a string, argument #1 ($start) converted to 0"); + } start_type = IS_LONG; } if (UNEXPECTED(EG(exception))) { @@ -2923,8 +2946,11 @@ PHP_FUNCTION(range) } if (is_step_double) { - php_error_docref(NULL, E_WARNING, "Argument #3 ($step) must be of type int when generating an array" - " of characters, inputs converted to 0"); + /* Only emit warning if one of the input is not a numeric digit */ + if (start_type == IS_STRING || end_type == IS_STRING) { + php_error_docref(NULL, E_WARNING, "Argument #3 ($step) must be of type int when generating an array" + " of characters, inputs converted to 0"); + } if (UNEXPECTED(EG(exception))) { RETURN_THROWS(); } diff --git a/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt b/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt index c2191106fe67b..d804b00a54bef 100644 --- a/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt +++ b/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt @@ -32,7 +32,6 @@ var_dump( range(1, 2, 0.1) ); var_dump( range(2, 1, 0.1) ); var_dump( range(1, 2, "0.1") ); -var_dump( range("1", "2", 0.1) ); echo "Done\n"; ?> @@ -272,28 +271,4 @@ array(11) { [10]=> float(2) } -array(11) { - [0]=> - float(1) - [1]=> - float(1.1) - [2]=> - float(1.2) - [3]=> - float(1.3) - [4]=> - float(1.4) - [5]=> - float(1.5) - [6]=> - float(1.6) - [7]=> - float(1.7) - [8]=> - float(1.8) - [9]=> - float(1.9) - [10]=> - float(2) -} Done diff --git a/ext/standard/tests/array/range/range_inputs_string_digits.phpt b/ext/standard/tests/array/range/range_inputs_string_digits.phpt new file mode 100644 index 0000000000000..55c52775f7f06 --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_string_digits.phpt @@ -0,0 +1,97 @@ +--TEST-- +Test range() function digits +--FILE-- + +--EXPECT-- +Only digits +array(9) { + [0]=> + string(1) "1" + [1]=> + string(1) "2" + [2]=> + string(1) "3" + [3]=> + string(1) "4" + [4]=> + string(1) "5" + [5]=> + string(1) "6" + [6]=> + string(1) "7" + [7]=> + string(1) "8" + [8]=> + string(1) "9" +} +array(9) { + [0]=> + string(1) "9" + [1]=> + string(1) "8" + [2]=> + string(1) "7" + [3]=> + string(1) "6" + [4]=> + string(1) "5" + [5]=> + string(1) "4" + [6]=> + string(1) "3" + [7]=> + string(1) "2" + [8]=> + string(1) "1" +} +Only digits and char +array(9) { + [0]=> + string(1) "9" + [1]=> + string(1) ":" + [2]=> + string(1) ";" + [3]=> + string(1) "<" + [4]=> + string(1) "=" + [5]=> + string(1) ">" + [6]=> + string(1) "?" + [7]=> + string(1) "@" + [8]=> + string(1) "A" +} +array(9) { + [0]=> + string(1) "A" + [1]=> + string(1) "@" + [2]=> + string(1) "?" + [3]=> + string(1) ">" + [4]=> + string(1) "=" + [5]=> + string(1) "<" + [6]=> + string(1) ";" + [7]=> + string(1) ":" + [8]=> + string(1) "9" +} +Done diff --git a/ext/standard/tests/array/range/range_inputs_string_digits_float_step.phpt b/ext/standard/tests/array/range/range_inputs_string_digits_float_step.phpt new file mode 100644 index 0000000000000..296b6d15d3cf4 --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_string_digits_float_step.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test range() function where boundary are string digits and step is a float +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECT-- +array(11) { + [0]=> + float(1) + [1]=> + float(1.1) + [2]=> + float(1.2) + [3]=> + float(1.3) + [4]=> + float(1.4) + [5]=> + float(1.5) + [6]=> + float(1.6) + [7]=> + float(1.7) + [8]=> + float(1.8) + [9]=> + float(1.9) + [10]=> + float(2) +} diff --git a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt index d63f5c0ca58af..1270e19c646aa 100644 --- a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt +++ b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt @@ -1,5 +1,5 @@ --TEST-- -Test range() function with basic/expected string inputs +Test range() function with unexpected string inputs --INI-- serialize_precision=14 --FILE-- @@ -12,9 +12,7 @@ echo "Range cannot operate on an empty string\n"; var_dump( range("Z", "") ); // Both strings are cast to int, i.e. 0 var_dump( range("", "Z") ); // Both strings are cast to int, i.e. 0 -echo "Mixing numeric string and character\n"; -var_dump( range("1", "A") ); // The char is cast to an int, i.e. 0 -var_dump( range("?", "1") ); // The char is cast to an int, i.e. 0 +echo "Mixing numeric float string and character\n"; var_dump( range("3.5", "A") ); // The char is cast to a float, i.e. 0 var_dump( range("?", "3.5") ); // The char is cast to a float, i.e. 0 @@ -52,23 +50,7 @@ array(1) { [0]=> int(0) } -Mixing numeric string and character - -Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d -array(2) { - [0]=> - int(1) - [1]=> - int(0) -} - -Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d -array(2) { - [0]=> - int(0) - [1]=> - int(1) -} +Mixing numeric float string and character Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d array(4) { From 391af8bb2c083a6a96dd8812196b0ea25b385e08 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 16 May 2023 20:25:53 +0100 Subject: [PATCH 20/22] Split tests and add cases --- .../array/range/range_inputs_float_basic.phpt | 148 ++++++++++ .../array/range/range_inputs_int_basic.phpt | 188 +++++++++++++ .../range/range_inputs_numeric_basic.phpt | 258 ++---------------- .../range/range_inputs_string_digits.phpt | 2 +- .../range/range_inputs_string_numeric.phpt | 188 +++++++++++++ 5 files changed, 554 insertions(+), 230 deletions(-) create mode 100644 ext/standard/tests/array/range/range_inputs_float_basic.phpt create mode 100644 ext/standard/tests/array/range/range_inputs_int_basic.phpt create mode 100644 ext/standard/tests/array/range/range_inputs_string_numeric.phpt diff --git a/ext/standard/tests/array/range/range_inputs_float_basic.phpt b/ext/standard/tests/array/range/range_inputs_float_basic.phpt new file mode 100644 index 0000000000000..de13e6333c2af --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_float_basic.phpt @@ -0,0 +1,148 @@ +--TEST-- +range(): float boundary inputs +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECT-- +Increasing Range +array(6) { + [0]=> + float(1.5) + [1]=> + float(2.5) + [2]=> + float(3.5) + [3]=> + float(4.5) + [4]=> + float(5.5) + [5]=> + float(6.5) +} + +Decreasing range +array(6) { + [0]=> + float(6.5) + [1]=> + float(5.5) + [2]=> + float(4.5) + [3]=> + float(3.5) + [4]=> + float(2.5) + [5]=> + float(1.5) +} + +Boundaries are equal +array(1) { + [0]=> + float(5.5) +} + +Passing int step +array(4) { + [0]=> + float(1.5) + [1]=> + float(4.5) + [2]=> + float(7.5) + [3]=> + float(10.5) +} +array(4) { + [0]=> + float(10.5) + [1]=> + float(7.5) + [2]=> + float(4.5) + [3]=> + float(1.5) +} +array(4) { + [0]=> + float(1.5) + [1]=> + float(4.5) + [2]=> + float(7.5) + [3]=> + float(10.5) +} + +Passing float step +array(7) { + [0]=> + float(1.6) + [1]=> + float(1.7) + [2]=> + float(1.8) + [3]=> + float(1.9) + [4]=> + float(2) + [5]=> + float(2.1) + [6]=> + float(2.2) +} +array(7) { + [0]=> + float(2.2) + [1]=> + float(2.1) + [2]=> + float(2) + [3]=> + float(1.9) + [4]=> + float(1.8) + [5]=> + float(1.7) + [6]=> + float(1.6) +} +array(7) { + [0]=> + float(1.6) + [1]=> + float(1.7) + [2]=> + float(1.8) + [3]=> + float(1.9) + [4]=> + float(2) + [5]=> + float(2.1) + [6]=> + float(2.2) +} +Done diff --git a/ext/standard/tests/array/range/range_inputs_int_basic.phpt b/ext/standard/tests/array/range/range_inputs_int_basic.phpt new file mode 100644 index 0000000000000..a50b41112b0b1 --- /dev/null +++ b/ext/standard/tests/array/range/range_inputs_int_basic.phpt @@ -0,0 +1,188 @@ +--TEST-- +range(): integer boundary inputs +--INI-- +serialize_precision=14 +--FILE-- + +--EXPECT-- +Increasing Range +array(10) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) + [4]=> + int(5) + [5]=> + int(6) + [6]=> + int(7) + [7]=> + int(8) + [8]=> + int(9) + [9]=> + int(10) +} + +Decreasing range +array(10) { + [0]=> + int(10) + [1]=> + int(9) + [2]=> + int(8) + [3]=> + int(7) + [4]=> + int(6) + [5]=> + int(5) + [6]=> + int(4) + [7]=> + int(3) + [8]=> + int(2) + [9]=> + int(1) +} + +Boundaries are equal +array(1) { + [0]=> + int(5) +} + +Passing int step +array(4) { + [0]=> + int(1) + [1]=> + int(4) + [2]=> + int(7) + [3]=> + int(10) +} +array(4) { + [0]=> + int(10) + [1]=> + int(7) + [2]=> + int(4) + [3]=> + int(1) +} +array(4) { + [0]=> + int(1) + [1]=> + int(4) + [2]=> + int(7) + [3]=> + int(10) +} + +Passing float step +array(11) { + [0]=> + float(1) + [1]=> + float(1.1) + [2]=> + float(1.2) + [3]=> + float(1.3) + [4]=> + float(1.4) + [5]=> + float(1.5) + [6]=> + float(1.6) + [7]=> + float(1.7) + [8]=> + float(1.8) + [9]=> + float(1.9) + [10]=> + float(2) +} +array(11) { + [0]=> + float(2) + [1]=> + float(1.9) + [2]=> + float(1.8) + [3]=> + float(1.7) + [4]=> + float(1.6) + [5]=> + float(1.5) + [6]=> + float(1.4) + [7]=> + float(1.3) + [8]=> + float(1.2) + [9]=> + float(1.1) + [10]=> + float(1) +} +array(11) { + [0]=> + float(1) + [1]=> + float(1.1) + [2]=> + float(1.2) + [3]=> + float(1.3) + [4]=> + float(1.4) + [5]=> + float(1.5) + [6]=> + float(1.6) + [7]=> + float(1.7) + [8]=> + float(1.8) + [9]=> + float(1.9) + [10]=> + float(2) +} +Done diff --git a/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt b/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt index d804b00a54bef..09e9e415b9c3f 100644 --- a/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt +++ b/ext/standard/tests/array/range/range_inputs_numeric_basic.phpt @@ -1,44 +1,18 @@ --TEST-- -Test range() function with basic/expected numeric inputs +range(): mixed numeric types boundary inputs --INI-- serialize_precision=14 --FILE-- --EXPECT-- --- Integers as Low and High -- --- An array of elements from low to high -- -array(10) { +array(4) { [0]=> int(1) [1]=> @@ -47,47 +21,8 @@ array(10) { int(3) [3]=> int(4) - [4]=> - int(5) - [5]=> - int(6) - [6]=> - int(7) - [7]=> - int(8) - [8]=> - int(9) - [9]=> - int(10) -} - --- An array of elements from high to low -- -array(10) { - [0]=> - int(10) - [1]=> - int(9) - [2]=> - int(8) - [3]=> - int(7) - [4]=> - int(6) - [5]=> - int(5) - [6]=> - int(4) - [7]=> - int(3) - [8]=> - int(2) - [9]=> - int(1) } - --- Numeric Strings as Low and High -- --- An array of elements from low to high -- -array(10) { +array(4) { [0]=> int(1) [1]=> @@ -96,179 +31,44 @@ array(10) { int(3) [3]=> int(4) - [4]=> - int(5) - [5]=> - int(6) - [6]=> - int(7) - [7]=> - int(8) - [8]=> - int(9) - [9]=> - int(10) -} - --- An array of elements from high to low -- -array(10) { - [0]=> - int(10) - [1]=> - int(9) - [2]=> - int(8) - [3]=> - int(7) - [4]=> - int(6) - [5]=> - int(5) - [6]=> - int(4) - [7]=> - int(3) - [8]=> - int(2) - [9]=> - int(1) } - --- Low and High are equal -- -array(1) { +array(4) { [0]=> - int(5) -} - --- floats as Low and High -- -array(6) { - [0]=> - float(5.1) - [1]=> - float(6.1) - [2]=> - float(7.1) - [3]=> - float(8.1) - [4]=> - float(9.1) - [5]=> - float(10.1) -} -array(6) { - [0]=> - float(10.1) - [1]=> - float(9.1) - [2]=> - float(8.1) - [3]=> - float(7.1) - [4]=> - float(6.1) - [5]=> - float(5.1) -} -array(6) { - [0]=> - float(5.1) - [1]=> - float(6.1) - [2]=> - float(7.1) - [3]=> - float(8.1) - [4]=> - float(9.1) - [5]=> - float(10.1) -} -array(6) { - [0]=> - float(10.1) + float(1.5) [1]=> - float(9.1) + float(2.5) [2]=> - float(8.1) + float(3.5) [3]=> - float(7.1) - [4]=> - float(6.1) - [5]=> - float(5.1) + float(4.5) } - --- Passing step with Low and High -- -array(11) { +array(4) { [0]=> - float(1) + float(1.5) [1]=> - float(1.1) + float(2.5) [2]=> - float(1.2) + float(3.5) [3]=> - float(1.3) - [4]=> - float(1.4) - [5]=> - float(1.5) - [6]=> - float(1.6) - [7]=> - float(1.7) - [8]=> - float(1.8) - [9]=> - float(1.9) - [10]=> - float(2) + float(4.5) } -array(11) { +array(4) { [0]=> - float(2) + int(9) [1]=> - float(1.9) + int(10) [2]=> - float(1.8) + int(11) [3]=> - float(1.7) - [4]=> - float(1.6) - [5]=> - float(1.5) - [6]=> - float(1.4) - [7]=> - float(1.3) - [8]=> - float(1.2) - [9]=> - float(1.1) - [10]=> - float(1) + int(12) } -array(11) { +array(4) { [0]=> - float(1) + int(9) [1]=> - float(1.1) + int(10) [2]=> - float(1.2) + int(11) [3]=> - float(1.3) - [4]=> - float(1.4) - [5]=> - float(1.5) - [6]=> - float(1.6) - [7]=> - float(1.7) - [8]=> - float(1.8) - [9]=> - float(1.9) - [10]=> - float(2) + int(12) } -Done diff --git a/ext/standard/tests/array/range/range_inputs_string_digits.phpt b/ext/standard/tests/array/range/range_inputs_string_digits.phpt index 55c52775f7f06..b218c582ff6d3 100644 --- a/ext/standard/tests/array/range/range_inputs_string_digits.phpt +++ b/ext/standard/tests/array/range/range_inputs_string_digits.phpt @@ -1,5 +1,5 @@ --TEST-- -Test range() function digits +Test range() function with string digits --FILE-- +--EXPECT-- +Increasing Range +array(10) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) + [4]=> + int(5) + [5]=> + int(6) + [6]=> + int(7) + [7]=> + int(8) + [8]=> + int(9) + [9]=> + int(10) +} + +Decreasing range +array(10) { + [0]=> + int(10) + [1]=> + int(9) + [2]=> + int(8) + [3]=> + int(7) + [4]=> + int(6) + [5]=> + int(5) + [6]=> + int(4) + [7]=> + int(3) + [8]=> + int(2) + [9]=> + int(1) +} + +Boundaries are equal +array(1) { + [0]=> + string(1) "5" +} + +Passing int step +array(4) { + [0]=> + int(1) + [1]=> + int(4) + [2]=> + int(7) + [3]=> + int(10) +} +array(4) { + [0]=> + int(10) + [1]=> + int(7) + [2]=> + int(4) + [3]=> + int(1) +} +array(4) { + [0]=> + int(1) + [1]=> + int(4) + [2]=> + int(7) + [3]=> + int(10) +} + +Passing float step +array(11) { + [0]=> + float(1) + [1]=> + float(1.1) + [2]=> + float(1.2) + [3]=> + float(1.3) + [4]=> + float(1.4) + [5]=> + float(1.5) + [6]=> + float(1.6) + [7]=> + float(1.7) + [8]=> + float(1.8) + [9]=> + float(1.9) + [10]=> + float(2) +} +array(11) { + [0]=> + float(2) + [1]=> + float(1.9) + [2]=> + float(1.8) + [3]=> + float(1.7) + [4]=> + float(1.6) + [5]=> + float(1.5) + [6]=> + float(1.4) + [7]=> + float(1.3) + [8]=> + float(1.2) + [9]=> + float(1.1) + [10]=> + float(1) +} +array(11) { + [0]=> + float(1) + [1]=> + float(1.1) + [2]=> + float(1.2) + [3]=> + float(1.3) + [4]=> + float(1.4) + [5]=> + float(1.5) + [6]=> + float(1.6) + [7]=> + float(1.7) + [8]=> + float(1.8) + [9]=> + float(1.9) + [10]=> + float(2) +} +Done From 69512d2b425230a5f1f47f1d6ec31de5ad8ad5c9 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 31 May 2023 11:11:44 +0100 Subject: [PATCH 21/22] Update error message to mention singly byte string --- ext/standard/array.c | 8 ++++---- ext/standard/tests/array/range/bug32021.phpt | 2 +- .../tests/array/range/range_inputs_null_variations.phpt | 4 ++-- .../tests/array/range/range_inputs_string_invalid.phpt | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 489aae1b3eda5..235bdf79d5a4c 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2928,14 +2928,14 @@ PHP_FUNCTION(range) if (UNEXPECTED(start_type + end_type < 2*IS_STRING)) { if (start_type < IS_STRING) { if (end_type != IS_ARRAY) { - php_error_docref(NULL, E_WARNING, "Argument #1 ($start) must be a string if" - " argument #2 ($end) is a string, argument #2 ($end) converted to 0"); + php_error_docref(NULL, E_WARNING, "Argument #1 ($start) must be a single byte string if" + " argument #2 ($end) is a single byte string, argument #2 ($end) converted to 0"); } end_type = IS_LONG; } else if (end_type < IS_STRING) { if (start_type != IS_ARRAY) { - php_error_docref(NULL, E_WARNING, "Argument #2 ($end) must be a string if" - " argument #1 ($start) is a string, argument #1 ($start) converted to 0"); + php_error_docref(NULL, E_WARNING, "Argument #2 ($end) must be a single byte string if" + " argument #1 ($start) is a single byte string, argument #1 ($start) converted to 0"); } start_type = IS_LONG; } diff --git a/ext/standard/tests/array/range/bug32021.phpt b/ext/standard/tests/array/range/bug32021.phpt index 949e522baf1bc..3ffc677b89e5c 100644 --- a/ext/standard/tests/array/range/bug32021.phpt +++ b/ext/standard/tests/array/range/bug32021.phpt @@ -9,7 +9,7 @@ ALIVE --EXPECTF-- Warning: range(): Argument #1 ($start) must not be empty, casted to 0 in %s on line %d -Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d +Warning: range(): Argument #1 ($start) must be a single byte string if argument #2 ($end) is a single byte string, argument #2 ($end) converted to 0 in %s on line %d array(1) { [0]=> int(0) diff --git a/ext/standard/tests/array/range/range_inputs_null_variations.phpt b/ext/standard/tests/array/range/range_inputs_null_variations.phpt index d4ead2e405bc4..bec34efe65404 100644 --- a/ext/standard/tests/array/range/range_inputs_null_variations.phpt +++ b/ext/standard/tests/array/range/range_inputs_null_variations.phpt @@ -97,7 +97,7 @@ null with string boundary Deprecated: range(): Passing null to parameter #1 ($start) of type string|int|float is deprecated in %s on line %d -Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d +Warning: range(): Argument #1 ($start) must be a single byte string if argument #2 ($end) is a single byte string, argument #2 ($end) converted to 0 in %s on line %d array(1) { [0]=> int(0) @@ -105,7 +105,7 @@ array(1) { Deprecated: range(): Passing null to parameter #2 ($end) of type string|int|float is deprecated in %s on line %d -Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d +Warning: range(): Argument #2 ($end) must be a single byte string if argument #1 ($start) is a single byte string, argument #1 ($start) converted to 0 in %s on line %d array(1) { [0]=> int(0) diff --git a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt index 1270e19c646aa..2518f82500dbd 100644 --- a/ext/standard/tests/array/range/range_inputs_string_invalid.phpt +++ b/ext/standard/tests/array/range/range_inputs_string_invalid.phpt @@ -37,7 +37,7 @@ Range cannot operate on an empty string Warning: range(): Argument #2 ($end) must not be empty, casted to 0 in %s on line %d -Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d +Warning: range(): Argument #2 ($end) must be a single byte string if argument #1 ($start) is a single byte string, argument #1 ($start) converted to 0 in %s on line %d array(1) { [0]=> int(0) @@ -45,14 +45,14 @@ array(1) { Warning: range(): Argument #1 ($start) must not be empty, casted to 0 in %s on line %d -Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d +Warning: range(): Argument #1 ($start) must be a single byte string if argument #2 ($end) is a single byte string, argument #2 ($end) converted to 0 in %s on line %d array(1) { [0]=> int(0) } Mixing numeric float string and character -Warning: range(): Argument #1 ($start) must be a string if argument #2 ($end) is a string, argument #2 ($end) converted to 0 in %s on line %d +Warning: range(): Argument #1 ($start) must be a single byte string if argument #2 ($end) is a single byte string, argument #2 ($end) converted to 0 in %s on line %d array(4) { [0]=> float(3.5) @@ -64,7 +64,7 @@ array(4) { float(0.5) } -Warning: range(): Argument #2 ($end) must be a string if argument #1 ($start) is a string, argument #1 ($start) converted to 0 in %s on line %d +Warning: range(): Argument #2 ($end) must be a single byte string if argument #1 ($start) is a single byte string, argument #1 ($start) converted to 0 in %s on line %d array(4) { [0]=> float(0) From b9c7382b398918fa818b1b6247aaf40e5c700650 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 18 Jun 2023 14:07:55 +0100 Subject: [PATCH 22/22] UPGRADING doc --- UPGRADING | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/UPGRADING b/UPGRADING index 1f6df9e880310..02fc9e61e24de 100644 --- a/UPGRADING +++ b/UPGRADING @@ -52,6 +52,27 @@ PHP 8.3 UPGRADE NOTES . C functions that have a return type of void now return null instead of returning the following object object(FFI\CData:void) { } +- Standard: + . The range() function has had various changes: + * A TypeError is now thrown when passing objects, resources, or arrays + as the boundary inputs + * A more descriptive ValueError is thrown when passing 0 for $step + * A ValueError is now thrown when using a negative $step for increasing ranges + * If $step is a float that can be interpreted as an int, it is now done so + * A ValueError is now thrown if any argument is infinity or NAN + * An E_WARNING is now emitted if $start or $end is the empty string. + The value continues to be cast to the value 0. + * An E_WARNING is now emitted if $start or $end has more than one byte, + only if it is a non-numeric string. + * An E_WARNING is now emitted if $start or $end is cast to an integer + because the other boundary input is a number. (e.g. range(5, 'z');) + * An E_WARNING is now emitted if $step is a float when trying to generate + a range of characters, except if both boundary inputs are numeric strings + (e.g. range('5', '9', 0.5); does not produce a warning) + * range() now produce a list of characters if one of the boundary inputs is + a string digit instead of casting the other input to int + (e.g. range('5', 'z');) + ======================================== 2. New Features ======================================== @@ -113,7 +134,7 @@ PHP 8.3 UPGRADE NOTES . Changed DOMCharacterData::appendData() tentative return type to true. - Intl: - . datefmt_set_timezone (and its alias IntlDateformatter::setTimeZone) + . datefmt_set_timezone (and its alias IntlDateformatter::setTimeZone) now returns true on success, previously null was returned. . IntlBreakiterator::setText() now returns false on failure, previously null was returned.