Skip to content

Commit bef69ce

Browse files
committed
[Bug #22387] Fix termlen in str_shared_replace
The buffer is copied over based on the new string's termlen so we can just set the new encoding raw instead of recalculating based on the old encoding's termlen.
1 parent e45f082 commit bef69ce

2 files changed

Lines changed: 27 additions & 4 deletions

File tree

‎string.c‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,8 +1853,6 @@ str_shared_replace(VALUE str, VALUE str2)
18531853
if (str_embed_capa(str) >= RSTRING_LEN(str2) + termlen) {
18541854
STR_SET_EMBED(str);
18551855
memcpy(RSTRING_PTR(str), RSTRING_PTR(str2), (size_t)RSTRING_LEN(str2) + termlen);
1856-
rb_enc_associate(str, enc);
1857-
ENC_CODERANGE_SET(str, cr);
18581856
}
18591857
else {
18601858
if (STR_EMBED_P(str2)) {
@@ -1886,9 +1884,12 @@ str_shared_replace(VALUE str, VALUE str2)
18861884
STR_SET_EMBED(str2);
18871885
RSTRING_PTR(str2)[0] = 0;
18881886
STR_SET_LEN(str2, 0);
1889-
rb_enc_associate(str, enc);
1890-
ENC_CODERANGE_SET(str, cr);
18911887
}
1888+
1889+
// We used str2's termlen above so we set enc raw
1890+
// to avoid adjusting it based on str1's old enc.
1891+
rb_enc_raw_set(str, enc);
1892+
ENC_CODERANGE_SET(str, cr);
18921893
}
18931894

18941895
VALUE

‎test/-ext-/string/test_capacity.rb‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,34 @@ def test_frozen_root_no_embed_capacity_with_multibyte_terminator
8787
assert_equal(capacity, capa(s))
8888
end
8989

90+
def test_encode_bang_capacity_with_multibyte_terminator
91+
s = smallest_slot_utf16le_string
92+
assert(Bug::String.cstr_embedded?(s))
93+
94+
s.encode!("UTF-8")
95+
96+
# The UTF-8 form no longer fits in the slot so the buffer is on heap.
97+
refute(Bug::String.cstr_embedded?(s))
98+
99+
# and sized precisely
100+
assert_equal(s.bytesize, capa(s))
101+
end
102+
90103
private
91104

92105
def capa(str)
93106
Bug::String.capacity(str)
94107
end
95108

109+
# A UTF-16LE string that fills the smallest slot, so that its UTF-8 form
110+
# (1.5x as long) cannot stay embedded.
111+
def smallest_slot_utf16le_string
112+
embed_capa = pool_slot_size(0) - embed_header_size
113+
str = ("\u{30AF}" * ((embed_capa - 2) / 2)).encode("UTF-16LE").b
114+
str.force_encoding("UTF-16LE")
115+
str
116+
end
117+
96118
def multibyte_terminator_string
97119
("\u{30AF}\u{30FC}\u{30DD}\u{30F3}\u{1F381}" * 100).encode("UTF-16LE")
98120
end

0 commit comments

Comments
 (0)