diff --git a/ext/-test-/ensure_and_callcc/ensure_and_callcc.c b/ext/-test-/ensure_and_callcc/ensure_and_callcc.c new file mode 100644 index 0000000000..c098a725eb --- /dev/null +++ b/ext/-test-/ensure_and_callcc/ensure_and_callcc.c @@ -0,0 +1,39 @@ +#include "ruby.h" + +struct require_data { + VALUE obj; + VALUE fname; +}; + +static VALUE +call_require(VALUE arg) +{ + struct require_data *data = (struct require_data *)arg; + rb_f_require(data->obj, data->fname); + return Qnil; +} + +static VALUE +call_ensure(VALUE _) +{ + VALUE v = rb_gv_get("$ensure_called"); + int called = FIX2INT(v) + 1; + rb_gv_set("$ensure_called", INT2FIX(called)); + return Qnil; +} + +static VALUE +require_with_ensure(VALUE self, VALUE fname) +{ + struct require_data data = { + .obj = self, + .fname = fname + }; + return rb_ensure(call_require, (VALUE)&data, call_ensure, Qnil); +} + +void +Init_ensure_and_callcc(void) +{ + rb_define_method(rb_mKernel, "require_with_ensure", require_with_ensure, 1); +} diff --git a/ext/-test-/ensure_and_callcc/extconf.rb b/ext/-test-/ensure_and_callcc/extconf.rb new file mode 100644 index 0000000000..123b80b8d0 --- /dev/null +++ b/ext/-test-/ensure_and_callcc/extconf.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: false +require "mkmf" + +require_relative "../auto_ext.rb" +auto_ext(inc: true) diff --git a/test/-ext-/required.rb b/test/-ext-/required.rb new file mode 100644 index 0000000000..70514355ff --- /dev/null +++ b/test/-ext-/required.rb @@ -0,0 +1,10 @@ +require 'continuation' +cont = nil +a = [*1..10].reject do |i| + callcc {|c| cont = c} if !cont and i == 10 + false +end +if a.size < 1000 + a.unshift(:x) + cont.call +end diff --git a/test/-ext-/test_ensure_and_callcc.rb b/test/-ext-/test_ensure_and_callcc.rb new file mode 100644 index 0000000000..90f8abbb3c --- /dev/null +++ b/test/-ext-/test_ensure_and_callcc.rb @@ -0,0 +1,36 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' + +class TestEnsureAndCallcc < Test::Unit::TestCase + require '-test-/ensure_and_callcc' + + def test_bug20655_dir_chdir_using_rb_ensure + need_continuation + called = 0 + tmp = nil + Dir.chdir('/tmp') do + tmp = Dir.pwd + cont = nil + callcc{|c| cont = c} + assert_equal(tmp, Dir.pwd, "BUG #20655: ensure called and pwd was changed unexpectedly") + called += 1 + cont.call if called < 10 + end + end + + def test_bug20655_extension_using_rb_ensure + need_continuation + $ensure_called = 0 + require_with_ensure(File.join(__dir__, 'required')) + assert_equal(1, $ensure_called, "BUG #20655: ensure called unexpectedly in the required script even without exceptions") + end + + private + def need_continuation + unless respond_to?(:callcc, true) + EnvUtil.suppress_warning {require 'continuation'} + end + omit 'requires callcc support' unless respond_to?(:callcc, true) + end +end