diff options
author | nagachika <nagachika@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2019-08-07 11:45:24 +0000 |
---|---|---|
committer | nagachika <nagachika@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2019-08-07 11:45:24 +0000 |
commit | f5930c87174c369eaad42523ffd0f3cb8ff15b8a (patch) | |
tree | 990b1ae441a12e0522672c1819c0ea75f311b57f /test | |
parent | b405ad8ed489232ea6051d44695a57156eb2a5cc (diff) |
merge revision(s) 3f9562015e651735bfc2fdd14e8f6963b673e22a,c06ddfee878524168e4af07443217ed2f8d0954b,3b3b4a44e57dfe03ce3913009d69a33d6f6100be: [Backport #15792]
Get rid of indirect sharing
* string.c (str_duplicate): share the root shared string if the
original string is already sharing, so that all shared strings
refer the root shared string directly. indirect sharing can
cause a dangling pointer.
[Bug #15792]
str_duplicate: Don't share with a frozen shared string
This is a follow up for 3f9562015e651735bfc2fdd14e8f6963b673e22a.
Before this commit, it was possible to create a shared string which
shares with another shared string by passing a frozen shared string
to `str_duplicate`.
Such string looks like:
```
-------- -----------------
| root | ------ owns -----> | root's buffer |
-------- -----------------
^ ^ ^
----------- | |
| shared1 | ------ references ----- |
----------- |
^ |
----------- |
| shared2 | ------ references ---------
-----------
```
This is bad news because `rb_fstring(shared2)` can make `shared1`
independent, which severs the reference from `shared1` to `root`:
```c
/* from fstr_update_callback() */
str = str_new_frozen(rb_cString, shared2); /* can return shared1 */
if (STR_SHARED_P(str)) { /* shared1 is also a shared string */
str_make_independent(str); /* no frozen check */
}
```
If `shared1` was the only reference to `root`, then `root` can be
reclaimed by the GC, leaving `shared2` in a corrupted state:
```
----------- --------------------
| shared1 | -------- owns --------> | shared1's buffer |
----------- --------------------
^
|
----------- -------------------------
| shared2 | ------ references ----> | root's buffer (freed) |
----------- -------------------------
```
Here is a reproduction script for the situation this commit fixes.
```ruby
a = ('a' * 24).strip.freeze.strip
-a
p a
4.times { GC.start }
p a
```
- string.c (str_duplicate): always share with the root string when
the original is a shared string.
- test_rb_str_dup.rb: specifically test `rb_str_dup` to make
sure it does not try to share with a shared string.
[Bug #15792]
Closes: https://github.com/ruby/ruby/pull/2159
Update dependencies
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_2_6@67731 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'test')
-rw-r--r-- | test/-ext-/string/test_rb_str_dup.rb | 16 | ||||
-rw-r--r-- | test/ruby/test_string.rb | 9 |
2 files changed, 25 insertions, 0 deletions
diff --git a/test/-ext-/string/test_rb_str_dup.rb b/test/-ext-/string/test_rb_str_dup.rb new file mode 100644 index 0000000000..49b6af9598 --- /dev/null +++ b/test/-ext-/string/test_rb_str_dup.rb @@ -0,0 +1,16 @@ +require 'test/unit' +require '-test-/string' + +class Test_RbStrDup < Test::Unit::TestCase + def test_nested_shared_non_frozen + str = Bug::String.rb_str_dup(Bug::String.rb_str_dup("a" * 50)) + assert_send([Bug::String, :shared_string?, str]) + assert_not_send([Bug::String, :sharing_with_shared?, str], '[Bug #15792]') + end + + def test_nested_shared_frozen + str = Bug::String.rb_str_dup(Bug::String.rb_str_dup("a" * 50).freeze) + assert_send([Bug::String, :shared_string?, str]) + assert_not_send([Bug::String, :sharing_with_shared?, str], '[Bug #15792]') + end +end diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 1fa5126931..d50380aaf6 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -2972,6 +2972,15 @@ CODE end =end + def test_nesting_shared + a = ('a' * 24).encode(Encoding::ASCII).gsub('x', '') + hash = {} + hash[a] = true + assert_equal(('a' * 24), a) + 4.times { GC.start } + assert_equal(('a' * 24), a, '[Bug #15792]') + end + def test_shared_force_encoding s = "\u{3066}\u{3059}\u{3068}".gsub(//, '') h = {} |