mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
c06ddfee87
This is a follow up for 3f9562015e
.
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
35 lines
896 B
C
35 lines
896 B
C
#include "ruby.h"
|
|
|
|
VALUE rb_str_dup(VALUE str);
|
|
|
|
static VALUE
|
|
bug_rb_str_dup(VALUE self, VALUE str)
|
|
{
|
|
rb_check_type(str, T_STRING);
|
|
return rb_str_dup(str);
|
|
}
|
|
|
|
static VALUE
|
|
bug_shared_string_p(VALUE self, VALUE str)
|
|
{
|
|
rb_check_type(str, T_STRING);
|
|
return RB_FL_TEST(str, RUBY_ELTS_SHARED) && RB_FL_TEST(str, RSTRING_NOEMBED) ? Qtrue : Qfalse;
|
|
}
|
|
|
|
static VALUE
|
|
bug_sharing_with_shared_p(VALUE self, VALUE str)
|
|
{
|
|
rb_check_type(str, T_STRING);
|
|
if (bug_shared_string_p(self, str)) {
|
|
return bug_shared_string_p(self, RSTRING(str)->as.heap.aux.shared);
|
|
}
|
|
return Qfalse;
|
|
}
|
|
|
|
void
|
|
Init_string_rb_str_dup(VALUE klass)
|
|
{
|
|
rb_define_singleton_method(klass, "rb_str_dup", bug_rb_str_dup, 1);
|
|
rb_define_singleton_method(klass, "shared_string?", bug_shared_string_p, 1);
|
|
rb_define_singleton_method(klass, "sharing_with_shared?", bug_sharing_with_shared_p, 1);
|
|
}
|