[前][次][番号順一覧][スレッド一覧]

ruby-changes:74393

From: Yusuke <ko1@a...>
Date: Tue, 8 Nov 2022 14:42:56 +0900 (JST)
Subject: [ruby-changes:74393] 4a7d6c2852 (master): Fix false LocalJumpError when branch coverage is enabled

https://git.ruby-lang.org/ruby.git/commit/?id=4a7d6c2852

From 4a7d6c2852aa734506be83c932168e8f974687b5 Mon Sep 17 00:00:00 2001
From: Yusuke Endoh <mame@r...>
Date: Tue, 8 Nov 2022 11:52:22 +0900
Subject: Fix false LocalJumpError when branch coverage is enabled

`throw TAG_BREAK` instruction makes a jump only if the continuation of
catch of TAG_BREAK exactly matches the instruction immediately following
the "send" instruction that is currently being executed. Otherwise, it
seems to determine break from proc-closure.

Branch coverage may insert some recording instructions after "send"
instruction, which broke the conditions for TAG_BREAK to work properly.

This change forces to set the continuation of catch of TAG_BREAK
immediately after "send" (or "invokesuper") instruction.

[Bug #18991]
---
 compile.c                      | 25 ++++++++++++++++++++++++-
 test/coverage/test_coverage.rb | 14 ++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/compile.c b/compile.c
index 359d55c0f2..d8d2738eb8 100644
--- a/compile.c
+++ b/compile.c
@@ -7449,7 +7449,30 @@ compile_iter(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in https://github.com/ruby/ruby/blob/trunk/compile.c#L7449
                            ISEQ_TYPE_BLOCK, line);
         CHECK(COMPILE(ret, "iter caller", node->nd_iter));
     }
-    ADD_LABEL(ret, retry_end_l);
+
+    {
+        // We need to put the label "retry_end_l" immediately after the last "send" instruction.
+        // This because vm_throw checks if the break cont is equal to the index of next insn of the "send".
+        // (Otherwise, it is considered "break from proc-closure". See "TAG_BREAK" handling in "vm_throw_start".)
+        //
+        // Normally, "send" instruction is at the last.
+        // However, qcall under branch coverage measurement adds some instructions after the "send".
+        //
+        // Note that "invokesuper" appears instead of "send".
+        INSN *iobj;
+        LINK_ELEMENT *last_elem = LAST_ELEMENT(ret);
+        iobj = IS_INSN(last_elem) ? (INSN*) last_elem : (INSN*) get_prev_insn((INSN*) last_elem);
+        while (INSN_OF(iobj) != BIN(send) && INSN_OF(iobj) != BIN(invokesuper)) {
+            iobj = (INSN*) get_prev_insn(iobj);
+        }
+        ELEM_INSERT_NEXT(&iobj->link, (LINK_ELEMENT*) retry_end_l);
+
+        // LINK_ANCHOR has a pointer to the last element, but ELEM_INSERT_NEXT does not update it
+        // even if we add an insn to the last of LINK_ANCHOR. So this updates it manually.
+        if (&iobj->link == LAST_ELEMENT(ret)) {
+            ret->last = (LINK_ELEMENT*) retry_end_l;
+        }
+    }
 
     if (popped) {
         ADD_INSN(ret, line_node, pop);
diff --git a/test/coverage/test_coverage.rb b/test/coverage/test_coverage.rb
index 1a21235d0a..a2a7718a30 100644
--- a/test/coverage/test_coverage.rb
+++ b/test/coverage/test_coverage.rb
@@ -964,4 +964,18 @@ class TestCoverage < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/coverage/test_coverage.rb#L964
       p :NG
     end;
   end
+
+  def test_tag_break_with_branch_coverage
+    result = {
+      :branches => {
+        [:"&.", 0, 1, 0, 1, 6] => {
+          [:then, 1, 1, 0, 1, 6] => 1,
+          [:else, 2, 1, 0, 1, 6] => 0,
+        },
+      },
+    }
+    assert_coverage(<<~"end;", { branches: true }, result)
+      1&.tap do break end
+    end;
+  end
 end
-- 
cgit v1.2.3


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]