From d50a309202fa7f96db9e6bdd7e4a907828c43612 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 15 Dec 2019 20:21:28 +0100 Subject: [PATCH 1/7] Allow variable strings to be defined for a string offset This stops truncating strings with multiple bytes to one byte and using the first byte as the char to replace the string offset. At the same time allow to pass an empty string to remove the byte from the string. This partially invalidates Bug 71572. --- Zend/tests/bug71572.phpt | 19 ++-- Zend/tests/indexing_001.phpt | 4 +- Zend/tests/str_offset_004.phpt | 4 +- .../string_offset_variable_length_values.phpt | 42 +++++++++ Zend/zend_execute.c | 89 +++++++++++++++---- tests/lang/bug22592.phpt | 18 ++-- 6 files changed, 132 insertions(+), 44 deletions(-) create mode 100644 Zend/tests/string_offset_variable_length_values.phpt 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..6c1c7bc596284 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_INTERNED_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 - 1); // -1 as we replace a byte + 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_INTERNED_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/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" From f898af98d461d00dc2e81382ac2242d99003952f Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 15 Dec 2019 21:47:46 +0100 Subject: [PATCH 2/7] Possible fix for JIT --- ext/opcache/jit/zend_jit_helpers.c | 94 +++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 22 deletions(-) diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 0efd07c3dce6c..48edc7ce48dd9 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -861,8 +861,8 @@ static zend_never_inline ZEND_COLD void zend_wrong_string_offset(void) static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, zval *value, zval *result) { - zend_string *old_str; - zend_uchar c; + zend_string *old_str + char *string_value; size_t string_len; zend_long offset; @@ -879,7 +879,6 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, if (Z_TYPE_P(value) != IS_STRING) { /* Convert to string, just the time to pick the 1st byte */ zend_string *tmp = zval_try_get_string_func(value); - if (UNEXPECTED(!tmp)) { if (result) { ZVAL_UNDEF(result); @@ -887,34 +886,55 @@ 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(tmp); + 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 (result) { - ZVAL_NULL(result); - } - 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) { + 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)) { + 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)); + } + } 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; + 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; + ZVAL_NEW_STR(str, zend_string_init(strcat(Z_STRVAL_P(str), string_value), offset + string_len, 0)); + if (result) { + ZVAL_INTERNED_STR(result, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 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); @@ -925,12 +945,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 - 1); // -1 as we replace a byte + 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 (result) { - /* Return the new character */ - ZVAL_INTERNED_STR(result, ZSTR_CHAR(c)); + ZVAL_INTERNED_STR(result, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); } + + efree(buffer); } static void ZEND_FASTCALL zend_jit_assign_dim_helper(zval *object_ptr, zval *dim, zval *value, zval *result) From d1c762b5dec22ca23cb2d5d5e3a0f9bde50848dc Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 16 Dec 2019 00:01:09 +0100 Subject: [PATCH 3/7] Revert "Possible fix for JIT" Because I don't know how to fix the error with the char * defintion This reverts commit 1c9a8aae81c1e911b3b6907a1b6d6e653069b6e5. --- ext/opcache/jit/zend_jit_helpers.c | 94 +++++++----------------------- 1 file changed, 22 insertions(+), 72 deletions(-) diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 48edc7ce48dd9..0efd07c3dce6c 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -861,8 +861,8 @@ static zend_never_inline ZEND_COLD void zend_wrong_string_offset(void) static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, zval *value, zval *result) { - zend_string *old_str - char *string_value; + zend_string *old_str; + zend_uchar c; size_t string_len; zend_long offset; @@ -879,6 +879,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, if (Z_TYPE_P(value) != IS_STRING) { /* Convert to string, just the time to pick the 1st byte */ zend_string *tmp = zval_try_get_string_func(value); + if (UNEXPECTED(!tmp)) { if (result) { ZVAL_UNDEF(result); @@ -886,55 +887,34 @@ 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); - zend_string_release_ex(tmp, 0); + c = (zend_uchar)ZSTR_VAL(tmp)[0]; + zend_string_release(tmp); } else { - string_value = Z_STRVAL_P(value); string_len = Z_STRLEN_P(value); + c = (zend_uchar)Z_STRVAL_P(value)[0]; } - 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)) { - 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 (string_len == 0) { + /* Error on empty input string */ + zend_error(E_WARNING, "Cannot assign an empty string to a string offset"); if (result) { - /* Return the new character */ - ZVAL_INTERNED_STR(result, ZSTR_CHAR(c)); + ZVAL_NULL(result); } + 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 */ zend_long old_len = Z_STRLEN_P(str); - ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), offset + string_len, 0)); + 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); - ZVAL_NEW_STR(str, zend_string_init(strcat(Z_STRVAL_P(str), string_value), offset + string_len, 0)); - if (result) { - ZVAL_INTERNED_STR(result, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); - } - return; + 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); @@ -945,42 +925,12 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, zend_string_forget_hash_val(Z_STR_P(str)); } - // Buffer offset - int k = 0; - // Source offset - int i = 0; - char *buffer = emalloc(Z_STRLEN_P(str) + string_len - 1); // -1 as we replace a byte - 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)); + Z_STRVAL_P(str)[offset] = c; + if (result) { - ZVAL_INTERNED_STR(result, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); + /* Return the new character */ + ZVAL_INTERNED_STR(result, ZSTR_CHAR(c)); } - - efree(buffer); } static void ZEND_FASTCALL zend_jit_assign_dim_helper(zval *object_ptr, zval *dim, zval *value, zval *result) From 236c583a47884bfeb283def46eafe8292d1d57ad Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 16 Dec 2019 04:46:41 +0100 Subject: [PATCH 4/7] New implementation (without leaks) --- Zend/zend_execute.c | 61 +++++++++---------------- ext/opcache/jit/zend_jit_helpers.c | 71 +++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 57 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6c1c7bc596284..d72aba01649f1 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1571,6 +1571,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, { char *string_value; size_t string_len; + zend_long old_len = Z_STRLEN_P(str); zend_long offset; offset = zend_check_string_offset(dim, BP_VAR_W EXECUTE_DATA_CC); @@ -1611,7 +1612,6 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, 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; @@ -1633,61 +1633,42 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, return; } + /* -1 for the byte we are replacing */ + zend_long new_len = old_len + string_len - 1; if ((size_t)offset >= Z_STRLEN_P(str)) { /* Extend string */ - zend_long old_len = Z_STRLEN_P(str); 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); memcpy(Z_STRVAL_P(str) + offset, string_value, string_len); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { - ZVAL_INTERNED_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); + 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)); + } + + zend_string *tmp = zend_string_init(Z_STRVAL_P(str), new_len, 0); + if (string_len > 0) { + memcpy(ZSTR_VAL(tmp) + offset, string_value, string_len); + } + /* Copy after the replacement string, from the position of the initial string after the offset, + * for the remainder of the initial string (old length - offset) */ + memcpy(ZSTR_VAL(tmp) + offset + string_len, Z_STRVAL_P(str) + offset + 1, old_len - offset); + + if (!Z_REFCOUNTED_P(str)) { + ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), new_len, 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)); + ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), new_len, 0)); } else { zend_string_forget_hash_val(Z_STR_P(str)); } + + memcpy(Z_STRVAL_P(str), ZSTR_VAL(tmp), new_len); + zend_string_release_ex(tmp, 0); - // Buffer offset - int k = 0; - // Source offset - int i = 0; - char *buffer = emalloc(Z_STRLEN_P(str) + string_len - 1); // -1 as we replace a byte - 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))) { - ZVAL_INTERNED_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); + 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..3a55bcc8707ad 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -862,6 +862,7 @@ static zend_never_inline ZEND_COLD void zend_wrong_string_offset(void) static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, zval *value, zval *result) { zend_string *old_str; + zend_long old_len = Z_STRLEN_P(str); zend_uchar c; size_t string_len; zend_long offset; @@ -895,27 +896,63 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, 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) { + if ((size_t) offset >= Z_STRLEN_P(str)) { + /* Extend string if needed */ + 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) { - ZVAL_NULL(result); + /* Return the new character */ + ZVAL_INTERNED_STR(result, ZSTR_CHAR(c)); } return; } - if (offset < 0) { /* Handle negative offset */ - offset += (zend_long)Z_STRLEN_P(str); - } - + /* -1 for the byte we are replacing */ + zend_long new_len = old_len + string_len - 1; 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; + old_str = Z_STR_P(str); + /* Extend string */ + 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; - } else if (!Z_REFCOUNTED_P(str)) { + memcpy(Z_STRVAL_P(str) + offset, ZSTR_VAL(old_str), string_len); + + zend_string_release(old_str); + if (result) { + ZVAL_STR(result, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); + } + return; + } + + old_str = Z_STR_P(str); + zend_string *tmp = zend_string_init(Z_STRVAL_P(str), new_len, 0); + if (string_len > 0) { + memcpy(ZSTR_VAL(tmp) + offset, ZSTR_VAL(old_str), string_len); + } + /* Copy after the replacement string, from the position of the initial string after the offset, + * for the remainder of the initial string (old length - offset) */ + memcpy(ZSTR_VAL(tmp) + offset + string_len, Z_STRVAL_P(str) + offset + 1, old_len - offset); + + 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; @@ -925,11 +962,11 @@ 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; + memcpy(Z_STRVAL_P(str), ZSTR_VAL(tmp), new_len); + zend_string_release_ex(tmp, 0); 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)); } } From 7ed0a4d30871c8126cccdd40917746417ea5a217 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 16 Dec 2019 09:10:14 +0100 Subject: [PATCH 5/7] Revert "New implementation (without leaks)" Due to some null bytes disapering and I have no idea why. This reverts commit 236c583a47884bfeb283def46eafe8292d1d57ad. --- Zend/zend_execute.c | 61 ++++++++++++++++--------- ext/opcache/jit/zend_jit_helpers.c | 71 +++++++----------------------- 2 files changed, 57 insertions(+), 75 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index d72aba01649f1..6c1c7bc596284 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1571,7 +1571,6 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, { char *string_value; size_t string_len; - zend_long old_len = Z_STRLEN_P(str); zend_long offset; offset = zend_check_string_offset(dim, BP_VAR_W EXECUTE_DATA_CC); @@ -1612,6 +1611,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, 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; @@ -1633,42 +1633,61 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, return; } - /* -1 for the byte we are replacing */ - zend_long new_len = old_len + string_len - 1; if ((size_t)offset >= Z_STRLEN_P(str)) { /* Extend string */ + zend_long old_len = Z_STRLEN_P(str); 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); 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)); + ZVAL_INTERNED_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); } return; - } - - zend_string *tmp = zend_string_init(Z_STRVAL_P(str), new_len, 0); - if (string_len > 0) { - memcpy(ZSTR_VAL(tmp) + offset, string_value, string_len); - } - /* Copy after the replacement string, from the position of the initial string after the offset, - * for the remainder of the initial string (old length - offset) */ - memcpy(ZSTR_VAL(tmp) + offset + string_len, Z_STRVAL_P(str) + offset + 1, old_len - offset); - - if (!Z_REFCOUNTED_P(str)) { - ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), new_len, 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), new_len, 0)); + 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)); } - - memcpy(Z_STRVAL_P(str), ZSTR_VAL(tmp), new_len); - zend_string_release_ex(tmp, 0); + // Buffer offset + int k = 0; + // Source offset + int i = 0; + char *buffer = emalloc(Z_STRLEN_P(str) + string_len - 1); // -1 as we replace a byte + 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))) { - ZVAL_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); + ZVAL_INTERNED_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 3a55bcc8707ad..0efd07c3dce6c 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -862,7 +862,6 @@ static zend_never_inline ZEND_COLD void zend_wrong_string_offset(void) static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, zval *value, zval *result) { zend_string *old_str; - zend_long old_len = Z_STRLEN_P(str); zend_uchar c; size_t string_len; zend_long offset; @@ -896,63 +895,27 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, c = (zend_uchar)Z_STRVAL_P(value)[0]; } - 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) { - if ((size_t) offset >= Z_STRLEN_P(str)) { - /* Extend string if needed */ - 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 (string_len == 0) { + /* Error on empty input string */ + zend_error(E_WARNING, "Cannot assign an empty string to a string offset"); if (result) { - /* Return the new character */ - ZVAL_INTERNED_STR(result, ZSTR_CHAR(c)); + ZVAL_NULL(result); } return; } - /* -1 for the byte we are replacing */ - zend_long new_len = old_len + string_len - 1; - if ((size_t)offset >= Z_STRLEN_P(str)) { - old_str = Z_STR_P(str); - /* Extend string */ - 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); - memcpy(Z_STRVAL_P(str) + offset, ZSTR_VAL(old_str), string_len); - - zend_string_release(old_str); - if (result) { - ZVAL_STR(result, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); - } - return; + if (offset < 0) { /* Handle negative offset */ + offset += (zend_long)Z_STRLEN_P(str); } - old_str = Z_STR_P(str); - zend_string *tmp = zend_string_init(Z_STRVAL_P(str), new_len, 0); - if (string_len > 0) { - memcpy(ZSTR_VAL(tmp) + offset, ZSTR_VAL(old_str), string_len); - } - /* Copy after the replacement string, from the position of the initial string after the offset, - * for the remainder of the initial string (old length - offset) */ - memcpy(ZSTR_VAL(tmp) + offset + string_len, Z_STRVAL_P(str) + offset + 1, old_len - offset); - - if (!Z_REFCOUNTED_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; @@ -962,11 +925,11 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, zend_string_forget_hash_val(Z_STR_P(str)); } - memcpy(Z_STRVAL_P(str), ZSTR_VAL(tmp), new_len); - zend_string_release_ex(tmp, 0); + Z_STRVAL_P(str)[offset] = c; if (result) { - ZVAL_STR(result, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); + /* Return the new character */ + ZVAL_INTERNED_STR(result, ZSTR_CHAR(c)); } } From 7e61e6c06364f553860e23d544f643ed7ca100af Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 16 Dec 2019 09:34:58 +0100 Subject: [PATCH 6/7] Fix leaks --- Zend/zend_execute.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6c1c7bc596284..813cee45176d4 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1640,7 +1640,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len); memcpy(Z_STRVAL_P(str) + offset, string_value, string_len); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { - ZVAL_INTERNED_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); + 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)) { @@ -1656,7 +1656,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, int k = 0; // Source offset int i = 0; - char *buffer = emalloc(Z_STRLEN_P(str) + string_len - 1); // -1 as we replace a byte + 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) { @@ -1684,7 +1684,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, 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))) { - ZVAL_INTERNED_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); + ZVAL_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0)); } efree(buffer); From 0b6d52c8d17f874560f3e3080a571f640f60ffab Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 16 Dec 2019 10:16:12 +0100 Subject: [PATCH 7/7] Add JIT implementation --- ext/opcache/jit/zend_jit_helpers.c | 101 ++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 17 deletions(-) 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)); } }