Skip to content

Commit 0068849

Browse files
committed
Backport 4.0 [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 4ac2a34 commit 0068849

2 files changed

Lines changed: 32 additions & 4 deletions

File tree

‎string.c‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,8 +1808,6 @@ str_shared_replace(VALUE str, VALUE str2)
18081808
if (str_embed_capa(str) >= RSTRING_LEN(str2) + termlen) {
18091809
STR_SET_EMBED(str);
18101810
memcpy(RSTRING_PTR(str), RSTRING_PTR(str2), (size_t)RSTRING_LEN(str2) + termlen);
1811-
rb_enc_associate(str, enc);
1812-
ENC_CODERANGE_SET(str, cr);
18131811
}
18141812
else {
18151813
if (STR_EMBED_P(str2)) {
@@ -1841,9 +1839,12 @@ str_shared_replace(VALUE str, VALUE str2)
18411839
STR_SET_EMBED(str2);
18421840
RSTRING_PTR(str2)[0] = 0;
18431841
STR_SET_LEN(str2, 0);
1844-
rb_enc_associate(str, enc);
1845-
ENC_CODERANGE_SET(str, cr);
18461842
}
1843+
1844+
// We used str2's termlen above so we set enc raw
1845+
// to avoid adjusting it based on str1's old enc.
1846+
rb_enc_raw_set(str, enc);
1847+
ENC_CODERANGE_SET(str, cr);
18471848
}
18481849

18491850
VALUE

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require 'test/unit'
33
require '-test-/string'
44
require 'rbconfig/sizeof'
5+
require 'objspace'
56

67
class Test_StringCapacity < Test::Unit::TestCase
78
def test_capacity_embedded
@@ -74,12 +75,34 @@ def test_frozen_root_capacity_with_multibyte_terminator
7475
assert_equal(capacity, capa(s))
7576
end
7677

78+
def test_encode_bang_capacity_with_multibyte_terminator
79+
s = smallest_slot_utf16le_string
80+
assert(Bug::String.cstr_embedded?(s))
81+
82+
s.encode!("UTF-8")
83+
84+
# The UTF-8 form no longer fits in the slot so the buffer is on heap.
85+
refute(Bug::String.cstr_embedded?(s))
86+
87+
# and sized precisely
88+
assert_equal(s.bytesize, capa(s))
89+
end
90+
7791
private
7892

7993
def capa(str)
8094
Bug::String.capacity(str)
8195
end
8296

97+
# A UTF-16LE string that fills the smallest slot, so that its UTF-8 form
98+
# (1.5x as long) cannot stay embedded.
99+
def smallest_slot_utf16le_string
100+
embed_capa = pool_slot_size(0) - embed_header_size
101+
str = ("\u{30AF}" * ((embed_capa - 2) / 2)).encode("UTF-16LE").b
102+
str.force_encoding("UTF-16LE")
103+
str
104+
end
105+
83106
def multibyte_terminator_string
84107
("\u{30AF}\u{30FC}\u{30DD}\u{30F3}\u{1F381}" * 100).encode("UTF-16LE")
85108
end
@@ -88,6 +111,10 @@ def embed_header_size
88111
GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*']
89112
end
90113

114+
def pool_slot_size(_idx = 0)
115+
Integer(ObjectSpace.dump("")[/"slot_size":(\d+)/, 1])
116+
end
117+
91118
def max_embed_len
92119
GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] - embed_header_size - 1
93120
end

0 commit comments

Comments
 (0)