From b80abc33ae4e01e1f0b56f661604154c012ca6e6 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Thu, 9 Jul 2020 23:55:24 +0200 Subject: [PATCH] Fix several issues related to exceptions, including #7 and #8 Exceptions are now propagated through saved vm environments (#8). Pop-handler and pop are generated in the right sequence, following NewtonOS compiler. Fix si_set_lex_scope for native functions. Add unit tests for various cases. Add BinEqual function for tests. --- sample/exception.newt | 14 ++++- src/newt_core/NewtBC.c | 90 ++++++++++++++++----------- src/newt_core/NewtFns.c | 60 ++++++++++++++++-- src/newt_core/NewtGC.c | 4 +- src/newt_core/NewtVM.c | 102 +++++++++++++++++++++++-------- src/newt_core/incs/NewtBC.h | 2 +- src/newt_core/incs/NewtFns.h | 1 + src/newt_core/incs/NewtType.h | 3 +- src/newt_core/incs/NewtVM.h | 7 ++- tests/test_common.newt | 105 +++++++++++++++++++++++++++++++ tests/test_exceptions.newt | 112 ++++++++++++++++++++++++++++++++++ 11 files changed, 426 insertions(+), 74 deletions(-) create mode 100644 tests/test_common.newt create mode 100755 tests/test_exceptions.newt diff --git a/sample/exception.newt b/sample/exception.newt index 53f37c2..4b6c4e7 100755 --- a/sample/exception.newt +++ b/sample/exception.newt @@ -18,12 +18,24 @@ begin Throw('|evt.ex.foo;type.ref.frame|, {}); onexception |evt.ex.foo| do Print("Caught custom with data\n"); - + // Catch and print data. try Throw('|evt.ex.foo;type.ref.string|, "Some data"); onexception |evt.ex.foo| do Print("Caught: " & CurrentException().data & "\n"); + + // Catch custom with subexception. + try + Throw('|evt.ex.foo.sub|, nil); + onexception |evt.ex.foo| do + Print("Caught custom sub\n"); + + // Catch custom with subexception and Apply + try + Apply(GetGlobalFn('Throw), ['|evt.ex.foo.sub|, nil]); + onexception |evt.ex.foo| do + Print("Caught custom sub with apply\n"); // Don't catch. try diff --git a/src/newt_core/NewtBC.c b/src/newt_core/NewtBC.c index d650b29..60b6de9 100755 --- a/src/newt_core/NewtBC.c +++ b/src/newt_core/NewtBC.c @@ -121,12 +121,12 @@ static int16_t NBCMakeFnArgs(newtRefArg fn, nps_syntax_node_t * stree, nps_nod static nbc_env_t * NBCMakeFnEnv(nps_syntax_node_t * stree, nps_node_t args); static uint32_t NBCGenBranch(uint8_t a); static void NBCDefLocal(newtRefArg type, newtRefArg r, bool init); -static void NBCBackPatch(uint32_t cx, int16_t b); +static void NBCBackPatch(uint32_t cx, uintptr_t b); static void NBCPushBreakStack(uint32_t cx); static void NBCBreakBackPatchs(uint32_t loop_head, uint32_t cx); static void NBCPushOnexcpStack(uint32_t cx); -static void NBCOnexcpBackPatchs(uint32_t try_head, uint32_t cx); -static void NBCGenOnexcpPC(int32_t pc); +static void NBCOnexcpBackPatchs(uint32_t try_head); +static void NBCGenOnexcpPC(void); static void NBCGenOnexcpBranch(void); static void NBCOnexcpBackPatchL(uint32_t sp, int32_t pc); @@ -142,8 +142,8 @@ static void NBCGenGlobalVar(nps_syntax_node_t * stree, nps_node_t r); static void NBCGenLocalVar(nps_syntax_node_t * stree, nps_node_t type, nps_node_t r); static bool NBCTypeValid(nps_node_t type); static int16_t NBCGenTryPre(nps_syntax_node_t * stree, nps_node_t r); -static int16_t NBCGenTryPost(nps_syntax_node_t * stree, nps_node_t r, uint32_t * onexcpspP); -static void NBCGenTry(nps_syntax_node_t * stree, nps_node_t expr, nps_node_t onexception_list); +static int16_t NBCGenTryPost(nps_syntax_node_t * stree, nps_node_t r, uint32_t * onexcpspP, bool ret); +static void NBCGenTry(nps_syntax_node_t * stree, nps_node_t expr, nps_node_t onexception_list, bool ret); static void NBCGenIfThenElse(nps_syntax_node_t * stree, nps_node_t cond, nps_node_t thenelse, bool ret); static void NBCGenAnd(nps_syntax_node_t * stree, nps_node_t op1, nps_node_t op2); static void NBCGenOr(nps_syntax_node_t * stree, nps_node_t op1, nps_node_t op2); @@ -222,7 +222,13 @@ void NBCGenCodeEnv(nbc_env_t * env, uint8_t a, int16_t b) bc = ENV_BC(env); if (a == kNBCFieldMask) - b = 1; + b = 7; + // pop-handler is 07 00 07 + // Quoting Walter Smith: + // Due to historical stupidity, the pophandlers instruction is the + // nonexistent eighth unary1op. Its encoding is 07 00 07! + // https://github.com/wrs/prota/blob/a07c772bce39c5153687dd853206c0803dabdf05/runtime/interpreter.cpp#L593 + // NEWT/0 and @wrs' Prota don't care, but NewtonOS might. if (a != kNBCFieldMask && ((a & kNBCFieldMask) == a || @@ -599,10 +605,27 @@ void NBCDefLocal(newtRefArg type, newtRefArg r, bool init) * @note 分岐命令やループ命令などすぐにオペデータが決定しない場合に使う */ -void NBCBackPatch(uint32_t cx, int16_t b) -{ - BC[cx + 1] = b >> 8; - BC[cx + 2] = b & 0xff; +void NBCBackPatch(uint32_t cx, uintptr_t b) +{ + // If value fits on 16 bits, simply write it + // Otherwise, add it to literals and modify previous instruction. + // Values might not fit if it is a push constant of an integer > 8192 + // which may happen for exception handlers (branches are limited to 2^16 + // so exceptions should be possible up to 2^16). + if (b < 0x10000) { + BC[cx + 1] = b >> 8; + BC[cx + 2] = b & 0xff; + } else { + if (BC[cx] == (kNBCPushConstant | kNBCFieldMask)) { + // push-constant + ssize_t ix = NewtFindArrayIndex(newt_bc_env->literals, b, 0); + if (ix == -1) // リテラルに追加 + ix = NBCAddLiteralEnv(newt_bc_env, b); + BC[cx + 1] = ix >> 8; + BC[cx + 2] = ix & 0xff; + BC[cx] = kNBCPush | kNBCFieldMask; + } + } } @@ -689,7 +712,7 @@ void NBCPushOnexcpStack(uint32_t cx) * @return なし */ -void NBCOnexcpBackPatchs(uint32_t try_head, uint32_t cx) +void NBCOnexcpBackPatchs(uint32_t try_head) { uint32_t branch; @@ -700,28 +723,29 @@ void NBCOnexcpBackPatchs(uint32_t try_head, uint32_t cx) if (branch < try_head) break; - NBCBackPatch(branch, cx); // ブランチをバックパッチ + if (branch + 3 == CX) { + // Simplify and remove branch. + CX -= 3; + } else { + NBCBackPatch(branch, CX); // ブランチをバックパッチ + } } } /*------------------------------------------------------------------------*/ /** 例外処理命令のバイトコードを生成する - * - * @param pc [in] 例外処理命令のプログラムカウンタ * * @return なし */ -void NBCGenOnexcpPC(int32_t pc) +void NBCGenOnexcpPC(void) { - newtRefVar r; - int16_t b; - - r = NewtMakeInteger(pc); - b = NBCGenPushLiteral(r); + uint32_t cx = CX; - NBCPushOnexcpStack(b); // バックパッチのためにスタックにプッシュする + NBCGenCode(kNBCPushConstant, 0xFFFF); // push constant, later patched + // as push literal if required. + NBCPushOnexcpStack(cx); // バックパッチのためにスタックにプッシュする } @@ -757,7 +781,7 @@ void NBCOnexcpBackPatchL(uint32_t sp, int32_t pc) return; r = NewtMakeInteger(pc); - NewtSetArraySlot(LITERALS, ONEXCPSTACK[sp], r); + NBCBackPatch(ONEXCPSTACK[sp], r); } @@ -1159,7 +1183,7 @@ int16_t NBCGenTryPre(nps_syntax_node_t * stree, nps_node_t r) { case kNPSOnexception: NBCGenPUSH(node->op1); // シンボル - NBCGenOnexcpPC(-1); // PC(ダミー) + NBCGenOnexcpPC(); numExcps = 1; break; @@ -1186,7 +1210,7 @@ int16_t NBCGenTryPre(nps_syntax_node_t * stree, nps_node_t r) */ int16_t NBCGenTryPost(nps_syntax_node_t * stree, nps_node_t r, - uint32_t * onexcpspP) + uint32_t * onexcpspP, bool ret) { int16_t numExcps = 0; @@ -1204,15 +1228,15 @@ int16_t NBCGenTryPost(nps_syntax_node_t * stree, nps_node_t r, (*onexcpspP)++; // onexception のコード生成 - NBCGenBC_stmt(stree, node->op2, true); + NBCGenBC_stmt(stree, node->op2, ret); NBCGenOnexcpBranch(); numExcps = 1; break; case kNPSOnexceptionList: - numExcps = NBCGenTryPost(stree, node->op1, onexcpspP) - + NBCGenTryPost(stree, node->op2, onexcpspP); + numExcps = NBCGenTryPost(stree, node->op1, onexcpspP, ret) + + NBCGenTryPost(stree, node->op2, onexcpspP, ret); break; } } @@ -1232,7 +1256,7 @@ int16_t NBCGenTryPost(nps_syntax_node_t * stree, nps_node_t r, */ void NBCGenTry(nps_syntax_node_t * stree, nps_node_t expr, - nps_node_t onexception_list) + nps_node_t onexception_list, bool ret) { uint32_t onexcp_cx; uint32_t branch_cx; @@ -1244,17 +1268,17 @@ void NBCGenTry(nps_syntax_node_t * stree, nps_node_t expr, NBCGenCode(kNBCNewHandlers, numExcps); // 実行文 - NBCGenBC_op(stree, expr); + NBCGenBC_stmt(stree, expr, ret); NBCGenCode(kNBCPopHandlers, 0); branch_cx = NBCGenBranch(kNBCBranch); // onexception onexcp_cx = CX; - NBCGenTryPost(stree, onexception_list, &onexcpsp); + NBCGenTryPost(stree, onexception_list, &onexcpsp, ret); // onexception の終了 - NBCOnexcpBackPatchs(onexcp_cx, CX); // onexception の終了をバックパッチ + NBCOnexcpBackPatchs(onexcp_cx); // onexception の終了をバックパッチ NBCGenCode(kNBCPopHandlers, 0); // ONEXCPSP を戻す @@ -2278,13 +2302,11 @@ void NBCGenSyntaxCode(nps_syntax_node_t * stree, nps_syntax_node_t * node, bool break; case kNPSTry: - NBCGenTry(stree, node->op1, node->op2); - NVCGenNoResult(ret); + NBCGenTry(stree, node->op1, node->op2, ret); break; case kNPSIf: NBCGenIfThenElse(stree, node->op1, node->op2, ret); -// NVCGenNoResult(ret); break; case kNPSLoop: diff --git a/src/newt_core/NewtFns.c b/src/newt_core/NewtFns.c index b9739b8..0a1ec8d 100755 --- a/src/newt_core/NewtFns.c +++ b/src/newt_core/NewtFns.c @@ -203,15 +203,14 @@ newtRef NcLookupSymbol(newtRefArg r, newtRefArg name) * @param name [in] 例外シンボル * @param data [in] 例外データ * - * @retval NIL + * @retval stack head (to be pushed by calling function) * * @note スクリプトからの呼出し用 */ newtRef NsThrow(newtRefArg rcvr, newtRefArg name, newtRefArg data) { - NVMThrow(name, data); - return kNewtRefNIL; + return NVMThrow(name, data); } @@ -220,15 +219,14 @@ newtRef NsThrow(newtRefArg rcvr, newtRefArg name, newtRefArg data) * * @param rcvr [in] レシーバ * - * @retval NIL + * @retval stack head (to be pushed by calling function) * * @note スクリプトからの呼出し用 */ newtRef NsRethrow(newtRefArg rcvr) { - NVMRethrow(); - return kNewtRefNIL; + return NVMRethrow(); } @@ -2201,6 +2199,56 @@ newtRef NsGetEnv(newtRefArg rcvr, newtRefArg r) #if 0 #pragma mark - #endif + +/*------------------------------------------------------------------------*/ +/** + * Determine if two binaries are equal. + * + * @param rcvr self (ignored). + * @param a the first binary to consider. + * @param b the second binary to consider. + * @return true if the two binaries are equal, nil otherwise. + */ +newtRef NsBinEqual(newtRefArg rcvr, newtRefArg a, newtRefArg b) +{ + char* aString; + char* bString; + newtRefVar theResult = kNewtRefNIL; + + (void) rcvr; + + /* check parameters */ + if (! NewtRefIsBinary(a)) + { + theResult = NewtThrow(kNErrNotABinaryObject, a); + } else if (! NewtRefIsBinary(b)) { + theResult = NewtThrow(kNErrNotABinaryObject, b); + } else if (a == b) { + theResult = kNewtRefTRUE; + } else { + size_t aLen = NewtBinaryLength(a); + size_t bLen = NewtBinaryLength(b); + + if (aLen == bLen) { + newtObjRef aObj = NewtRefToPointer(a); + newtObjRef bObj = NewtRefToPointer(b); + newtRefVar aKlass = aObj->as.klass; + newtRefVar bKlass = bObj->as.klass; + + if (NewtRefEqual(aKlass, bKlass)) { + uint8_t* aPtr = NewtRefToBinary(a); + uint8_t* bPtr = NewtRefToBinary(b); + + if (memcmp(aPtr, bPtr, aLen) == 0) { + theResult = kNewtRefTRUE; + } + } + } + } + + return theResult; +} + /*------------------------------------------------------------------------*/ /** オフセット位置から符号付きの1バイトを取り出す。 * diff --git a/src/newt_core/NewtGC.c b/src/newt_core/NewtGC.c index 7b9ae8b..9872a2e 100755 --- a/src/newt_core/NewtGC.c +++ b/src/newt_core/NewtGC.c @@ -465,20 +465,18 @@ void NewtGCStackMark(vm_env_t * env, bool mark) // 例外ハンドラ・スタック NewtGCRefMark(env->currexcp, mark); -/* { vm_excp_t * excpstack; vm_excp_t * excp; excpstack = (vm_excp_t *)env->excpstack.stackp; - for (i = 0; i < env->excpsp; i++) + for (i = 0; i < env->excpstack.sp; i++) { excp = &excpstack[i]; NewtGCRefMark(excp->sym, mark); } } -*/ } diff --git a/src/newt_core/NewtVM.c b/src/newt_core/NewtVM.c index 88b5565..287e39e 100755 --- a/src/newt_core/NewtVM.c +++ b/src/newt_core/NewtVM.c @@ -220,7 +220,7 @@ static simple_instruction_t simple_instructions[] = si_set_lex_scope, // 004 set-lex-scope si_iternext, // 005 iter-next si_iterdone, // 006 iter-done - si_pop_handlers // 007 000 001 pop-handlers + si_pop_handlers // 007 000 007 pop-handlers }; @@ -439,8 +439,10 @@ newtErr NVMGetExceptionErrCode(newtRefArg r, bool dump) if (NewtRefIsNIL(r)) return kNErrNone; - if (dump) + if (dump) { + NewtFprintf(stderr, "Uncaught exception:\n"); NewtPrintObject(stderr, r); + } err = NcGetSlot(r, NSSYM0(error)); @@ -484,20 +486,27 @@ newtRef NVMMakeExceptionFrame(newtRefArg name, newtRefArg data) * @param name [in] シンボル * @param data [in] 例外フレーム * - * @return なし + * @return stack head (to be pushed back) */ -void NVMThrowData(newtRefArg name, newtRefArg data) +newtRef NVMThrowData(newtRefArg name, newtRefArg data) { vm_excp_t * excp; size_t i; + size_t next_excpstack_top; // 例外処理中ならクリアする NVMClearCurrException(); CURREXCP = data; - - for (i = EXCPSP; 0 < i; i--) + // Do not rewind past the current environment + // Instead, we'll rethrow on environment pop. + if (vm_env.next) { + next_excpstack_top = vm_env.next->excpstack.sp; + } else { + next_excpstack_top = 0; + } + for (i = EXCPSP; next_excpstack_top < i; i--) { excp = &EXCPSTACK[i - 1]; @@ -505,11 +514,15 @@ void NVMThrowData(newtRefArg name, newtRefArg data) { reg_rewind(excp->callsp); PC = excp->pc; - return; + SP = excp->sp; + return stk_pop0(); } } + // Could not find any handler in this environment, will pass to next + // environment (vm_env_pop will rethrow) NVMNoStackFrameForReturn(); + return kNewtRefNIL; } @@ -519,25 +532,25 @@ void NVMThrowData(newtRefArg name, newtRefArg data) * @param name [in] シンボル * @param data [in] データ * - * @return なし + * @return stack head (to be pushed back) */ -void NVMThrow(newtRefArg name, newtRefArg data) +newtRef NVMThrow(newtRefArg name, newtRefArg data) { newtRefVar r; r = NVMMakeExceptionFrame(name, data); - NVMThrowData(name, r); + return NVMThrowData(name, r); } /*------------------------------------------------------------------------*/ /** rethrow する * - * @return なし + * @return stack head (to be pushed back) */ -void NVMRethrow(void) +newtRef NVMRethrow(void) { if (NewtRefIsNotNIL(CURREXCP)) { @@ -547,9 +560,9 @@ void NVMRethrow(void) currexcp = CURREXCP; name = NcGetSlot(currexcp, NSSYM0(name)); -// excp_pop_handlers(); - NVMThrowData(name, currexcp); + return NVMThrowData(name, currexcp); } + return kNewtRefNIL; } @@ -636,7 +649,8 @@ void NVMNoStackFrameForReturn(void) { BC = NULL; BCLEN = 0; - CALLSP = 0; + // Change PC to exit other tests such as si_set_lex_scope. + PC = (uint32_t) -1; } @@ -887,6 +901,7 @@ bool excp_push(newtRefArg sym, newtRefArg pc) excp = &EXCPSTACK[EXCPSP]; excp->callsp = CALLSP; + excp->sp = SP; excp->excppc = PC; excp->sym = sym; excp->pc = (uint32_t) NewtRefToInteger(pc); @@ -1735,10 +1750,12 @@ void NVMFuncCall(newtRefArg fn, int16_t numArgs) if (type == kNewtNativeFn || type == kNewtNativeFunc) { // ネイティブ関数の呼出し // Save CALLSP to know if an exception occurred. + // Also set PC to 0 if there was no handler. uint32_t saveCALLSP; reg_save(SP - numArgs + 1); FUNC = fn; saveCALLSP = CALLSP; + PC = 0; switch (type) { @@ -1753,7 +1770,7 @@ void NVMFuncCall(newtRefArg fn, int16_t numArgs) break; } - if (saveCALLSP == CALLSP) + if (saveCALLSP == CALLSP && PC == 0) { reg_pop(); } @@ -1813,12 +1830,14 @@ void NVMMessageSend(newtRefArg impl, newtRefArg receiver, newtRefArg fn, int16_t if (type == kNewtNativeFn || type == kNewtNativeFunc) { // ネイティブ関数の呼出し // Save CALLSP to know if an exception occurred. + // Also set PC to 0 if there was no handler. uint32_t saveCALLSP; reg_save(SP - numArgs + 1); FUNC = fn; RCVR = receiver; IMPL = impl; saveCALLSP = CALLSP; + PC = 0; switch (type) { @@ -1833,7 +1852,7 @@ void NVMMessageSend(newtRefArg impl, newtRefArg receiver, newtRefArg fn, int16_t break; } - if (saveCALLSP == CALLSP) + if (saveCALLSP == CALLSP && PC == 0) { reg_pop(); } @@ -2045,13 +2064,22 @@ void si_set_lex_scope(void) { newtRefVar fn; newtRefVar af; + int type; - fn = NcClone(stk_pop()); - af = NcClone(NcGetSlot(fn, NSSYM0(argFrame))); - NcSetSlot(af, NSSYM0(_nextArgFrame), LOCALS); - NcSetSlot(af, NSSYM0(_parent), RCVR); - NcSetSlot(af, NSSYM0(_implementor), IMPL); - NcSetSlot(fn, NSSYM0(argFrame), af); + fn = stk_pop(); + type = NewtRefFunctionType(fn); + if (type == kNewtNotFunction) { + return stk_push(NVMThrow(kNErrNotAFunction, fn)); + } + if (type != kNewtNativeFn && type != kNewtNativeFunc) + { + fn = NcClone(fn); + af = NcClone(NcGetSlot(fn, NSSYM0(argFrame))); + NcSetSlot(af, NSSYM0(_nextArgFrame), LOCALS); + NcSetSlot(af, NSSYM0(_parent), RCVR); + NcSetSlot(af, NSSYM0(_implementor), IMPL); + NcSetSlot(fn, NSSYM0(argFrame), af); + } stk_push(fn); } @@ -3452,7 +3480,26 @@ void vm_env_pop(void) if (next) { + // Save current exception, if any, for a possible rethrow. + newtRefVar currexcp = kNewtRefUnbind; + if (PC == (uint32_t) -1) { + currexcp = CURREXCP; + } + + // Pointers can have been changed by realloc + next->stack.stackp = vm_env.stack.stackp; + next->callstack.stackp = vm_env.callstack.stackp; + next->excpstack.stackp = vm_env.excpstack.stackp; + vm_env = *next; + + // Rethrow, but not with NVMRethrow that is meant for rethrow bytecode + // instruction (executed in a handler), but with NVMThrowData. + if (NewtRefIsNotNIL(currexcp)) { + newtRefVar name; + name = NcGetSlot(currexcp, NSSYM0(name)); + stk_push(NVMThrowData(name, currexcp)); + } } } @@ -3611,6 +3658,8 @@ void NVMInitGlobalFns1(void) NewtDefGlobalFunc(NSSYM(Array), NsMakeArray, 2, "Array(size, initialValue)"); NewtDefGlobalFunc(NSSYM(SetContains), NsSetContains, 2, "SetContains( array, item )"); + + NewtDefGlobalFunc(NSSYM(BinEqual), NsBinEqual, 2, "BinEqual(a, b)"); } @@ -3727,6 +3776,7 @@ void NVMInit(void) newtRefVar result; vm_env.level = 0; + vm_env.next = NULL; NVMInitREG(); NVMInitSTACK(); @@ -3776,7 +3826,7 @@ void NVMLoop(uint32_t callsp) if (NEWT_DEBUG) NewtDebugMsg("VM", "VM Level = %d\n", vm_env.level); - while (callsp < CALLSP && PC < BCLEN) + while (callsp < CALLSP && PC < BCLEN && BC != NULL) { op = BC[PC]; @@ -3834,8 +3884,10 @@ void NVMLoop(uint32_t callsp) void NVMFnCall(newtRefArg fn, int16_t numArgs) { + uint32_t pc = PC; stk_push(fn); si_set_lex_scope(); + if (pc != PC) return; is_invoke(numArgs); } @@ -3865,7 +3917,7 @@ newtRef NVMCall(newtRefArg fn, int16_t numArgs, newtErr * errP) NVMLoop(CALLSP - 1); result = stk_top(); - // restore the VM + // restore the VM, rethrowing if required. vm_env_pop(); } diff --git a/src/newt_core/incs/NewtBC.h b/src/newt_core/incs/NewtBC.h index 50780ff..68f28c0 100755 --- a/src/newt_core/incs/NewtBC.h +++ b/src/newt_core/incs/NewtBC.h @@ -38,7 +38,7 @@ enum { kNBCSetLexScope = 004, // 004 set-lex-scope kNBCIterNext = 005, // 005 iter-next kNBCIterDone = 006, // 006 iter-done - kNBCPopHandlers = 007 // 007 000 001 pop-handlers + kNBCPopHandlers = 007 // 007 000 007 pop-handlers }; diff --git a/src/newt_core/incs/NewtFns.h b/src/newt_core/incs/NewtFns.h index 322e690..bb1d9bf 100755 --- a/src/newt_core/incs/NewtFns.h +++ b/src/newt_core/incs/NewtFns.h @@ -161,6 +161,7 @@ newtRef NsExit(newtRefArg rcvr, newtRefArg r); newtRef NsCompile(newtRefArg rcvr, newtRefArg r); newtRef NsGetEnv(newtRefArg rcvr, newtRefArg r); +newtRef NsBinEqual(newtRefArg rcvr, newtRefArg a, newtRefArg b); newtRef NsExtractByte(newtRefArg rcvr, newtRefArg r, newtRefArg offset); newtRef NsExtractWord(newtRefArg rcvr, newtRefArg r, newtRefArg offset); diff --git a/src/newt_core/incs/NewtType.h b/src/newt_core/incs/NewtType.h index f3136aa..a822e73 100755 --- a/src/newt_core/incs/NewtType.h +++ b/src/newt_core/incs/NewtType.h @@ -125,8 +125,9 @@ enum { /* 型宣言 */ -// Ref(Integer, Pointer, Charcter, Spatial, Magic pointer) +// Ref(Integer, Pointer, Character, Special, Magic pointer) typedef uintptr_t newtRef; ///< オブジェクト参照 +// NewtGC() is only called when there should be no lingering newtRefVar on the stack. typedef newtRef newtRefVar; ///< オブジェクト参照変数 typedef const newtRef newtRefArg; ///< オブジェクト参照引数 diff --git a/src/newt_core/incs/NewtVM.h b/src/newt_core/incs/NewtVM.h index 8c40940..59841e5 100755 --- a/src/newt_core/incs/NewtVM.h +++ b/src/newt_core/incs/NewtVM.h @@ -95,6 +95,7 @@ typedef struct { /// 例外ハンドラ typedef struct { uint32_t callsp; ///< 呼出しスタックのスタックポインタ + uint32_t sp; ///< Saved stack pointer uint32_t excppc; ///< 例外ハンドラを作成したときのプログラムカウンタ newtRefVar sym; ///< シンボル @@ -141,9 +142,9 @@ newtRef NVMSelf(void); newtRef NVMCurrentFunction(void); newtRef NVMCurrentImplementor(void); bool NVMHasVar(newtRefArg name); -void NVMThrowData(newtRefArg name, newtRefArg data); -void NVMThrow(newtRefArg name, newtRefArg data); -void NVMRethrow(void); +newtRef NVMThrowData(newtRefArg name, newtRefArg data); +newtRef NVMThrow(newtRefArg name, newtRefArg data); +newtRef NVMRethrow(void); newtRef NVMCurrentException(void); void NVMClearException(void); diff --git a/tests/test_common.newt b/tests/test_common.newt new file mode 100644 index 0000000..4d7fbc6 --- /dev/null +++ b/tests/test_common.newt @@ -0,0 +1,105 @@ +func TestEquality(x, y) +begin + local equal := nil; + if IsImmediate(x) then equal := x = y; + if IsBinary(x) then equal := IsBinary(y) and BinEqual(x, y); + if IsFrame(x) then begin + if IsFrame(y) and Length(x) = Length(y) then + begin + equal := true; + foreach k, v in x do + equal := equal and HasSlot(k, y) and TestEquality(v, y.k) + end; + end; + if IsArray(x) then begin + if IsArray(y) and Length(x) = Length(y) then + begin + equal := true; + foreach k, v in x do + equal := equal and TestEquality(v, y[k]) + end; + end; + return equal; +end; + +global protoTestCase := { + Run: func() + begin + local finalResult := true; + foreach k, v in self do + begin + local symbolAsStr := k & ""; + if BeginsWith(symbolAsStr, "test") and IsFunction(v) then + begin + local len := StrLen(symbolAsStr); + while len < 70 do + begin + symbolAsStr := symbolAsStr & "."; + len := len + 1; + end; + Print(symbolAsStr); + local resultStr := "OK"; + local failedException := nil; + try + Perform(self, k, []); + onexception |evt.ex.test| do + begin + resultStr := "FAILED"; + finalResult := nil; + failedException := CurrentException(); + end + onexception |evt| do + begin + resultStr := "ERROR"; + finalResult := nil; + failedException := CurrentException(); + end; + Print(" " & resultStr & "\n"); + if failedException then + P(failedException); + end; + end; + return finalResult; + end, + + AssertTrue: func(x) + begin + if not x then + begin + Throw('|evt.ex.test.assertTrueFailed|, {x: x}) + end; + end, + + AssertEqual: func(x, y) + begin + if not TestEquality(x, y) then + begin + Throw('|evt.ex.test.assertEqualFailed|, {x: x, y: y}) + end; + end, + + AssertEqualDelta: func(x, y, delta) + begin + if x > y + delta or x < y - delta then + begin + Throw('|evt.ex.test.assertEqualDeltaFailed|, {x: x, y: y, delta: delta}) + end; + end, + + AssertThrow: func(sym, closure) + begin + local caught := nil; + local exception := nil; + try + call closure with () + onexception |evt| do + begin + exception := CurrentException(); + caught := HasSubclass(exception.name, sym); + end; + if not caught then + begin + Throw('|evt.ex.test.assertThrowFailed|, {sym: sym, exception: exception, closure: closure}) + end; + end, +}; diff --git a/tests/test_exceptions.newt b/tests/test_exceptions.newt new file mode 100755 index 0000000..6cebb2e --- /dev/null +++ b/tests/test_exceptions.newt @@ -0,0 +1,112 @@ +#!newt + +if not load("test_common.newt") then +begin + Print("Could not load test_common.newt\n"); + Exit(1); +end; + +local testCases := [ + { + _proto: protoTestCase, + testCatchBuiltIn: func() begin + :AssertThrow('|evt.ex.fr|, func() begin + 5 / 0 + end); + :AssertTrue(begin + local caught := nil; + try + 5 / 0 + onexception |evt.ex.fr| do + caught := true; + caught + end) + end, + testCatchCustom: func() begin + :AssertTrue(begin + local caught := nil; + try + Throw('|evt.ex.foo|, nil) + onexception |evt.ex.foo| do + caught := true; + caught + end) + end, + testCatchCustomWithData: func() begin + :AssertEqual({}, begin + local result := nil; + try + Throw('|evt.ex.foo;type.ref.frame|, {}) + onexception |evt.ex.foo| do + result := CurrentException().data; + result + end) + end, + testCatchCustomWithString: func() begin + :AssertEqual("Some data", begin + local result := nil; + try + Throw('|evt.ex.foo;type.ref.string|, "Some data") + onexception |evt.ex.foo| do + result := CurrentException().data; + result + end) + end, + testCatchCustomWithSubException: func() begin + :AssertTrue(begin + local caught := nil; + try + Throw('|evt.ex.foo.sub|, nil) + onexception |evt.ex.foo| do + caught := true; + caught + end) + end, + testCatchCustomWithApply: func() begin + :AssertTrue(begin + local caught := nil; + try + Apply(GetGlobalFn('Throw), ['|evt.ex.foo.sub|, nil]) + onexception |evt.ex.foo| do + caught := true; + caught + end) + end, + testCatchCustomWithPerform: func() begin + :AssertTrue(begin + local caught := nil; + try + Perform({throw: func() begin Throw('|evt.ex.foo.sub|, nil) end}, 'throw, []) + onexception |evt.ex.foo| do + caught := true; + caught + end) + end, + testRethrow: func() begin + :AssertTrue(begin + local caught := nil; + local recaught := nil; + try + try + Throw('|evt.ex.foo|, nil); + onexception |evt.ex.foo| do + begin + caught := true; + Rethrow(); + end; + onexception |evt.ex.foo| do + recaught := true; + caught and recaught + end) + end, + } +]; + +local finalResult := true; + +foreach tc in testCases do +begin + finalResult := finalResult && tc:Run(); +end; + +if not finalResult then Exit(1);