Skip to content

Allow variable strings to be defined for a string offset #5013

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
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
19 changes: 6 additions & 13 deletions Zend/tests/bug71572.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,9 @@ var_dump($str[3] = "");
var_dump($str[10] = "");
var_dump($str);
?>
--EXPECTF--
Warning: Cannot assign an empty string to a string offset in %s on line %d
NULL

Warning: Cannot assign an empty string to a string offset in %s on line %d
NULL

Warning: Cannot assign an empty string to a string offset in %s on line %d
NULL

Warning: Cannot assign an empty string to a string offset in %s on line %d
NULL
string(3) "abc"
--EXPECT--
string(2) "bc"
string(1) "b"
string(3) "b "
string(10) "b "
string(10) "b "
4 changes: 2 additions & 2 deletions Zend/tests/indexing_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ array(1) {
Warning: Illegal string offset 'foo' in %s on line %d

Warning: Array to string conversion in %s on line %d
string(1) "A"
string(5) "Array"

Warning: Illegal string offset 'foo' in %s on line %d

Warning: Array to string conversion in %s on line %d
string(1) "A"
string(5) "Array"
Cannot use a scalar value as an array
float(0.1)
array(1) {
Expand Down
4 changes: 2 additions & 2 deletions Zend/tests/str_offset_004.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ Warning: Illegal string offset: -20 in %sstr_offset_004.php on line %d
string(15) "abCZefghijPQmno"
string(15) "AbCZefghijPQmno"
string(21) "AbCZefghijPQmno N"
string(21) "AbCZefghijPQmno UN"
string(21) "AbCZefghijPQmno nUN"
string(23) "AbCZefghijPQmno UFON"
string(23) "AbCZefghijPQmno U ON"
42 changes: 42 additions & 0 deletions Zend/tests/string_offset_variable_length_values.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
Some string offset replacement with variable length values
--FILE--
<?php

function str(): string { return "Hello world"; }
var_dump(str()[20] = '');
var_dump(str()[20] = 'あ');
var_dump(str()[0] = '');
var_dump(str()[0] = 'あ');
var_dump(str()[4] = '');
var_dump(str()[4] = 'あ');
var_dump(str()[-5] = '');
var_dump(str()[-5] = 'あ');

try {
var_dump(str()[-20] = '');
} catch (\Error $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
var_dump(str()[-20] = 'あ');
} catch (\Error $e) {
echo $e->getMessage(), \PHP_EOL;
}

?>
--EXPECTF--
string(20) "Hello world "
string(23) "Hello world あ"
string(10) "ello world"
string(13) "あello world"
string(10) "Hell world"
string(13) "Hellあ world"
string(10) "Hello orld"
string(13) "Hello あorld"

Warning: Illegal string offset: -20 in %s on line %d
NULL

Warning: Illegal string offset: -20 in %s on line %d
NULL
89 changes: 71 additions & 18 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -1569,7 +1569,7 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_deprecated_function(c

static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, zval *value OPLINE_DC EXECUTE_DATA_DC)
{
zend_uchar c;
char *string_value;
size_t string_len;
zend_long offset;

Expand All @@ -1593,33 +1593,56 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
return;
}

string_value = ZSTR_VAL(tmp);
string_len = ZSTR_LEN(tmp);
c = (zend_uchar)ZSTR_VAL(tmp)[0];
zend_string_release_ex(tmp, 0);
} else {
string_value = Z_STRVAL_P(value);
string_len = Z_STRLEN_P(value);
c = (zend_uchar)Z_STRVAL_P(value)[0];
}

if (string_len == 0) {
/* Error on empty input string */
zend_error(E_WARNING, "Cannot assign an empty string to a string offset");
if (offset < 0) { /* Handle negative offset */
offset += (zend_long)Z_STRLEN_P(str);
}

/* If it's a byte char replace byte directly */
if (string_len == 1) {
zend_uchar c = (zend_uchar) string_value[0];

if ((size_t)offset >= Z_STRLEN_P(str)) {
/* Extend string if needed */
zend_long old_len = Z_STRLEN_P(str);
ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), offset + 1, 0));
memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len);
Z_STRVAL_P(str)[offset+1] = 0;
} else if (!Z_REFCOUNTED_P(str)) {
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
} else if (Z_REFCOUNT_P(str) > 1) {
Z_DELREF_P(str);
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
} else {
zend_string_forget_hash_val(Z_STR_P(str));
}

Z_STRVAL_P(str)[offset] = c;

if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
/* Return the new character */
ZVAL_INTERNED_STR(EX_VAR(opline->result.var), ZSTR_CHAR(c));
}
return;
}

if (offset < 0) { /* Handle negative offset */
offset += (zend_long)Z_STRLEN_P(str);
}

if ((size_t)offset >= Z_STRLEN_P(str)) {
/* Extend string if needed */
/* Extend string */
zend_long old_len = Z_STRLEN_P(str);
ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), offset + 1, 0));
ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), offset + string_len, 0));
memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len);
Z_STRVAL_P(str)[offset+1] = 0;
memcpy(Z_STRVAL_P(str) + offset, string_value, string_len);
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
}
return;
} else if (!Z_REFCOUNTED_P(str)) {
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
} else if (Z_REFCOUNT_P(str) > 1) {
Expand All @@ -1629,12 +1652,42 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
zend_string_forget_hash_val(Z_STR_P(str));
}

Z_STRVAL_P(str)[offset] = c;

// Buffer offset
int k = 0;
// Source offset
int i = 0;
char *buffer = emalloc(Z_STRLEN_P(str) + string_len);
char *source = Z_STRVAL_P(str);
// Append bytes from the source string to the buffer until the offset is reached
while (i < offset) {
buffer[k] = source[i];
i++;
k++;
}
i++; // Skip byte being replaced
// If not an empty string then append all the bytes from the value to the buffer
if (string_len > 0) {
int j = 0;
while (string_value[j] != '\0') {
buffer[k] = string_value[j];
j++;
k++;
}
}
// Add remaining bytes from the source string.
while (source[i] != '\0') {
buffer[k] = source[i];
i++;
k++;
}
// Append NUL byte to make a valid C string.
buffer[k] = '\0';
ZVAL_NEW_STR(str, zend_string_init(buffer, Z_STRLEN_P(str) + string_len - 1, 0));
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
/* Return the new character */
ZVAL_INTERNED_STR(EX_VAR(opline->result.var), ZSTR_CHAR(c));
ZVAL_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
}

efree(buffer);
}

static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *ref)
Expand Down
101 changes: 84 additions & 17 deletions ext/opcache/jit/zend_jit_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
{
zend_string *old_str;
zend_uchar c;
char *string_value;
size_t string_len;
zend_long offset;

Expand All @@ -888,11 +889,11 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
}

string_len = ZSTR_LEN(tmp);
c = (zend_uchar)ZSTR_VAL(tmp)[0];
string_value = ZSTR_VAL(tmp);
zend_string_release(tmp);
} else {
string_len = Z_STRLEN_P(value);
c = (zend_uchar)Z_STRVAL_P(value)[0];
string_value = Z_STRVAL_P(value);
}

if (string_len == 0) {
Expand All @@ -904,32 +905,98 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
return;
}

if (offset < 0) { /* Handle negative offset */
offset += (zend_long)Z_STRLEN_P(str);
/* If it's a byte char replace byte directly */
if (string_len == 1) {
c = (zend_uchar)string_value[0];

if (offset < 0) { /* Handle negative offset */
offset += (zend_long)
Z_STRLEN_P(str);
}

if ((size_t) offset >= Z_STRLEN_P(str)) {
/* Extend string if needed */
zend_long old_len = Z_STRLEN_P(str);
Z_STR_P(str) = zend_string_extend(Z_STR_P(str), offset + 1, 0);
Z_TYPE_INFO_P(str) = IS_STRING_EX;
memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len);
Z_STRVAL_P(str)[offset + 1] = 0;
} else if (!Z_REFCOUNTED_P(str)) {
old_str = Z_STR_P(str);
Z_STR_P(str) = zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0);
Z_TYPE_INFO_P(str) = IS_STRING_EX;
zend_string_release(old_str);
} else {
SEPARATE_STRING(str);
zend_string_forget_hash_val(Z_STR_P(str));
}

Z_STRVAL_P(str)[offset] = c;

if (result) {
/* Return the new character */
ZVAL_INTERNED_STR(result, ZSTR_CHAR(c));
}
return;
}



if ((size_t)offset >= Z_STRLEN_P(str)) {
/* Extend string if needed */
/* Extend string */
zend_long old_len = Z_STRLEN_P(str);
Z_STR_P(str) = zend_string_extend(Z_STR_P(str), offset + 1, 0);
Z_TYPE_INFO_P(str) = IS_STRING_EX;
memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len);
Z_STRVAL_P(str)[offset+1] = 0;
zend_string *tmp = zend_string_extend(Z_STR_P(str), offset + string_len, 0); // Leak
memset(ZSTR_VAL(tmp) + old_len, ' ', offset - old_len);
memcpy(ZSTR_VAL(tmp) + offset, string_value, string_len);
if (result) {
ZVAL_STR(result, zend_string_init(ZSTR_VAL(tmp), ZSTR_LEN(tmp), 0));
}
zend_string_release_ex(tmp, 0);
return;
} else if (!Z_REFCOUNTED_P(str)) {
old_str = Z_STR_P(str);
Z_STR_P(str) = zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0);
Z_TYPE_INFO_P(str) = IS_STRING_EX;
zend_string_release(old_str);
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
} else if (Z_REFCOUNT_P(str) > 1) {
Z_DELREF_P(str);
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
} else {
SEPARATE_STRING(str);
zend_string_forget_hash_val(Z_STR_P(str));
}

Z_STRVAL_P(str)[offset] = c;
// Buffer offset
int k = 0;
// Source offset
int i = 0;
char *buffer = emalloc(Z_STRLEN_P(str) + string_len);
char *source = Z_STRVAL_P(str);
// Append bytes from the source string to the buffer until the offset is reached
while (i < offset) {
buffer[k] = source[i];
i++;
k++;
}
i++; // Skip byte being replaced
// If not an empty string then append all the bytes from the value to the buffer
if (string_len > 0) {
int j = 0;
while (string_value[j] != '\0') {
buffer[k] = string_value[j];
j++;
k++;
}
}
// Add remaining bytes from the source string.
while (source[i] != '\0') {
buffer[k] = source[i];
i++;
k++;
}
// Append NUL byte to make a valid C string.
buffer[k] = '\0';
ZVAL_NEW_STR(str, zend_string_init(buffer, Z_STRLEN_P(str) + string_len - 1, 0));
efree(buffer);

if (result) {
/* Return the new character */
ZVAL_INTERNED_STR(result, ZSTR_CHAR(c));
ZVAL_STR(result, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
}
}

Expand Down
18 changes: 9 additions & 9 deletions tests/lang/bug22592.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ var_dump($result);
string(5) "* *-*"
string(7) "* *-* *"
string(7) "*4*-* *"
string(7) "*4*s* *"
string(8) "*4*s* *0"
string(8) "*-*-* *0"
string(8) "*-*s*s*0"
string(8) "4-4s4s*0"
string(9) "4-4s4s505"
string(9) "454s4s505"
string(12) "*4*string* *"
string(12) "*4*stri0g* *"
string(12) "*-*-tri0g* *"
string(33) "*-**-*-tstringi0g* *tstringi0g* *"
string(33) "4-4*4*-tstringi0g* *tstringi0g* *"
string(33) "4-4*4*5t5tringi0g* *tstringi0g* *"
string(33) "454*4*5t5tringi0g* *tstringi0g* *"
string(1) "-"
string(1) "s"
string(33) "*-**-*-tstringi0g* *tstringi0g* *"
string(1) "4"
string(1) "5"
string(1) "5"
string(9) "a54s4a50a"
string(33) "a54*4*5t5tringi0g* *tstringi0a* a"