diff --git a/Zend/tests/bug71572.phpt b/Zend/tests/bug71572.phpt index 063a98fcb917e..dc1c15f749783 100644 --- a/Zend/tests/bug71572.phpt +++ b/Zend/tests/bug71572.phpt @@ -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 " diff --git a/Zend/tests/indexing_001.phpt b/Zend/tests/indexing_001.phpt index 453b5ca86f80d..f0b18e364363f 100644 --- a/Zend/tests/indexing_001.phpt +++ b/Zend/tests/indexing_001.phpt @@ -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) { diff --git a/Zend/tests/str_offset_004.phpt b/Zend/tests/str_offset_004.phpt index 435ab235fa61d..c6a7920d0bc3e 100644 --- a/Zend/tests/str_offset_004.phpt +++ b/Zend/tests/str_offset_004.phpt @@ -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" diff --git a/Zend/tests/string_offset_variable_length_values.phpt b/Zend/tests/string_offset_variable_length_values.phpt new file mode 100644 index 0000000000000..f5c0fc064e791 --- /dev/null +++ b/Zend/tests/string_offset_variable_length_values.phpt @@ -0,0 +1,42 @@ +--TEST-- +Some string offset replacement with variable length values +--FILE-- +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 diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 275a3be309084..813cee45176d4 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -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; @@ -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) { @@ -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) diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 0efd07c3dce6c..b49e2b54562b1 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -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; @@ -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) { @@ -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)); } } diff --git a/tests/lang/bug22592.phpt b/tests/lang/bug22592.phpt index 4614efc1692b7..3dcc5b8c6629e 100644 --- a/tests/lang/bug22592.phpt +++ b/tests/lang/bug22592.phpt @@ -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"