Skip to content

Commit 390d77b

Browse files
committed
Fix memory leak in String#encode when StringValue raises
The following script leaks memory: 10.times do 100_000.times do "\ufffd".encode(Encoding::US_ASCII, fallback: proc { Object.new }) rescue end puts `ps -o rss= -p #{$$}` end Before: 450244 887748 1325124 1762756 2200260 2637508 3075012 3512516 3950020 4387524 After: 12236 12364 12748 13004 13388 13516 13772 13772 13772 13772
1 parent 5384136 commit 390d77b

File tree

2 files changed

+37
-2
lines changed

2 files changed

+37
-2
lines changed

test/ruby/test_string.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3807,6 +3807,36 @@ class MyError < StandardError; end
38073807
end
38083808
end
38093809

3810+
def test_encode_fallback_not_string_memory_leak
3811+
{
3812+
"hash" => <<~RUBY,
3813+
fallback = Hash.new { Object.new }
3814+
RUBY
3815+
"proc" => <<~RUBY,
3816+
fallback = proc { Object.new }
3817+
RUBY
3818+
"method" => <<~RUBY,
3819+
def my_method(_str) = Object.new
3820+
fallback = method(:my_method)
3821+
RUBY
3822+
"aref" => <<~RUBY,
3823+
fallback = Object.new
3824+
def fallback.[](_str) = Object.new
3825+
RUBY
3826+
}.each do |type, code|
3827+
assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true)
3828+
class MyError < StandardError; end
3829+
3830+
#{code}
3831+
3832+
100_000.times do |i|
3833+
"\\ufffd".encode(Encoding::US_ASCII, fallback:)
3834+
rescue TypeError
3835+
end
3836+
RUBY
3837+
end
3838+
end
3839+
38103840
private
38113841

38123842
def assert_bytesplice_result(expected, s, *args)

transcode.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2360,7 +2360,13 @@ transcode_loop_fallback_try(VALUE a)
23602360
{
23612361
struct transcode_loop_fallback_args *args = (struct transcode_loop_fallback_args *)a;
23622362

2363-
return args->fallback_func(args->fallback, args->rep);
2363+
VALUE ret = args->fallback_func(args->fallback, args->rep);
2364+
2365+
if (!UNDEF_P(ret) && !NIL_P(ret)) {
2366+
StringValue(ret);
2367+
}
2368+
2369+
return ret;
23642370
}
23652371

23662372
static void
@@ -2428,7 +2434,6 @@ transcode_loop(const unsigned char **in_pos, unsigned char **out_pos,
24282434
}
24292435

24302436
if (!UNDEF_P(rep) && !NIL_P(rep)) {
2431-
StringValue(rep);
24322437
ret = rb_econv_insert_output(ec, (const unsigned char *)RSTRING_PTR(rep),
24332438
RSTRING_LEN(rep), rb_enc_name(rb_enc_get(rep)));
24342439
if ((int)ret == -1) {

0 commit comments

Comments
 (0)