mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
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]
This commit is contained in:
parent
f7db1affd1
commit
4a7d6c2852
Notes:
git
2022-11-08 05:37:26 +00:00
2 changed files with 38 additions and 1 deletions
25
compile.c
25
compile.c
|
@ -7449,7 +7449,30 @@ compile_iter(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in
|
|||
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);
|
||||
|
|
|
@ -964,4 +964,18 @@ class TestCoverage < Test::Unit::TestCase
|
|||
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
|
||||
|
|
Loading…
Reference in a new issue