diff options
-rw-r--r-- | internal.h | 2 | ||||
-rw-r--r-- | io.c | 26 | ||||
-rw-r--r-- | string.c | 47 | ||||
-rw-r--r-- | test/ruby/test_io.rb | 18 | ||||
-rw-r--r-- | version.h | 2 |
5 files changed, 84 insertions, 11 deletions
diff --git a/internal.h b/internal.h index 6540f367f1..41d7c7569d 100644 --- a/internal.h +++ b/internal.h @@ -1459,6 +1459,8 @@ VALUE rb_id_quote_unprintable(ID); char *rb_str_fill_terminator(VALUE str, const int termlen); void rb_str_change_terminator_length(VALUE str, const int oldtermlen, const int termlen); VALUE rb_str_locktmp_ensure(VALUE str, VALUE (*func)(VALUE), VALUE arg); +VALUE rb_str_tmp_frozen_acquire(VALUE str); +void rb_str_tmp_frozen_release(VALUE str, VALUE tmp); VALUE rb_str_chomp_string(VALUE str, VALUE chomp); #ifdef RUBY_ENCODING_H VALUE rb_external_str_with_enc(VALUE str, rb_encoding *eenc); @@ -1423,6 +1423,9 @@ static long io_fwrite(VALUE str, rb_io_t *fptr, int nosync) { int converted = 0; + VALUE tmp; + long n, len; + const char *ptr; #ifdef _WIN32 if (fptr->mode & FMODE_TTY) { long len = rb_w32_write_console(str, fptr->fd); @@ -1432,11 +1435,13 @@ io_fwrite(VALUE str, rb_io_t *fptr, int nosync) str = do_writeconv(str, fptr, &converted); if (converted) OBJ_FREEZE(str); - else - str = rb_str_new_frozen(str); - return io_binwrite(str, RSTRING_PTR(str), RSTRING_LEN(str), - fptr, nosync); + tmp = rb_str_tmp_frozen_acquire(str); + RSTRING_GETMEM(tmp, ptr, len); + n = io_binwrite(tmp, ptr, len, fptr, nosync); + rb_str_tmp_frozen_release(str, tmp); + + return n; } ssize_t @@ -4727,8 +4732,10 @@ rb_io_sysseek(int argc, VALUE *argv, VALUE io) static VALUE rb_io_syswrite(VALUE io, VALUE str) { + VALUE tmp; rb_io_t *fptr; - long n; + long n, len; + const char *ptr; if (!RB_TYPE_P(str, T_STRING)) str = rb_obj_as_string(str); @@ -4737,16 +4744,15 @@ rb_io_syswrite(VALUE io, VALUE str) GetOpenFile(io, fptr); rb_io_check_writable(fptr); - str = rb_str_new_frozen(str); - if (fptr->wbuf.len) { rb_warn("syswrite for buffered IO"); } - n = rb_write_internal(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); - RB_GC_GUARD(str); - + tmp = rb_str_tmp_frozen_acquire(str); + RSTRING_GETMEM(tmp, ptr, len); + n = rb_write_internal(fptr->fd, ptr, len); if (n == -1) rb_sys_fail_path(fptr->pathv); + rb_str_tmp_frozen_release(str, tmp); return LONG2FIX(n); } @@ -70,6 +70,7 @@ VALUE rb_cSymbol; * 1: RSTRING_NOEMBED * 2: STR_SHARED (== ELTS_SHARED) * 2-6: RSTRING_EMBED_LEN (5 bits == 32) + * 6: STR_IS_SHARED_M (shared, when RSTRING_NOEMBED==1 && klass==0) * 7: STR_TMPLOCK * 8-9: ENC_CODERANGE (2 bits) * 10-16: ENCODING (7 bits == 128) @@ -79,6 +80,7 @@ VALUE rb_cSymbol; */ #define RUBY_MAX_CHAR_LEN 16 +#define STR_IS_SHARED_M FL_USER6 #define STR_TMPLOCK FL_USER7 #define STR_NOFREE FL_USER18 #define STR_FAKESTR FL_USER19 @@ -150,6 +152,8 @@ VALUE rb_cSymbol; if (!FL_TEST(str, STR_FAKESTR)) { \ RB_OBJ_WRITE((str), &RSTRING(str)->as.heap.aux.shared, (shared_str)); \ FL_SET((str), STR_SHARED); \ + if (RBASIC_CLASS((shared_str)) == 0) /* for CoW-friendliness */ \ + FL_SET_RAW((shared_str), STR_IS_SHARED_M); \ } \ } while (0) @@ -1127,6 +1131,45 @@ rb_str_new_frozen(VALUE orig) return str; } +VALUE +rb_str_tmp_frozen_acquire(VALUE orig) +{ + VALUE tmp; + + if (OBJ_FROZEN_RAW(orig)) return orig; + + tmp = str_new_frozen(0, orig); + OBJ_INFECT(tmp, orig); + + return tmp; +} + +void +rb_str_tmp_frozen_release(VALUE orig, VALUE tmp) +{ + if (RBASIC_CLASS(tmp) != 0) + return; + + if (STR_EMBED_P(tmp)) { + assert(OBJ_FROZEN_RAW(tmp)); + rb_gc_force_recycle(tmp); + } + else if (FL_TEST_RAW(orig, STR_SHARED) && + !FL_TEST_RAW(orig, STR_TMPLOCK|RUBY_FL_FREEZE)) { + VALUE shared = RSTRING(orig)->as.heap.aux.shared; + + if (shared == tmp && !FL_TEST_RAW(tmp, STR_IS_SHARED_M)) { + FL_UNSET_RAW(orig, STR_SHARED); + assert(RSTRING(orig)->as.heap.ptr == RSTRING(tmp)->as.heap.ptr); + assert(RSTRING(orig)->as.heap.len == RSTRING(tmp)->as.heap.len); + RSTRING(orig)->as.heap.aux.capa = RSTRING(tmp)->as.heap.aux.capa; + RBASIC(orig)->flags |= RBASIC(tmp)->flags & STR_NOFREE; + assert(OBJ_FROZEN_RAW(tmp)); + rb_gc_force_recycle(tmp); + } + } +} + static VALUE str_new_frozen(VALUE klass, VALUE orig) { @@ -1152,6 +1195,8 @@ str_new_frozen(VALUE klass, VALUE orig) RSTRING(str)->as.heap.len -= ofs + rest; } else { + if (RBASIC_CLASS(shared) == 0) + FL_SET_RAW(shared, STR_IS_SHARED_M); return shared; } } @@ -1171,6 +1216,8 @@ str_new_frozen(VALUE klass, VALUE orig) RBASIC(str)->flags |= RBASIC(orig)->flags & STR_NOFREE; RBASIC(orig)->flags &= ~STR_NOFREE; STR_SET_SHARED(orig, str); + if (klass == 0) + FL_UNSET_RAW(str, STR_IS_SHARED_M); } } diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index a6ab981a9c..61dfba3180 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -3508,5 +3508,23 @@ __END__ end end; end + + def test_write_no_garbage + res = {} + ObjectSpace.count_objects(res) # creates strings on first call + [ 'foo'.b, '*' * 24 ].each do |buf| + with_pipe do |r, w| + before = ObjectSpace.count_objects(res)[:T_STRING] + n = w.write(buf) + s = w.syswrite(buf) + after = ObjectSpace.count_objects(res)[:T_STRING] + assert_equal before, after, + 'no strings left over after write [ruby-core:78898] [Bug #13085]' + assert_not_predicate buf, :frozen?, 'no inadvertant freeze' + assert_equal buf.bytesize, n, 'IO#write wrote expected size' + assert_equal s, n, 'IO#syswrite wrote expected size' + end + end + end end end @@ -1,6 +1,6 @@ #define RUBY_VERSION "2.4.0" #define RUBY_RELEASE_DATE "2017-03-13" -#define RUBY_PATCHLEVEL 95 +#define RUBY_PATCHLEVEL 96 #define RUBY_RELEASE_YEAR 2017 #define RUBY_RELEASE_MONTH 3 |