Skip to content

Commit 9ea82a2

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. This should be backported to 4.0
1 parent 5d10671 commit 9ea82a2

3 files changed

Lines changed: 29 additions & 1 deletion

File tree

‎ext/-test-/string/capacity.c‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@ bug_str_new_shared(VALUE klass, VALUE str)
1717
return rb_str_new_shared(str);
1818
}
1919

20+
static VALUE
21+
bug_str_tmp_frozen_acquire_release(VALUE klass, VALUE str)
22+
{
23+
VALUE tmp = rb_str_tmp_frozen_acquire(str);
24+
rb_str_tmp_frozen_release(str, tmp);
25+
return str;
26+
}
27+
2028
void
2129
Init_string_capacity(VALUE klass)
2230
{
2331
rb_define_singleton_method(klass, "capacity", bug_str_capacity, 1);
2432
rb_define_singleton_method(klass, "rb_str_new_shared", bug_str_new_shared, 1);
33+
rb_define_singleton_method(klass, "tmp_frozen_acquire_release", bug_str_tmp_frozen_acquire_release, 1);
2534
}

‎string.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1629,7 +1629,7 @@ rb_str_tmp_frozen_release(VALUE orig, VALUE tmp)
16291629
if (STR_EMBED_P(tmp)) {
16301630
RUBY_ASSERT(OBJ_FROZEN_RAW(tmp));
16311631
}
1632-
else if (FL_TEST_RAW(orig, STR_SHARED | STR_TMPLOCK) == STR_TMPLOCK &&
1632+
else if (FL_TEST_RAW(orig, STR_SHARED | STR_TMPLOCK) == STR_SHARED &&
16331633
!OBJ_FROZEN_RAW(orig)) {
16341634
VALUE shared = RSTRING(orig)->as.heap.aux.shared;
16351635

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,31 @@ def test_capacity_fstring
6060
assert_equal(s.length, capa(s))
6161
end
6262

63+
# Temporarily handing a string's buffer over to a frozen shared root
64+
# (rb_str_tmp_frozen_acquire) uses an internal ASCII-8BIT string. Use
65+
# UTF-16LE so that the terminator length of the root differs from the
66+
# terminator length of the string to verify that releasing the root restores
67+
# the capacity in the string's own encoding.
68+
def test_frozen_root_capacity_with_multibyte_terminator
69+
s = multibyte_terminator_string
70+
capacity = capa(s)
71+
assert_operator(capacity, :>=, s.bytesize)
72+
73+
Bug::String.tmp_frozen_acquire_release(s)
74+
75+
assert_equal(capacity, capa(s))
76+
end
77+
6378
private
6479

6580
def capa(str)
6681
Bug::String.capacity(str)
6782
end
6883

84+
def multibyte_terminator_string
85+
("\u{30AF}\u{30FC}\u{30DD}\u{30F3}\u{1F381}" * 100).encode("UTF-16LE")
86+
end
87+
6988
def embed_header_size
7089
GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*']
7190
end

0 commit comments

Comments
 (0)