From 9b4eba84d5ce8bc86c147a0cfb2a6f6c6863a789 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 22 Apr 2021 10:44:52 +0900 Subject: [PATCH] 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 --- compile.c | 34 ++++++++++++++++----- iseq.h | 1 + test/ruby/test_exception.rb | 60 +++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/compile.c b/compile.c index 7e1724c8145926..5aac09c0e81035 100644 --- a/compile.c +++ b/compile.c @@ -5203,9 +5203,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; @@ -6697,7 +6710,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); @@ -6756,7 +6769,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); @@ -6769,7 +6782,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); @@ -6829,7 +6842,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); @@ -6841,7 +6854,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"); @@ -6927,7 +6940,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); @@ -7088,7 +7108,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); diff --git a/iseq.h b/iseq.h index 6a6c69a1146064..904b8914273b80 100644 --- a/iseq.h +++ b/iseq.h @@ -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; diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 087bcda5acbfca..0a2a31281d8745 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -78,6 +78,66 @@ def test_exception_ensure_2 # just duplication? 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