Fix try/catch catches more than it should #1859
Close #1885, #2140, #2011, #2220, #2485, #2073 Rename the FORK_OPT opcode to TRY_BEGIN, add a TRY_END opcode, and wrap errors when raising through a TRY_END so that they will not be caught by the matching TRY_BEGIN. Now a `try exp catch handler` expression generates code like: TRY_BEGIN handler <exp> TRY_END JUMP past_handler handler: <handler> past_handler: ... On backtrack through TRY_BEGIN it just backtracks. If anything past the whole thing raises when <exp> produced a value, then the TRY_END will catch the error, wrap it in another, and backtrack. The TRY_BEGIN will see a wrapped error and then it will unwrap and re-raise the error. If <exp> raises, then TRY_BEGIN will catch the error and jump to the handler, but the TRY_BEGIN will not stack_save() in that case, so on raise/backtrack the TRY_BEGIN will not execute again (nor will the TRY_END).
This commit is contained in:
1 parent
98f709d0c1
commit
1cf6515c06
7 files changed
+468
-368
No files matched your search
+54
-2
@@ -348,6 +348,7 @@ jv jq_next(jq_state *jq) {
|
||||
|
||||
int raising;
|
||||
int backtracking = !jq->initial_execution;
|
||||
|
||||
jq->initial_execution = 0;
|
||||
assert(jv_get_kind(jq->error) == JV_KIND_NULL);
|
||||
while (1) {
|
||||
@@ -808,7 +809,59 @@ jv jq_next(jq_state *jq) {
|
||||
break;
|
||||
}
|
||||
|
||||
case FORK_OPT:
|
||||
case TRY_BEGIN:
|
||||
stack_save(jq, pc - 1, stack_get_pos(jq));
|
||||
pc++; // skip handler offset this time
|
||||
break;
|
||||
|
||||
case TRY_END:
|
||||
stack_save(jq, pc - 1, stack_get_pos(jq));
|
||||
break;
|
||||
|
||||
case ON_BACKTRACK(TRY_BEGIN): {
|
||||
if (!raising) {
|
||||
/*
|
||||
* `try EXP ...` -- EXP backtracked (e.g., EXP was `empty`), so we
|
||||
* backtrack more:
|
||||
*/
|
||||
jv_free(stack_pop(jq));
|
||||
goto do_backtrack;
|
||||
}
|
||||
|
||||
/*
|
||||
* Else `(try EXP ... ) | EXP2` raised an error.
|
||||
*
|
||||
* If the error was wrapped in another error, then that means EXP2 raised
|
||||
* the error. We unwrap it and re-raise it as it wasn't raised by EXP.
|
||||
*
|
||||
* See commentary in gen_try().
|
||||
*/
|
||||
jv e = jv_invalid_get_msg(jv_copy(jq->error));
|
||||
if (!jv_is_valid(e) && jv_invalid_has_msg(jv_copy(e))) {
|
||||
set_error(jq, e);
|
||||
goto do_backtrack;
|
||||
}
|
||||
jv_free(e);
|
||||
|
||||
/*
|
||||
* Else we caught an error containing a non-error value, so we jump to
|
||||
* the handler.
|
||||
*
|
||||
* See commentary in gen_try().
|
||||
*/
|
||||
uint16_t offset = *pc++;
|
||||
jv_free(stack_pop(jq)); // free the input
|
||||
stack_push(jq, jv_invalid_get_msg(jq->error)); // push the error's message
|
||||
jq->error = jv_null();
|
||||
pc += offset;
|
||||
break;
|
||||
}
|
||||
case ON_BACKTRACK(TRY_END):
|
||||
// Wrap the error so the matching TRY_BEGIN doesn't catch it
|
||||
if (raising)
|
||||
set_error(jq, jv_invalid_with_msg(jv_copy(jq->error)));
|
||||
goto do_backtrack;
|
||||
|
||||
case DESTRUCTURE_ALT:
|
||||
case FORK: {
|
||||
stack_save(jq, pc - 1, stack_get_pos(jq));
|
||||
@@ -816,7 +869,6 @@ jv jq_next(jq_state *jq) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ON_BACKTRACK(FORK_OPT):
|
||||
case ON_BACKTRACK(DESTRUCTURE_ALT): {
|
||||
if (jv_is_valid(jq->error)) {
|
||||
// `try EXP ...` backtracked here (no value, `empty`), so we backtrack more
|
||||
|
||||
Reference in new issue
Block a user