Skip to content

Commit 123a765

Browse files
committed
[Bug #22383] Fix inverted STR_SHARED check in rb_str_tmp_frozen_release
45a2c95 mistakenly rewrote FL_TEST_RAW(orig, STR_SHARED) && !FL_TEST_RAW(orig, STR_TMPLOCK|RUBY_FL_FREEZE) as FL_TEST_RAW(orig, STR_SHARED | STR_TMPLOCK) == STR_TMPLOCK requiring orig to not be shared, the opposite of the original condition. orig always shares the buffer with tmp here, so the buffer was never given back and the string stayed shared until its next modification copied it. The code then read aux.shared from strings that are not shared, where the union holds aux.capa. Backport of ruby#19018
1 parent 1533c10 commit 123a765

2 files changed

Lines changed: 24 additions & 1 deletion

File tree

‎string.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1592,7 +1592,7 @@ rb_str_tmp_frozen_release(VALUE orig, VALUE tmp)
15921592
if (STR_EMBED_P(tmp)) {
15931593
RUBY_ASSERT(OBJ_FROZEN_RAW(tmp));
15941594
}
1595-
else if (FL_TEST_RAW(orig, STR_SHARED | STR_TMPLOCK) == STR_TMPLOCK &&
1595+
else if (FL_TEST_RAW(orig, STR_SHARED | STR_TMPLOCK) == STR_SHARED &&
15961596
!OBJ_FROZEN_RAW(orig)) {
15971597
VALUE shared = RSTRING(orig)->as.heap.aux.shared;
15981598

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,35 @@ def test_capacity_fstring
5959
assert_equal(s.length, capa(s))
6060
end
6161

62+
# Writing a string to an IO temporarily hands its buffer over to a frozen
63+
# shared root (rb_str_tmp_frozen_acquire), which is an internal ASCII-8BIT
64+
# string. Use UTF-16LE so that the terminator length of the root (1) differs
65+
# from the terminator length of the string (2): releasing the root must
66+
# restore the capacity in the string's own encoding, otherwise the string's
67+
# idea of its buffer size (capa + terminator length) no longer matches the
68+
# size the buffer was allocated with.
69+
def test_frozen_root_capacity_with_multibyte_terminator
70+
s = multibyte_terminator_string
71+
capacity = capa(s)
72+
assert_operator(capacity, :>=, s.bytesize)
73+
74+
# Multiple arguments go through the writev path, which uses
75+
# rb_str_tmp_frozen_acquire.
76+
IO.pipe {|r, w| w.write(s, "")}
77+
78+
assert_equal(capacity, capa(s))
79+
end
80+
6281
private
6382

6483
def capa(str)
6584
Bug::String.capacity(str)
6685
end
6786

87+
def multibyte_terminator_string
88+
("\u{30AF}\u{30FC}\u{30DD}\u{30F3}\u{1F381}" * 100).encode("UTF-16LE")
89+
end
90+
6891
def embed_header_size
6992
GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*']
7093
end

0 commit comments

Comments
 (0)