Skip to content

Allow substr functions to accept null lengths #4840

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,8 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_callback_error(int num, char *e
#define Z_PARAM_LONG(dest) \
Z_PARAM_LONG_EX(dest, _dummy, 0, 0)

#define Z_PARAM_LONG_OR_NULL(dest, is_null) \
Z_PARAM_LONG_EX(dest, is_null, 1, 0)

/* no old equivalent */
#define Z_PARAM_NUMBER_EX(dest, check_null) \
Expand Down
7 changes: 4 additions & 3 deletions ext/iconv/iconv.c
Original file line number Diff line number Diff line change
Expand Up @@ -2028,13 +2028,14 @@ PHP_FUNCTION(iconv_substr)
size_t charset_len = 0;
zend_string *str;
zend_long offset, length = 0;
zend_bool len_is_null = 1;

php_iconv_err_t err;

smart_str retval = {0};

if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|ls",
&str, &offset, &length,
if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|l!s",
&str, &offset, &length, &len_is_null,
&charset, &charset_len) == FAILURE) {
return;
}
Expand All @@ -2044,7 +2045,7 @@ PHP_FUNCTION(iconv_substr)
RETURN_FALSE;
}

if (ZEND_NUM_ARGS() < 3) {
if (len_is_null) {
length = ZSTR_LEN(str);
}

Expand Down
2 changes: 1 addition & 1 deletion ext/iconv/iconv.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
function iconv_strlen(string $str, string $charset = UNKNOWN) {}

/** @return string|false */
function iconv_substr(string $str, int $offset, int $length = UNKNOWN, string $charset = UNKNOWN) {}
function iconv_substr(string $str, int $offset, ?int $length = null, string $charset = UNKNOWN) {}

/** @return int|false */
function iconv_strpos(string $haystack, string $needle, int $offset = 0, string $charset = UNKNOWN) {}
Expand Down
2 changes: 1 addition & 1 deletion ext/iconv/iconv_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_substr, 0, 0, 2)
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 1)
ZEND_ARG_TYPE_INFO(0, charset, IS_STRING, 0)
ZEND_END_ARG_INFO()

Expand Down
3 changes: 3 additions & 0 deletions ext/iconv/tests/iconv_substr.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ foo("
bar("This is a test", 100000);
bar("This is a test", 0, 100000);
bar("This is a test", -3);
bar("This is a test", -3, null);
bar("This is a test", 0, -9);
bar("This is a test", 0, -100000);
bar("This is a test", -9, -100000);
Expand All @@ -50,6 +51,8 @@ string(14) "This is a test"
string(14) "This is a test"
string(3) "est"
string(3) "est"
string(3) "est"
string(3) "est"
string(5) "This "
string(5) "This "
bool(false)
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -3473,7 +3473,7 @@ PHP_FUNCTION(array_slice)
Z_PARAM_ARRAY(input)
Z_PARAM_LONG(offset)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_EX(length, length_is_null, 1, 0)
Z_PARAM_LONG_OR_NULL(length, length_is_null)
Z_PARAM_BOOL(preserve_keys)
ZEND_PARSE_PARAMETERS_END();

Expand Down
6 changes: 3 additions & 3 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ function strrchr(string $haystack, string $needle) {}
function chunk_split(string $str, int $chunklen = 76, string $ending = "\r\n"): string {}

/** @return string|false */
function substr(string $str, int $start, int $length = UNKNOWN) {}
function substr(string $str, int $start, ?int $length = null) {}

/**
* @param mixed $str
Expand Down Expand Up @@ -566,7 +566,7 @@ function localeconv(): array {}
function strnatcasecmp(string $s1, string $s2): int {}

/** @return int|false */
function substr_count(string $haystack, string $needle, int $offset = 0, int $length = UNKNOWN) {}
function substr_count(string $haystack, string $needle, int $offset = 0, ?int $length = null) {}

function str_pad(string $input, int $pad_length, string $pad_string = " ", int $pad_type = STR_PAD_RIGHT): string {}

Expand All @@ -591,7 +591,7 @@ function str_split(string $str, int $split_length = 1): array {}
function strpbrk(string $haystack, string $char_list) {}

/** @return int|false */
function substr_compare(string $main_str, string $str, int $offset, int $length = UNKNOWN, bool $case_insensitivity = false) {}
function substr_compare(string $main_str, string $str, int $offset, ?int $length = null, bool $case_insensitivity = false) {}

function utf8_encode(string $data): string {}

Expand Down
6 changes: 3 additions & 3 deletions ext/standard/basic_functions_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_substr, 0, 0, 2)
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, start, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 1)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_substr_replace, 0, 0, 3)
Expand Down Expand Up @@ -751,7 +751,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_substr_count, 0, 0, 2)
ZEND_ARG_TYPE_INFO(0, haystack, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, needle, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 1)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_str_pad, 0, 2, IS_STRING, 0)
Expand Down Expand Up @@ -798,7 +798,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_substr_compare, 0, 0, 3)
ZEND_ARG_TYPE_INFO(0, main_str, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 1)
ZEND_ARG_TYPE_INFO(0, case_insensitivity, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

Expand Down
4 changes: 2 additions & 2 deletions ext/standard/streamsfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ PHP_FUNCTION(stream_select)
Z_PARAM_ARRAY_EX2(r_array, 1, 1, 0)
Z_PARAM_ARRAY_EX2(w_array, 1, 1, 0)
Z_PARAM_ARRAY_EX2(e_array, 1, 1, 0)
Z_PARAM_LONG_EX(sec, secnull, 1, 0)
Z_PARAM_LONG_OR_NULL(sec, secnull)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(usec)
ZEND_PARSE_PARAMETERS_END();
Expand Down Expand Up @@ -1493,7 +1493,7 @@ PHP_FUNCTION(stream_socket_enable_crypto)
Z_PARAM_RESOURCE(zstream)
Z_PARAM_BOOL(enable)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_EX(cryptokind, cryptokindnull, 1, 0)
Z_PARAM_LONG_OR_NULL(cryptokind, cryptokindnull)
Z_PARAM_RESOURCE(zsessstream)
ZEND_PARSE_PARAMETERS_END();

Expand Down
16 changes: 8 additions & 8 deletions ext/standard/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -2184,13 +2184,13 @@ PHP_FUNCTION(substr)
{
zend_string *str;
zend_long l = 0, f;
int argc = ZEND_NUM_ARGS();
zend_bool len_is_null = 1;

ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STR(str)
Z_PARAM_LONG(f)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(l)
Z_PARAM_LONG_OR_NULL(l, len_is_null)
ZEND_PARSE_PARAMETERS_END();

if (f > (zend_long)ZSTR_LEN(str)) {
Expand All @@ -2204,7 +2204,7 @@ PHP_FUNCTION(substr)
} else {
f = (zend_long)ZSTR_LEN(str) + f;
}
if (argc > 2) {
if (!len_is_null) {
if (l < 0) {
/* if "length" position is negative, set it to the length
* needed to stop that many chars from the end of the string
Expand All @@ -2224,7 +2224,7 @@ PHP_FUNCTION(substr)
} else {
goto truncate_len;
}
} else if (argc > 2) {
} else if (!len_is_null) {
if (l < 0) {
/* if "length" position is negative, set it to the length
* needed to stop that many chars from the end of the string
Expand Down Expand Up @@ -5553,7 +5553,7 @@ PHP_FUNCTION(substr_count)
{
char *haystack, *needle;
zend_long offset = 0, length = 0;
int ac = ZEND_NUM_ARGS();
zend_bool length_is_null = 1;
zend_long count = 0;
size_t haystack_len, needle_len;
const char *p, *endp;
Expand All @@ -5564,7 +5564,7 @@ PHP_FUNCTION(substr_count)
Z_PARAM_STRING(needle, needle_len)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(offset)
Z_PARAM_LONG(length)
Z_PARAM_LONG_OR_NULL(length, length_is_null)
ZEND_PARSE_PARAMETERS_END();

if (needle_len == 0) {
Expand All @@ -5584,7 +5584,7 @@ PHP_FUNCTION(substr_count)
}
p += offset;

if (ac == 4) {
if (!length_is_null) {

if (length < 0) {
length += (haystack_len - offset);
Expand Down Expand Up @@ -6098,7 +6098,7 @@ PHP_FUNCTION(substr_compare)
Z_PARAM_STR(s2)
Z_PARAM_LONG(offset)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_EX(len, len_is_default, 1, 0)
Z_PARAM_LONG_OR_NULL(len, len_is_default)
Z_PARAM_BOOL(cs)
ZEND_PARSE_PARAMETERS_END();

Expand Down
Binary file modified ext/standard/tests/strings/substr.phpt
Binary file not shown.
2 changes: 2 additions & 0 deletions ext/standard/tests/strings/substr_compare.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ substr_compare()
<?php

var_dump(substr_compare("abcde", "df", -2) < 0);
var_dump(substr_compare("abcde", "df", -2, null) < 0);
var_dump(substr_compare("abcde", "bc", 1, 2));
var_dump(substr_compare("abcde", "bcg", 1, 2));
var_dump(substr_compare("abcde", "BC", 1, 2, true));
Expand All @@ -25,6 +26,7 @@ echo "Done\n";
?>
--EXPECTF--
bool(true)
bool(true)
int(0)
int(0)
int(0)
Expand Down
4 changes: 4 additions & 0 deletions ext/standard/tests/strings/substr_count_basic.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ var_dump(substr_count($a, "bca"));
$a = str_repeat("abcacbabca", 100);
var_dump(substr_count($a, "bca"));
var_dump(substr_count($a, "bca", 200));
var_dump(substr_count($a, "bca", 200, null));
var_dump(substr_count($a, "bca", 200, 50));
var_dump(substr_count($a, "bca", -200));
var_dump(substr_count($a, "bca", -200, null));
var_dump(substr_count($a, "bca", -200, 50));
var_dump(substr_count($a, "bca", -200, -50));

Expand All @@ -42,8 +44,10 @@ int(0)
int(100)
int(200)
int(160)
int(160)
int(10)
int(40)
int(40)
int(10)
int(30)
Done