From 85ad910f23b5fe86a75591e98760e22cd5dcc867 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Sun, 3 Jul 2022 19:50:00 +0900 Subject: [PATCH] Attach __cause__, __context__ to exceptions; support 'raise ... from ...' --- src/compiler.c | 8 +- src/exceptions.c | 2 + src/kuroko/chunk.h | 1 + src/opcodes.h | 1 + src/vm.c | 185 ++++++++++++++++---------- test/testExceptionContexts.krk | 33 +++++ test/testExceptionContexts.krk.expect | 4 + 7 files changed, 166 insertions(+), 68 deletions(-) create mode 100644 test/testExceptionContexts.krk create mode 100644 test/testExceptionContexts.krk.expect diff --git a/src/compiler.c b/src/compiler.c index d7f24c0..0a01ad9 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -2222,7 +2222,13 @@ _anotherExcept: static void raiseStatement(void) { parsePrecedence(PREC_ASSIGNMENT); - emitByte(OP_RAISE); + + if (match(TOKEN_FROM)) { + parsePrecedence(PREC_ASSIGNMENT); + emitByte(OP_RAISE_FROM); + } else { + emitByte(OP_RAISE); + } } static size_t importModule(KrkToken * startOfName, int leadingDots) { diff --git a/src/exceptions.c b/src/exceptions.c index 30d0845..4bb62e8 100644 --- a/src/exceptions.c +++ b/src/exceptions.c @@ -31,6 +31,8 @@ static KrkValue krk_initException(int argc, const KrkValue argv[], int hasKw) { if (argc > 1) { krk_attachNamedValue(&self->fields, "arg", argv[1]); } + krk_attachNamedValue(&self->fields, "__cause__", NONE_VAL()); + krk_attachNamedValue(&self->fields, "__context__", NONE_VAL()); return argv[0]; } diff --git a/src/kuroko/chunk.h b/src/kuroko/chunk.h index 7da74bd..5612dfb 100644 --- a/src/kuroko/chunk.h +++ b/src/kuroko/chunk.h @@ -70,6 +70,7 @@ typedef enum { OP_INVOKE_AWAIT, OP_FLOORDIV, OP_UNSET, + OP_RAISE_FROM, OP_INPLACE_ADD, OP_INPLACE_BITAND, diff --git a/src/opcodes.h b/src/opcodes.h index f6b0472..2378fef 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -15,6 +15,7 @@ SIMPLE(OP_LESS) SIMPLE(OP_POP) SIMPLE(OP_INHERIT) SIMPLE(OP_RAISE) +SIMPLE(OP_RAISE_FROM) SIMPLE(OP_CLOSE_UPVALUE) SIMPLE(OP_DOCSTRING) SIMPLE(OP_BITOR) diff --git a/src/vm.c b/src/vm.c index 2b90d63..cec9c54 100644 --- a/src/vm.c +++ b/src/vm.c @@ -144,20 +144,30 @@ void krk_resetStack(void) { krk_currentThread.currentException = NONE_VAL(); } -/** - * Display a traceback by scanning up the stack / call frames. - * The format of the output here is modeled after the output - * given by CPython, so we display the outermost call first - * and then move inwards; on each call frame we try to open - * the source file and print the corresponding line. - */ -void krk_dumpTraceback(void) { - if (!krk_valuesEqual(krk_currentThread.currentException,NONE_VAL())) { - krk_push(krk_currentThread.currentException); +static void dumpInnerException(KrkValue exception, int depth) { + if (depth > 10) { + fprintf(stderr, "Too many inner exceptions encountered.\n"); + return; + } + + krk_push(exception); + if (IS_INSTANCE(exception)) { + + KrkValue inner; + + /* Print cause or context */ + if (krk_tableGet(&AS_INSTANCE(exception)->fields, OBJECT_VAL(S("__cause__")), &inner) && !IS_NONE(inner)) { + dumpInnerException(inner, depth + 1); + fprintf(stderr, "\nThe above exception was the direct cause of the following exception:\n\n"); + } else if (krk_tableGet(&AS_INSTANCE(exception)->fields, OBJECT_VAL(S("__context__")), &inner) && !IS_NONE(inner)) { + dumpInnerException(inner, depth + 1); + fprintf(stderr, "\nDuring handling of the above exception, another exception occurred:\n\n"); + } + KrkValue tracebackEntries; - if (IS_INSTANCE(krk_currentThread.currentException) - && krk_tableGet(&AS_INSTANCE(krk_currentThread.currentException)->fields, OBJECT_VAL(S("traceback")), &tracebackEntries) + if (krk_tableGet(&AS_INSTANCE(exception)->fields, OBJECT_VAL(S("traceback")), &tracebackEntries) && IS_list(tracebackEntries) && AS_LIST(tracebackEntries)->count > 0) { + /* This exception has a traceback we can print. */ fprintf(stderr, "Traceback (most recent call last):\n"); for (size_t i = 0; i < AS_LIST(tracebackEntries)->count; ++i) { @@ -184,53 +194,66 @@ void krk_dumpTraceback(void) { (function->name ? function->name->chars : "(unnamed)")); #ifndef NO_SOURCE_IN_TRACEBACK - /* Try to open the file */ - if (function->chunk.filename) { - FILE * f = fopen(function->chunk.filename->chars, "r"); - if (f) { - int line = 1; - do { - int c = fgetc(f); - if (c < -1) break; - if (c == '\n') { - line++; - continue; - } - if (line == lineNo) { - fprintf(stderr," "); - while (c == ' ') c = fgetc(f); - do { - fputc(c, stderr); - c = fgetc(f); - } while (!feof(f) && c > 0 && c != '\n'); - fprintf(stderr, "\n"); - break; - } - } while (!feof(f)); - fclose(f); - } + /* Try to open the file */ + if (function->chunk.filename) { + FILE * f = fopen(function->chunk.filename->chars, "r"); + if (f) { + int line = 1; + do { + int c = fgetc(f); + if (c < -1) break; + if (c == '\n') { + line++; + continue; + } + if (line == lineNo) { + fprintf(stderr," "); + while (c == ' ') c = fgetc(f); + do { + fputc(c, stderr); + c = fgetc(f); + } while (!feof(f) && c > 0 && c != '\n'); + fprintf(stderr, "\n"); + break; + } + } while (!feof(f)); + fclose(f); } + } #endif } } + } - /* Is this a SyntaxError? Handle those specially. */ - if (krk_isInstanceOf(krk_currentThread.currentException, vm.exceptions->syntaxError)) { - KrkValue result = krk_callDirect(krk_getType(krk_currentThread.currentException)->_tostr, 1); - fprintf(stderr, "%s\n", AS_CSTRING(result)); - return; - } - /* Clear the exception state while printing the exception. */ - krk_currentThread.flags &= ~(KRK_THREAD_HAS_EXCEPTION); - fprintf(stderr, "%s", krk_typeName(krk_currentThread.currentException)); - KrkValue result = krk_callDirect(krk_getType(krk_currentThread.currentException)->_tostr, 1); - if (!IS_STRING(result)) { - fprintf(stderr, "\n"); - } else { - fprintf(stderr, ": %s\n", AS_CSTRING(result)); - } - /* Turn the exception flag back on */ - krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION; + /* Is this a SyntaxError? Handle those specially. */ + if (krk_isInstanceOf(exception, vm.exceptions->syntaxError)) { + KrkValue result = krk_callDirect(krk_getType(exception)->_tostr, 1); + fprintf(stderr, "%s\n", AS_CSTRING(result)); + return; + } + /* Clear the exception state while printing the exception. */ + krk_currentThread.flags &= ~(KRK_THREAD_HAS_EXCEPTION); + fprintf(stderr, "%s", krk_typeName(exception)); + KrkValue result = krk_callDirect(krk_getType(exception)->_tostr, 1); + if (!IS_STRING(result)) { + fprintf(stderr, "\n"); + } else { + fprintf(stderr, ": %s\n", AS_CSTRING(result)); + } + /* Turn the exception flag back on */ + krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION; +} + +/** + * Display a traceback by scanning up the stack / call frames. + * The format of the output here is modeled after the output + * given by CPython, so we display the outermost call first + * and then move inwards; on each call frame we try to open + * the source file and print the corresponding line. + */ +void krk_dumpTraceback(void) { + if (!krk_valuesEqual(krk_currentThread.currentException,NONE_VAL())) { + dumpInnerException(krk_currentThread.currentException, 0); } } @@ -264,6 +287,36 @@ static void attachTraceback(void) { } /* else: probably a legacy 'raise str', just don't bother. */ } +static void attachInnerException(KrkValue innerException) { + if (IS_INSTANCE(krk_currentThread.currentException)) { + KrkInstance * theException = AS_INSTANCE(krk_currentThread.currentException); + if (krk_valuesSame(krk_currentThread.currentException,innerException)) { + /* re-raised? */ + return; + } else { + krk_attachNamedValue(&theException->fields, "__context__", innerException); + } + } +} + +static void raiseException(KrkValue base, KrkValue cause) { + if (IS_CLASS(base)) { + krk_push(base); + base = krk_callStack(0); + } + krk_currentThread.currentException = base; + if (IS_CLASS(cause)) { + krk_push(cause); + cause = krk_callStack(0); + } + if (IS_INSTANCE(krk_currentThread.currentException) && !IS_NONE(cause)) { + krk_attachNamedValue(&AS_INSTANCE(krk_currentThread.currentException)->fields, + "__cause__", cause); + } + attachTraceback(); + krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION; +} + /** * Raise an exception. Creates an exception object of the requested type * and formats a message string to attach to it. Exception classes are @@ -280,12 +333,9 @@ KrkValue krk_runtimeError(KrkClass * type, const char * fmt, ...) { /* Allocate an exception object of the requested type. */ KrkInstance * exceptionObject = krk_newInstance(type); krk_push(OBJECT_VAL(exceptionObject)); - krk_push(OBJECT_VAL(S("arg"))); - krk_push(OBJECT_VAL(krk_copyString(buf, len))); - /* Attach its argument */ - krk_tableSet(&exceptionObject->fields, krk_peek(1), krk_peek(0)); - krk_pop(); - krk_pop(); + krk_attachNamedValue(&exceptionObject->fields, "arg", OBJECT_VAL(krk_copyString(buf, len))); + krk_attachNamedValue(&exceptionObject->fields, "__cause__", NONE_VAL()); + krk_attachNamedValue(&exceptionObject->fields, "__context__", NONE_VAL()); krk_pop(); /* Set the current exception to be picked up by handleException */ @@ -2484,13 +2534,11 @@ _finishReturn: (void)0; case OP_INPLACE_POW: INPLACE_BINARY_OP(pow) case OP_RAISE: { - if (IS_CLASS(krk_peek(0))) { - krk_currentThread.currentException = krk_callStack(0); - } else { - krk_currentThread.currentException = krk_pop(); - } - attachTraceback(); - krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION; + raiseException(krk_peek(0), NONE_VAL()); + goto _finishException; + } + case OP_RAISE_FROM: { + raiseException(krk_peek(1), krk_peek(0)); goto _finishException; } case OP_CLOSE_UPVALUE: @@ -3275,6 +3323,9 @@ _finishReturn: (void)0; if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { _finishException: if (!handleException()) { + if (!IS_NONE(krk_currentThread.stackTop[-2])) { + attachInnerException(krk_currentThread.stackTop[-2]); + } frame = &krk_currentThread.frames[krk_currentThread.frameCount - 1]; frame->ip = frame->closure->function->chunk.code + AS_HANDLER_TARGET(krk_peek(0)); /* Stick the exception into the exception slot */ diff --git a/test/testExceptionContexts.krk b/test/testExceptionContexts.krk new file mode 100644 index 0000000..fa0dc97 --- /dev/null +++ b/test/testExceptionContexts.krk @@ -0,0 +1,33 @@ +try: + try: + try: + raise TypeError('a') + except TypeError: + raise ValueError('b') + except ValueError: + raise IndexError('c') +except IndexError as e: + print(repr(e), repr(e.__context__), repr(e.__context__.__context__)) + + +try: + raise TypeError('a') from ValueError('b') +except TypeError as e: + print(repr(e), repr(e.__cause__), repr(e.__context__)) + +try: + try: + try: + raise TypeError + except TypeError: + raise ValueError + except ValueError: + raise IndexError +except IndexError as e: + print(repr(e), repr(e.__context__), repr(e.__context__.__context__)) + + +try: + raise TypeError from ValueError +except TypeError as e: + print(repr(e), repr(e.__cause__), repr(e.__context__)) diff --git a/test/testExceptionContexts.krk.expect b/test/testExceptionContexts.krk.expect new file mode 100644 index 0000000..7b813d1 --- /dev/null +++ b/test/testExceptionContexts.krk.expect @@ -0,0 +1,4 @@ +IndexError('c') ValueError('b') TypeError('a') +TypeError('a') ValueError('b') None +IndexError() ValueError() TypeError() +TypeError() ValueError() None