load.c: avoid segfault when 'throw' occurs in the middle of rb_load_file_str

How can a 'throw' happen while the current thread is reading a Ruby source file
from disk and parsing it? It can happen if another thread calls Thread#raise,
and passes an Exception object which responds to #exception, and the custom #exception
method calls Kernel#throw.

In practice, this is most likely to happen if you combine the use of autoload and
Timeout.timeout.

An extra check is required to avoid a segfault in this case.

* load.c (rb_load_internal0): extra check before returning TAG_RAISE when a
  non-local transfer of control happens while loading and parsing a Ruby source file.
  [ruby-core:70169] [Bug #11404]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@51439 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nobu 2015-07-30 01:42:35 +00:00
Родитель d0240cbb6d
Коммит bcba951320
3 изменённых файлов: 37 добавлений и 1 удалений

Просмотреть файл

@ -1,3 +1,10 @@
Thu Jul 30 10:42:27 2015 Alex Dowad <alexinbeijing@gmail.com>
* load.c (rb_load_internal0): extra check before returning
TAG_RAISE when a non-local transfer of control happens while
loading and parsing a Ruby source file.
[ruby-core:70169] [Bug #11404]
Thu Jul 30 08:48:42 2015 Eric Wong <e@80x24.org>
* st.c (find_entry): constify st_table*

2
load.c
Просмотреть файл

@ -621,7 +621,7 @@ rb_load_internal0(rb_thread_t *th, VALUE fname, int wrap)
th->top_self = self;
th->top_wrapper = wrapper;
if (!loaded && !FIXNUM_P(th->errinfo)) {
if (!loaded && !FIXNUM_P(th->errinfo) && state != TAG_THROW) {
/* an error on loading don't include INT2FIX(TAG_FATAL) see r35625 */
return TAG_RAISE;
}

Просмотреть файл

@ -706,4 +706,33 @@ class TestRequire < Test::Unit::TestCase
END
}
end unless /mswin|mingw/ =~ RUBY_PLATFORM
def test_throw_while_loading
Tempfile.create(%w'bug-11404 .rb') do |f|
f.puts 'sleep'
f.close
assert_separately(["-", f.path], <<-'end;')
path = ARGV[0]
class Error < RuntimeError
def exception(*)
begin
throw :blah
rescue UncaughtThrowError
end
self
end
end
assert_throw(:blah) do
x = Thread.current
y = Thread.start {
sleep 0.00001
x.raise Error.new
}
load path
end
end;
end
end
end