Compare commits

..
2 Commits
8 changed files with 308 additions and 69 deletions
+7 -3
View File
@@ -979,12 +979,16 @@ int main (int argc, char* argv[])
{ {
hak_oow_t tab_size; hak_oow_t tab_size;
tab_size = 5000; tab_size = HAK_DFL_SYMTAB_SIZE;
hak_setoption (hak, HAK_SYMTAB_SIZE, &tab_size); hak_setoption (hak, HAK_SYMTAB_SIZE, &tab_size);
tab_size = 5000; tab_size = HAK_DFL_SYSDIC_SIZE;
hak_setoption (hak, HAK_SYSDIC_SIZE, &tab_size); hak_setoption (hak, HAK_SYSDIC_SIZE, &tab_size);
tab_size = 600; /* TODO: choose a better stack size or make this user specifiable */ tab_size = HAK_DFL_PROCSTK_SIZE; /* TODO: choose a better stack size or make this user specifiable */
hak_setoption (hak, HAK_PROCSTK_SIZE, &tab_size); hak_setoption (hak, HAK_PROCSTK_SIZE, &tab_size);
tab_size = HAK_DFL_EXSTK_SIZE; /* TODO: choose a better stack size or make this user specifiable */
hak_setoption (hak, HAK_EXSTK_SIZE, &tab_size);
tab_size = HAK_DFL_CLSTK_SIZE; /* TODO: choose a better stack size or make this user specifiable */
hak_setoption (hak, HAK_CLSTK_SIZE, &tab_size);
} }
{ {
+67 -58
View File
@@ -1134,6 +1134,7 @@ static int push_ctlblk (hak_t* hak, const hak_loc_t* errloc, hak_ctlblk_type_t t
HAK_MEMSET(&hak->c->ctlblk.info[new_depth], 0, HAK_SIZEOF(hak->c->ctlblk.info[new_depth])); HAK_MEMSET(&hak->c->ctlblk.info[new_depth], 0, HAK_SIZEOF(hak->c->ctlblk.info[new_depth]));
hak->c->ctlblk.info[new_depth]._type = type; hak->c->ctlblk.info[new_depth]._type = type;
/*hak->c->ctlblk.info[new_depth].in_catch = 0; not needed for memset above */
hak->c->ctlblk.depth = new_depth; hak->c->ctlblk.depth = new_depth;
return 0; return 0;
} }
@@ -2090,12 +2091,60 @@ static HAK_INLINE int emit_plus (hak_t* hak)
#endif #endif
/* ========================================================================= */ /* ========================================================================= */
static int emit_ctlblk_unwind (hak_t* hak, hak_cnode_t* src, int stop_at_loop)
{
hak_ooi_t i;
for (i = hak->c->ctlblk.depth; i > hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base; --i)
{
switch (hak->c->ctlblk.info[i]._type)
{
case HAK_CTLBLK_TYPE_LOOP:
/* a loop block needs no unwinding instruction */
if (stop_at_loop) return 1; /* for break/continue inside loop */
break;
case HAK_CTLBLK_TYPE_TRY:
/* emit an instruction to exit from the try loop. when it is in the catch side. don't emit anything */
if (!hak->c->ctlblk.info[i].in_catch &&
emit_byte_instruction(hak, HAK_CODE_TRY_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
break;
case HAK_CTLBLK_TYPE_CLASS:
/* emit an instruction to exit from the class definition scope being defined */
if (emit_byte_instruction(hak, HAK_CODE_CLASS_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
break;
}
}
return 0;
}
static void update_try_ctlblk_for_catch (hak_t* hak)
{
hak_ooi_t i;
/* find the innermost try block info and mark that it's now in the catch block side */
for (i = hak->c->ctlblk.depth; i > hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base; --i)
{
if (hak->c->ctlblk.info[i]._type == HAK_CTLBLK_TYPE_TRY)
{
hak->c->ctlblk.info[i].in_catch = 1;
return;
}
}
/* this must not happend. compile_catch() must not be invoked without
* going through the enclosing TRY block first. */
HAK_ASSERT(hak, !"internal error - this must never happen");
}
static int compile_break (hak_t* hak, hak_cnode_t* src) static int compile_break (hak_t* hak, hak_cnode_t* src)
{ {
/* (break) */ /* (break) */
hak_cnode_t* cmd, * obj; hak_cnode_t* cmd, * obj;
hak_ooi_t i; hak_ooi_t i;
int n;
HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src)); HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_BREAK)); HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_BREAK));
@@ -2123,30 +2172,15 @@ static int compile_break (hak_t* hak, hak_cnode_t* src)
return -1; return -1;
} }
for (i = hak->c->ctlblk.depth; i > hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base; --i) n = emit_ctlblk_unwind(hak, cmd, 1);
if (n <= -1) return -1;
if (n == 0)
{ {
switch (hak->c->ctlblk.info[i]._type) hak_setsynerrbfmt(hak, HAK_SYNERR_BREAK, HAK_CNODE_GET_LOC(cmd),
{
case HAK_CTLBLK_TYPE_LOOP:
goto inside_loop;
case HAK_CTLBLK_TYPE_TRY:
/* emit an instruction to exit from the try loop. */
if (emit_byte_instruction(hak, HAK_CODE_TRY_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
break;
case HAK_CTLBLK_TYPE_CLASS:
/* emit an instruction to exit from the class definition scope being defined */
if (emit_byte_instruction(hak, HAK_CODE_CLASS_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
break;
}
}
hak_setsynerrbfmt(hak, HAK_SYNERR_BREAK, HAK_CNODE_GET_LOC(src),
"%.*js outside loop", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd)); "%.*js outside loop", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
return -1; return -1;
}
inside_loop:
for (i = hak->c->cfs.top; i >= 0; --i) for (i = hak->c->cfs.top; i >= 0; --i)
{ {
const hak_cframe_t* tcf; const hak_cframe_t* tcf;
@@ -2217,6 +2251,7 @@ static int compile_continue (hak_t* hak, hak_cnode_t* src)
/* (continue) */ /* (continue) */
hak_cnode_t* cmd, * obj; hak_cnode_t* cmd, * obj;
hak_ooi_t i; hak_ooi_t i;
int n;
HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src)); HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_CONTINUE)); HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_CONTINUE));
@@ -2244,29 +2279,15 @@ static int compile_continue (hak_t* hak, hak_cnode_t* src)
return -1; return -1;
} }
for (i = hak->c->ctlblk.depth; i > hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base; --i) n = emit_ctlblk_unwind(hak, cmd, 1);
if (n <= -1) return -1;
if (n == 0)
{ {
switch (hak->c->ctlblk.info[i]._type) hak_setsynerrbfmt(hak, HAK_SYNERR_BREAK, HAK_CNODE_GET_LOC(cmd),
{
case HAK_CTLBLK_TYPE_LOOP:
goto inside_loop;
case HAK_CTLBLK_TYPE_TRY:
/*must emit an instruction to exit from the try loop.*/
if (emit_byte_instruction(hak, HAK_CODE_TRY_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
break;
case HAK_CTLBLK_TYPE_CLASS:
if (emit_byte_instruction(hak, HAK_CODE_CLASS_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
break;
}
}
hak_setsynerrbfmt(hak, HAK_SYNERR_BREAK, HAK_CNODE_GET_LOC(src),
"%.*js outside loop", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd)); "%.*js outside loop", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
return -1; return -1;
}
inside_loop:
for (i = hak->c->cfs.top; i >= 0; --i) for (i = hak->c->cfs.top; i >= 0; --i)
{ {
const hak_cframe_t* tcf; const hak_cframe_t* tcf;
@@ -3968,7 +3989,6 @@ static int compile_return (hak_t* hak, hak_cnode_t* src, int ret_from_home)
hak_cnode_t* obj, * val; hak_cnode_t* obj, * val;
hak_cframe_t* cf; hak_cframe_t* cf;
hak_funblk_info_t* fbi; hak_funblk_info_t* fbi;
hak_ooi_t i;
HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src)); HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_RETURN) || HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_RETURN) ||
@@ -3977,24 +3997,6 @@ static int compile_return (hak_t* hak, hak_cnode_t* src, int ret_from_home)
fbi = &hak->c->funblk.info[hak->c->funblk.depth]; fbi = &hak->c->funblk.info[hak->c->funblk.depth];
obj = HAK_CNODE_CONS_CDR(src); obj = HAK_CNODE_CONS_CDR(src);
for (i = hak->c->ctlblk.depth; i > hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base; --i)
{
switch (hak->c->ctlblk.info[i]._type)
{
case HAK_CTLBLK_TYPE_LOOP:
/* do nothing */
break;
case HAK_CTLBLK_TYPE_TRY:
if (emit_byte_instruction(hak, HAK_CODE_TRY_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
break;
case HAK_CTLBLK_TYPE_CLASS:
if (emit_byte_instruction(hak, HAK_CODE_CLASS_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
break;
}
}
if (fbi->tmpr_nrvars > 0) if (fbi->tmpr_nrvars > 0)
{ {
hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src); hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src);
@@ -4016,6 +4018,9 @@ static int compile_return (hak_t* hak, hak_cnode_t* src, int ret_from_home)
return -1; return -1;
} }
/* emit unwinding expression just before emitting the actual return instruction */
if (emit_ctlblk_unwind(hak, src, 0) <= -1) return -1;
/* TODO: pop stack if this is not the first statement... */ /* TODO: pop stack if this is not the first statement... */
if (emit_byte_instruction(hak, HAK_CODE_PUSH_RETURN_R, HAK_CNODE_GET_LOC(tmp)) <= -1) return -1; if (emit_byte_instruction(hak, HAK_CODE_PUSH_RETURN_R, HAK_CNODE_GET_LOC(tmp)) <= -1) return -1;
POP_CFRAME(hak); POP_CFRAME(hak);
@@ -4502,6 +4507,7 @@ static HAK_INLINE int compile_catch (hak_t* hak)
/* produce an instruction to store the exception value to an exception variable pushed by the 'throw' instruction */ /* produce an instruction to store the exception value to an exception variable pushed by the 'throw' instruction */
if (emit_variable_access(hak, VAR_ACCESS_POP, &vi, HAK_CNODE_GET_LOC(src)) <= -1) return -1; if (emit_variable_access(hak, VAR_ACCESS_POP, &vi, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
update_try_ctlblk_for_catch(hak);
SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_LIST, obj); SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_LIST, obj);
PUSH_SUBCFRAME(hak, COP_POST_CATCH, cmd); PUSH_SUBCFRAME(hak, COP_POST_CATCH, cmd);
@@ -7001,6 +7007,9 @@ static HAK_INLINE int emit_return (hak_t* hak)
HAK_ASSERT(hak, cf->opcode == COP_EMIT_RETURN); HAK_ASSERT(hak, cf->opcode == COP_EMIT_RETURN);
HAK_ASSERT(hak, cf->operand != HAK_NULL); HAK_ASSERT(hak, cf->operand != HAK_NULL);
/* emit unwinding expression just before emitting the actual return instruction */
if (emit_ctlblk_unwind(hak, cf->operand, 0) <= -1) return -1;
n = emit_byte_instruction(hak, (cf->u._return.from_home? HAK_CODE_RETURN_STACKTOP: HAK_CODE_RETURN_FROM_BLOCK), HAK_CNODE_GET_LOC(cf->operand)); n = emit_byte_instruction(hak, (cf->u._return.from_home? HAK_CODE_RETURN_STACKTOP: HAK_CODE_RETURN_FROM_BLOCK), HAK_CNODE_GET_LOC(cf->operand));
POP_CFRAME(hak); POP_CFRAME(hak);
+13 -1
View File
@@ -180,6 +180,8 @@ static void terminate_all_processes (hak_t* hak);
ap->exsp = HAK_SMOOI_TO_OOP(exsp); \ ap->exsp = HAK_SMOOI_TO_OOP(exsp); \
} while (0) } while (0)
/* normal stack top is the base of exstack */
#define HAK_EXSTACK_GET_BASE(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->st)
#define HAK_EXSTACK_GET_ST(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->exst) #define HAK_EXSTACK_GET_ST(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->exst)
#define HAK_EXSTACK_GET_SP(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->exsp) #define HAK_EXSTACK_GET_SP(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->exsp)
@@ -235,6 +237,8 @@ static void terminate_all_processes (hak_t* hak);
#define HAK_CLSTACK_CHOP(hak, clsp_) ((hak)->processor->active->clsp = HAK_SMOOI_TO_OOP(clsp_)) #define HAK_CLSTACK_CHOP(hak, clsp_) ((hak)->processor->active->clsp = HAK_SMOOI_TO_OOP(clsp_))
/* exstack top is the base of clstack */
#define HAK_CLSTACK_GET_BASE(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->exst)
#define HAK_CLSTACK_GET_ST(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->clst) #define HAK_CLSTACK_GET_ST(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->clst)
#define HAK_CLSTACK_GET_SP(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->clsp) #define HAK_CLSTACK_GET_SP(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->clsp)
@@ -4364,7 +4368,15 @@ static int execute (hak_t* hak)
case HAK_CODE_TRY_EXIT: case HAK_CODE_TRY_EXIT:
LOG_INST_0(hak, "try_exit"); LOG_INST_0(hak, "try_exit");
/* TODO: stack underflow check? */ /* writing the condition this way would be more explicit.
* if (HAK_EXSTACK_GET_SP(hak) - HAK_EXSTACK_GET_BASE(hak) < 4)
* it's simpler to use HAK_EXSTACK_IS_EMPTY() base PUSH, POP, POP_TO all move
* exsp by exactly 4. */
if (HAK_EXSTACK_IS_EMPTY(hak))
{
hak_seterrbfmt(hak, HAK_ESTKUNDFLW, "exception stack underflow");
goto oops_with_errmsg_supplement;
}
HAK_EXSTACK_POP(hak); HAK_EXSTACK_POP(hak);
break; break;
+1
View File
@@ -855,6 +855,7 @@ typedef enum hak_ctlblk_type_t hak_ctlblk_type_t;
struct hak_ctlblk_info_t struct hak_ctlblk_info_t
{ {
hak_ctlblk_type_t _type; hak_ctlblk_type_t _type;
int in_catch; /* used for HAK_CTLBLK_TYPE_TRY only */
}; };
typedef struct hak_ctlblk_info_t hak_ctlblk_info_t; typedef struct hak_ctlblk_info_t hak_ctlblk_info_t;
+13 -4
View File
@@ -279,11 +279,20 @@ typedef enum hak_option_t hak_option_t;
enum hak_option_dflval_t enum hak_option_dflval_t
{ {
HAK_DFL_LOG_MAXCAPA = HAK_LOG_CAPA_ALIGN * 16, HAK_DFL_LOG_MAXCAPA = HAK_LOG_CAPA_ALIGN * 16,
HAK_DFL_SYMTAB_SIZE = 5000,
HAK_DFL_SYSDIC_SIZE = 5000, #if defined(HAK_SMALL_MEMORY_FOOTPRINT)
HAK_DFL_PROCSTK_SIZE = 5000, HAK_DFL_SYMTAB_SIZE = 1024,
HAK_DFL_EXSTK_SIZE = 128, HAK_DFL_SYSDIC_SIZE = 1024
HAK_DFL_PROCSTK_SIZE = 1024, /* -> fstk 1024, the FSTK floor */
HAK_DFL_EXSTK_SIZE = 256, /* 4 slots/level x 59 levels = 236 */
HAK_DFL_CLSTK_SIZE = 16, /* = HAK_MIN_CLSTK_SIZE */
#else
HAK_DFL_SYMTAB_SIZE = 8192,
HAK_DFL_SYSDIC_SIZE = 8192,
HAK_DFL_PROCSTK_SIZE = 8192,
HAK_DFL_EXSTK_SIZE = 2048,
HAK_DFL_CLSTK_SIZE = 64 HAK_DFL_CLSTK_SIZE = 64
#endif
}; };
typedef enum hak_option_dflval_t hak_option_dflval_t; typedef enum hak_option_dflval_t hak_option_dflval_t;
+1
View File
@@ -32,6 +32,7 @@ check_SCRIPTS = \
sysproc-01.hak \ sysproc-01.hak \
sysproc-02.hak \ sysproc-02.hak \
tick-01.hak \ tick-01.hak \
try-01.hak \
va-01.hak \ va-01.hak \
var-01.hak \ var-01.hak \
var-02.hak \ var-02.hak \
+1
View File
@@ -577,6 +577,7 @@ check_SCRIPTS = \
sysproc-01.hak \ sysproc-01.hak \
sysproc-02.hak \ sysproc-02.hak \
tick-01.hak \ tick-01.hak \
try-01.hak \
va-01.hak \ va-01.hak \
var-01.hak \ var-01.hak \
var-02.hak \ var-02.hak \
+202
View File
@@ -0,0 +1,202 @@
## Two related defects in how 'return' interacts with try/catch. Both are
## fixed; this checks both, because each hid the other while writing the test.
##
## 1. compile_return() emitted TRY_EXIT/CLASS_EXIT before the return value was
## compiled, so the value expression ran with every enclosing handler in the
## function already popped - not just the innermost one. The unwinding now
## comes from emit_return(), after the value is on the operand stack.
## Only the syntactic form used to matter: try { x := (f) } caught what f
## threw while try { return (f) } did not, so both forms appear below.
##
## 2. A 'return', 'break' or 'continue' inside a catch body emitted a TRY_EXIT
## for the very try whose handler it sat in - but 'throw' has already
## unwound that frame, so the second pop drove the exception stack below its
## base. HAK_EXSTACK_IS_EMPTY tests 'exsp <= st', so an underflowed stack
## reads as empty from then on and EVERY later try in the process reported
## 'exception not handled'. The catch body is now compiled with its try
## block marked in_catch, which suppresses the instruction.
##
## That corruption was never visible in the function that caused it, so the
## assertions that actually guard it are the ones exercising a try AFTER a
## catch that returned, broke or continued.
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
fun boom() { throw 99 }
## ------------------------------------------------------------------
## 1. a throw inside a return value
## ------------------------------------------------------------------
fun c1() {
try { return (boom) } catch (e) { return (+ e 1) }
}
chk (= (c1) 100) "a throw in a return value is caught by the enclosing try"
## the catch need not return for the handler to run
seen := 0
fun c2() {
try { return (boom) } catch (e) { seen := e }
return 7
}
chk (= (c2) 7) "execution resumes after the try when the catch falls through"
chk (= seen 99) "the catch body ran"
fun c3() {
try { return (boom) } catch (e) { }
return 8
}
chk (= (c3) 8) "an empty catch body still handles the throw"
## nesting: the innermost handler wins and the outer one stays out of it.
## the old code unwound every enclosing try, so neither fired.
inner := 0
outer := 0
fun c4() {
try {
try { return (boom) } catch (e) { inner := (+ inner 1) }
} catch (e2) { outer := (+ outer 1) }
return 9
}
chk (= (c4) 9) "a nested try returns normally after handling"
chk (= inner 1) "the innermost try catches"
chk (= outer 0) "the outer try is left out of it"
## across a call boundary the callee's own handler wins; the old code let the
## throw escape to the caller's handler instead
g_seen := 0
h_seen := 0
fun g() { try { return (boom) } catch (e) { g_seen := 1 ; return -1 } }
fun h() { try { return (g) } catch (e) { h_seen := 1 ; return -2 } }
chk (= (h) -1) "the callee handles its own throw"
chk (= g_seen 1) "the callee's catch ran"
chk (= h_seen 0) "the caller's catch did not"
## the form that always worked must keep working
fun c5() {
| v |
v := 0
try { v := (boom) } catch (e) { v := e }
return v
}
chk (= (c5) 99) "a throw in an assignment is still caught"
## ------------------------------------------------------------------
## 2. leaving a catch body by return, break or continue
##
## each case is followed by a fresh try: that is where an underflowed
## exception stack shows up, never in the function that caused it.
## ------------------------------------------------------------------
## a try that must still work after the cases above, which all returned from
## inside a catch
fun still1() {
| v |
v := 0
try { v := (boom) } catch (e) { v := 21 }
return v
}
chk (= (still1) 21) "a try still works after earlier catch bodies returned"
## break out of a loop from inside a catch
bj := 0
while (< bj 9) {
bj := (+ bj 1)
try { y := (boom) } catch (e) { break }
}
chk (= bj 1) "break inside a catch leaves the loop"
fun still2() {
| v |
v := 0
try { v := (boom) } catch (e) { v := 22 }
return v
}
chk (= (still2) 22) "a try still works after a break from inside a catch"
## continue to the next iteration from inside a catch
ci := 0
ch := 0
while (< ci 3) {
ci := (+ ci 1)
try { z := (boom) } catch (e) { ch := (+ ch 1) ; continue }
ch := 100
}
chk (= ci 3) "continue inside a catch keeps iterating"
chk (= ch 3) "continue inside a catch skips the rest of the loop body"
fun still3() {
| v |
v := 0
try { v := (boom) } catch (e) { v := 23 }
return v
}
chk (= (still3) 23) "a try still works after a continue from inside a catch"
## the in_catch mark must suppress only the block whose handler we are in. the
## return below sits in the inner catch, so the inner frame is already gone,
## but the outer try is still live and has to be unwound on the way out. a
## frame left behind there would misdirect the next throw instead.
fun nest2() {
try {
try { w := (boom) } catch (e) { return 11 }
} catch (e2) { }
return 12
}
chk (= (nest2) 11) "a return from an inner catch still unwinds the outer try"
fun still4() {
| v |
v := 0
try { v := (boom) } catch (e) { v := 24 }
return v
}
chk (= (still4) 24) "a try still works after returning from a nested catch"
## ------------------------------------------------------------------
## 3. break and continue in a try BODY still unwind
##
## these share emit_ctlblk_unwind with return, and here the frame is live and
## must be popped. a leaked frame per iteration would exhaust the exception
## stack long before these loops finish.
## ------------------------------------------------------------------
i := 0
n := 0
while (< i 2000) {
i := (+ i 1)
try { if (= (mod i 2) 0) { continue } ; n := (+ n 1) } catch (e) { n := -1 }
}
chk (= n 1000) "continue inside a try body unwinds without leaking a handler"
i := 0
while (< i 2000) {
i := (+ i 1)
try { if (= i 7) { break } } catch (e) { }
}
chk (= i 7) "break inside a try body unwinds and leaves the loop"
## ------------------------------------------------------------------
## 4. a handler stays live across a whole call chain
## ------------------------------------------------------------------
hits := 0
caught := 0
fun rec(k) {
if (<= k 0) { throw 55 }
try { return (rec (- k 1)) } catch (e) { hits := (+ hits 1) ; caught := e ; return e }
}
chk (= (rec 20) 55) "recursion with a try per frame propagates the thrown value"
chk (= caught 55) "the thrown value arrives intact"
chk (= hits 1) "only the frame nearest the throw handles it"
fun still5() {
| v |
v := 0
try { v := (boom) } catch (e) { v := 25 }
return v
}
chk (= (still5) 25) "a try still works after deep try-per-frame recursion"