fix raise in exception with jump

add_ensure_iseq() adds ensure block to the end of
jump such as next/redo/return. However, if the rescue
cause are in the body, this rescue catches the exception
in ensure clause.

  iter do
    next
  rescue
    R
  ensure
    raise
  end

In this case, R should not be executed, but executed without this patch.

Fixes [Bug #13930]
Fixes [Bug #16618]

A part of tests are written by @jeremyevans https://github.com/ruby/ruby/pull/4291
This commit is contained in:
Koichi Sasada 2021-04-22 10:44:52 +09:00
parent 5512353d97
commit 609de71f04
Notes: git 2021-04-22 11:34:00 +09:00
3 changed files with 88 additions and 7 deletions

View File

@ -5356,9 +5356,22 @@ add_ensure_range(rb_iseq_t *iseq, struct ensure_range *erange,
erange->next = ne;
}
static bool
can_add_ensure_iseq(const rb_iseq_t *iseq)
{
if (ISEQ_COMPILE_DATA(iseq)->in_rescue && ISEQ_COMPILE_DATA(iseq)->ensure_node_stack) {
return false;
}
else {
return true;
}
}
static void
add_ensure_iseq(LINK_ANCHOR *const ret, rb_iseq_t *iseq, int is_return)
{
assert(can_add_ensure_iseq(iseq));
struct iseq_compile_data_ensure_node_stack *enlp =
ISEQ_COMPILE_DATA(iseq)->ensure_node_stack;
struct iseq_compile_data_ensure_node_stack *prev_enlp = enlp;
@ -6850,7 +6863,7 @@ compile_break(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i
const int line = nd_line(node);
unsigned long throw_flag = 0;
if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0) {
if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0 && can_add_ensure_iseq(iseq)) {
/* while/until */
LABEL *splabel = NEW_LABEL(0);
ADD_LABEL(ret, splabel);
@ -6909,7 +6922,7 @@ compile_next(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in
const int line = nd_line(node);
unsigned long throw_flag = 0;
if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0) {
if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0 && can_add_ensure_iseq(iseq)) {
LABEL *splabel = NEW_LABEL(0);
debugs("next in while loop\n");
ADD_LABEL(ret, splabel);
@ -6922,7 +6935,7 @@ compile_next(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in
ADD_INSN(ret, line, putnil);
}
}
else if (ISEQ_COMPILE_DATA(iseq)->end_label) {
else if (ISEQ_COMPILE_DATA(iseq)->end_label && can_add_ensure_iseq(iseq)) {
LABEL *splabel = NEW_LABEL(0);
debugs("next in block\n");
ADD_LABEL(ret, splabel);
@ -6982,7 +6995,7 @@ compile_redo(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in
{
const int line = nd_line(node);
if (ISEQ_COMPILE_DATA(iseq)->redo_label) {
if (ISEQ_COMPILE_DATA(iseq)->redo_label && can_add_ensure_iseq(iseq)) {
LABEL *splabel = NEW_LABEL(0);
debugs("redo in while");
ADD_LABEL(ret, splabel);
@ -6994,7 +7007,7 @@ compile_redo(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in
ADD_INSN(ret, line, putnil);
}
}
else if (iseq->body->type != ISEQ_TYPE_EVAL && ISEQ_COMPILE_DATA(iseq)->start_label) {
else if (iseq->body->type != ISEQ_TYPE_EVAL && ISEQ_COMPILE_DATA(iseq)->start_label && can_add_ensure_iseq(iseq)) {
LABEL *splabel = NEW_LABEL(0);
debugs("redo in block");
@ -7080,7 +7093,14 @@ compile_rescue(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node,
lstart->rescued = LABEL_RESCUE_BEG;
lend->rescued = LABEL_RESCUE_END;
ADD_LABEL(ret, lstart);
CHECK(COMPILE(ret, "rescue head", node->nd_head));
bool prev_in_rescue = ISEQ_COMPILE_DATA(iseq)->in_rescue;
ISEQ_COMPILE_DATA(iseq)->in_rescue = true;
{
CHECK(COMPILE(ret, "rescue head", node->nd_head));
}
ISEQ_COMPILE_DATA(iseq)->in_rescue = prev_in_rescue;
ADD_LABEL(ret, lend);
if (node->nd_else) {
ADD_INSN(ret, line, pop);
@ -7241,7 +7261,7 @@ compile_return(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node,
CHECK(COMPILE(ret, "return nd_stts (return val)", retval));
if (type == ISEQ_TYPE_METHOD) {
if (type == ISEQ_TYPE_METHOD && can_add_ensure_iseq(iseq)) {
add_ensure_iseq(ret, iseq, 1);
ADD_TRACE(ret, RUBY_EVENT_RETURN);
ADD_INSN(ret, line, leave);

1
iseq.h
View File

@ -101,6 +101,7 @@ struct iseq_compile_data {
struct iseq_compile_data_storage *storage_head;
struct iseq_compile_data_storage *storage_current;
} insn;
bool in_rescue;
int loopval_popped; /* used by NODE_BREAK */
int last_line;
int label_no;

View File

@ -78,6 +78,66 @@ class TestException < Test::Unit::TestCase
assert(!bad)
end
def test_exception_in_ensure_with_next
string = "[ruby-core:82936] [Bug #13930]"
assert_raise_with_message(RuntimeError, string) do
lambda do
next
rescue
assert(false)
ensure
raise string
end.call
assert(false)
end
assert_raise_with_message(RuntimeError, string) do
flag = true
while flag
flag = false
begin
next
rescue
assert(false)
ensure
raise string
end
end
end
end
def test_exception_in_ensure_with_redo
string = "[ruby-core:82936] [Bug #13930]"
assert_raise_with_message(RuntimeError, string) do
i = 0
lambda do
i += 1
redo if i < 2
rescue
assert(false)
ensure
raise string
end.call
assert(false)
end
end
def test_exception_in_ensure_with_return
@string = "[ruby-core:97104] [Bug #16618]"
def self.meow
return
assert(false)
rescue
assert(false)
ensure
raise @string
end
assert_raise_with_message(RuntimeError, @string) do
meow
end
end
def test_errinfo_in_debug
bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do
def to_s