Skip to content

Commit d50a309

Browse files
committed
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.
1 parent 7df8f95 commit d50a309

File tree

6 files changed

+132
-44
lines changed

6 files changed

+132
-44
lines changed

Zend/tests/bug71572.phpt

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,9 @@ var_dump($str[3] = "");
1010
var_dump($str[10] = "");
1111
var_dump($str);
1212
?>
13-
--EXPECTF--
14-
Warning: Cannot assign an empty string to a string offset in %s on line %d
15-
NULL
16-
17-
Warning: Cannot assign an empty string to a string offset in %s on line %d
18-
NULL
19-
20-
Warning: Cannot assign an empty string to a string offset in %s on line %d
21-
NULL
22-
23-
Warning: Cannot assign an empty string to a string offset in %s on line %d
24-
NULL
25-
string(3) "abc"
13+
--EXPECT--
14+
string(2) "bc"
15+
string(1) "b"
16+
string(3) "b "
17+
string(10) "b "
18+
string(10) "b "

Zend/tests/indexing_001.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ array(1) {
8080
Warning: Illegal string offset 'foo' in %s on line %d
8181

8282
Warning: Array to string conversion in %s on line %d
83-
string(1) "A"
83+
string(5) "Array"
8484

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

8787
Warning: Array to string conversion in %s on line %d
88-
string(1) "A"
88+
string(5) "Array"
8989
Cannot use a scalar value as an array
9090
float(0.1)
9191
array(1) {

Zend/tests/str_offset_004.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,5 @@ Warning: Illegal string offset: -20 in %sstr_offset_004.php on line %d
4545
string(15) "abCZefghijPQmno"
4646
string(15) "AbCZefghijPQmno"
4747
string(21) "AbCZefghijPQmno N"
48-
string(21) "AbCZefghijPQmno UN"
49-
string(21) "AbCZefghijPQmno nUN"
48+
string(23) "AbCZefghijPQmno UFON"
49+
string(23) "AbCZefghijPQmno U ON"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Some string offset replacement with variable length values
3+
--FILE--
4+
<?php
5+
6+
function str(): string { return "Hello world"; }
7+
var_dump(str()[20] = '');
8+
var_dump(str()[20] = '');
9+
var_dump(str()[0] = '');
10+
var_dump(str()[0] = '');
11+
var_dump(str()[4] = '');
12+
var_dump(str()[4] = '');
13+
var_dump(str()[-5] = '');
14+
var_dump(str()[-5] = '');
15+
16+
try {
17+
var_dump(str()[-20] = '');
18+
} catch (\Error $e) {
19+
echo $e->getMessage(), \PHP_EOL;
20+
}
21+
try {
22+
var_dump(str()[-20] = '');
23+
} catch (\Error $e) {
24+
echo $e->getMessage(), \PHP_EOL;
25+
}
26+
27+
?>
28+
--EXPECTF--
29+
string(20) "Hello world "
30+
string(23) "Hello world あ"
31+
string(10) "ello world"
32+
string(13) "あello world"
33+
string(10) "Hell world"
34+
string(13) "Hellあ world"
35+
string(10) "Hello orld"
36+
string(13) "Hello あorld"
37+
38+
Warning: Illegal string offset: -20 in %s on line %d
39+
NULL
40+
41+
Warning: Illegal string offset: -20 in %s on line %d
42+
NULL

Zend/zend_execute.c

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,7 +1569,7 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_deprecated_function(c
15691569

15701570
static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, zval *value OPLINE_DC EXECUTE_DATA_DC)
15711571
{
1572-
zend_uchar c;
1572+
char *string_value;
15731573
size_t string_len;
15741574
zend_long offset;
15751575

@@ -1593,33 +1593,56 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
15931593
return;
15941594
}
15951595

1596+
string_value = ZSTR_VAL(tmp);
15961597
string_len = ZSTR_LEN(tmp);
1597-
c = (zend_uchar)ZSTR_VAL(tmp)[0];
15981598
zend_string_release_ex(tmp, 0);
15991599
} else {
1600+
string_value = Z_STRVAL_P(value);
16001601
string_len = Z_STRLEN_P(value);
1601-
c = (zend_uchar)Z_STRVAL_P(value)[0];
16021602
}
16031603

1604-
if (string_len == 0) {
1605-
/* Error on empty input string */
1606-
zend_error(E_WARNING, "Cannot assign an empty string to a string offset");
1604+
if (offset < 0) { /* Handle negative offset */
1605+
offset += (zend_long)Z_STRLEN_P(str);
1606+
}
1607+
1608+
/* If it's a byte char replace byte directly */
1609+
if (string_len == 1) {
1610+
zend_uchar c = (zend_uchar) string_value[0];
1611+
1612+
if ((size_t)offset >= Z_STRLEN_P(str)) {
1613+
/* Extend string if needed */
1614+
zend_long old_len = Z_STRLEN_P(str);
1615+
ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), offset + 1, 0));
1616+
memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len);
1617+
Z_STRVAL_P(str)[offset+1] = 0;
1618+
} else if (!Z_REFCOUNTED_P(str)) {
1619+
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
1620+
} else if (Z_REFCOUNT_P(str) > 1) {
1621+
Z_DELREF_P(str);
1622+
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
1623+
} else {
1624+
zend_string_forget_hash_val(Z_STR_P(str));
1625+
}
1626+
1627+
Z_STRVAL_P(str)[offset] = c;
1628+
16071629
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1608-
ZVAL_NULL(EX_VAR(opline->result.var));
1630+
/* Return the new character */
1631+
ZVAL_INTERNED_STR(EX_VAR(opline->result.var), ZSTR_CHAR(c));
16091632
}
16101633
return;
16111634
}
16121635

1613-
if (offset < 0) { /* Handle negative offset */
1614-
offset += (zend_long)Z_STRLEN_P(str);
1615-
}
1616-
16171636
if ((size_t)offset >= Z_STRLEN_P(str)) {
1618-
/* Extend string if needed */
1637+
/* Extend string */
16191638
zend_long old_len = Z_STRLEN_P(str);
1620-
ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), offset + 1, 0));
1639+
ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), offset + string_len, 0));
16211640
memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len);
1622-
Z_STRVAL_P(str)[offset+1] = 0;
1641+
memcpy(Z_STRVAL_P(str) + offset, string_value, string_len);
1642+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1643+
ZVAL_INTERNED_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
1644+
}
1645+
return;
16231646
} else if (!Z_REFCOUNTED_P(str)) {
16241647
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
16251648
} 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,
16291652
zend_string_forget_hash_val(Z_STR_P(str));
16301653
}
16311654

1632-
Z_STRVAL_P(str)[offset] = c;
1633-
1655+
// Buffer offset
1656+
int k = 0;
1657+
// Source offset
1658+
int i = 0;
1659+
char *buffer = emalloc(Z_STRLEN_P(str) + string_len - 1); // -1 as we replace a byte
1660+
char *source = Z_STRVAL_P(str);
1661+
// Append bytes from the source string to the buffer until the offset is reached
1662+
while (i < offset) {
1663+
buffer[k] = source[i];
1664+
i++;
1665+
k++;
1666+
}
1667+
i++; // Skip byte being replaced
1668+
// If not an empty string then append all the bytes from the value to the buffer
1669+
if (string_len > 0) {
1670+
int j = 0;
1671+
while (string_value[j] != '\0') {
1672+
buffer[k] = string_value[j];
1673+
j++;
1674+
k++;
1675+
}
1676+
}
1677+
// Add remaining bytes from the source string.
1678+
while (source[i] != '\0') {
1679+
buffer[k] = source[i];
1680+
i++;
1681+
k++;
1682+
}
1683+
// Append NUL byte to make a valid C string.
1684+
buffer[k] = '\0';
1685+
ZVAL_NEW_STR(str, zend_string_init(buffer, Z_STRLEN_P(str) + string_len - 1, 0));
16341686
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1635-
/* Return the new character */
1636-
ZVAL_INTERNED_STR(EX_VAR(opline->result.var), ZSTR_CHAR(c));
1687+
ZVAL_INTERNED_STR(EX_VAR(opline->result.var), zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
16371688
}
1689+
1690+
efree(buffer);
16381691
}
16391692

16401693
static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *ref)

tests/lang/bug22592.phpt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ var_dump($result);
3939
string(5) "* *-*"
4040
string(7) "* *-* *"
4141
string(7) "*4*-* *"
42-
string(7) "*4*s* *"
43-
string(8) "*4*s* *0"
44-
string(8) "*-*-* *0"
45-
string(8) "*-*s*s*0"
46-
string(8) "4-4s4s*0"
47-
string(9) "4-4s4s505"
48-
string(9) "454s4s505"
42+
string(12) "*4*string* *"
43+
string(12) "*4*stri0g* *"
44+
string(12) "*-*-tri0g* *"
45+
string(33) "*-**-*-tstringi0g* *tstringi0g* *"
46+
string(33) "4-4*4*-tstringi0g* *tstringi0g* *"
47+
string(33) "4-4*4*5t5tringi0g* *tstringi0g* *"
48+
string(33) "454*4*5t5tringi0g* *tstringi0g* *"
4949
string(1) "-"
50-
string(1) "s"
50+
string(33) "*-**-*-tstringi0g* *tstringi0g* *"
5151
string(1) "4"
5252
string(1) "5"
5353
string(1) "5"
54-
string(9) "a54s4a50a"
54+
string(33) "a54*4*5t5tringi0g* *tstringi0a* a"

0 commit comments

Comments
 (0)