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:
Yusuke Endoh 2022-11-08 11:52:22 +09:00
Родитель f7db1affd1
Коммит 4a7d6c2852
2 изменённых файлов: 38 добавлений и 1 удалений

Просмотреть файл

@ -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