Attach __cause__, __context__ to exceptions; support 'raise ... from ...'

This commit is contained in:
K. Lange 2022-07-03 19:50:00 +09:00
parent 2d2691710c
commit 85ad910f23
7 changed files with 166 additions and 68 deletions

View File

@ -2222,7 +2222,13 @@ _anotherExcept:
static void raiseStatement(void) { static void raiseStatement(void) {
parsePrecedence(PREC_ASSIGNMENT); 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) { static size_t importModule(KrkToken * startOfName, int leadingDots) {

View File

@ -31,6 +31,8 @@ static KrkValue krk_initException(int argc, const KrkValue argv[], int hasKw) {
if (argc > 1) { if (argc > 1) {
krk_attachNamedValue(&self->fields, "arg", argv[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]; return argv[0];
} }

View File

@ -70,6 +70,7 @@ typedef enum {
OP_INVOKE_AWAIT, OP_INVOKE_AWAIT,
OP_FLOORDIV, OP_FLOORDIV,
OP_UNSET, OP_UNSET,
OP_RAISE_FROM,
OP_INPLACE_ADD, OP_INPLACE_ADD,
OP_INPLACE_BITAND, OP_INPLACE_BITAND,

View File

@ -15,6 +15,7 @@ SIMPLE(OP_LESS)
SIMPLE(OP_POP) SIMPLE(OP_POP)
SIMPLE(OP_INHERIT) SIMPLE(OP_INHERIT)
SIMPLE(OP_RAISE) SIMPLE(OP_RAISE)
SIMPLE(OP_RAISE_FROM)
SIMPLE(OP_CLOSE_UPVALUE) SIMPLE(OP_CLOSE_UPVALUE)
SIMPLE(OP_DOCSTRING) SIMPLE(OP_DOCSTRING)
SIMPLE(OP_BITOR) SIMPLE(OP_BITOR)

185
src/vm.c
View File

@ -144,20 +144,30 @@ void krk_resetStack(void) {
krk_currentThread.currentException = NONE_VAL(); krk_currentThread.currentException = NONE_VAL();
} }
/** static void dumpInnerException(KrkValue exception, int depth) {
* Display a traceback by scanning up the stack / call frames. if (depth > 10) {
* The format of the output here is modeled after the output fprintf(stderr, "Too many inner exceptions encountered.\n");
* given by CPython, so we display the outermost call first return;
* and then move inwards; on each call frame we try to open }
* the source file and print the corresponding line.
*/ krk_push(exception);
void krk_dumpTraceback(void) { if (IS_INSTANCE(exception)) {
if (!krk_valuesEqual(krk_currentThread.currentException,NONE_VAL())) {
krk_push(krk_currentThread.currentException); 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; KrkValue tracebackEntries;
if (IS_INSTANCE(krk_currentThread.currentException) if (krk_tableGet(&AS_INSTANCE(exception)->fields, OBJECT_VAL(S("traceback")), &tracebackEntries)
&& krk_tableGet(&AS_INSTANCE(krk_currentThread.currentException)->fields, OBJECT_VAL(S("traceback")), &tracebackEntries)
&& IS_list(tracebackEntries) && AS_LIST(tracebackEntries)->count > 0) { && IS_list(tracebackEntries) && AS_LIST(tracebackEntries)->count > 0) {
/* This exception has a traceback we can print. */ /* This exception has a traceback we can print. */
fprintf(stderr, "Traceback (most recent call last):\n"); fprintf(stderr, "Traceback (most recent call last):\n");
for (size_t i = 0; i < AS_LIST(tracebackEntries)->count; ++i) { 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)")); (function->name ? function->name->chars : "(unnamed)"));
#ifndef NO_SOURCE_IN_TRACEBACK #ifndef NO_SOURCE_IN_TRACEBACK
/* Try to open the file */ /* Try to open the file */
if (function->chunk.filename) { if (function->chunk.filename) {
FILE * f = fopen(function->chunk.filename->chars, "r"); FILE * f = fopen(function->chunk.filename->chars, "r");
if (f) { if (f) {
int line = 1; int line = 1;
do { do {
int c = fgetc(f); int c = fgetc(f);
if (c < -1) break; if (c < -1) break;
if (c == '\n') { if (c == '\n') {
line++; line++;
continue; continue;
} }
if (line == lineNo) { if (line == lineNo) {
fprintf(stderr," "); fprintf(stderr," ");
while (c == ' ') c = fgetc(f); while (c == ' ') c = fgetc(f);
do { do {
fputc(c, stderr); fputc(c, stderr);
c = fgetc(f); c = fgetc(f);
} while (!feof(f) && c > 0 && c != '\n'); } while (!feof(f) && c > 0 && c != '\n');
fprintf(stderr, "\n"); fprintf(stderr, "\n");
break; break;
} }
} while (!feof(f)); } while (!feof(f));
fclose(f); fclose(f);
}
} }
}
#endif #endif
} }
} }
}
/* Is this a SyntaxError? Handle those specially. */ /* Is this a SyntaxError? Handle those specially. */
if (krk_isInstanceOf(krk_currentThread.currentException, vm.exceptions->syntaxError)) { if (krk_isInstanceOf(exception, vm.exceptions->syntaxError)) {
KrkValue result = krk_callDirect(krk_getType(krk_currentThread.currentException)->_tostr, 1); KrkValue result = krk_callDirect(krk_getType(exception)->_tostr, 1);
fprintf(stderr, "%s\n", AS_CSTRING(result)); fprintf(stderr, "%s\n", AS_CSTRING(result));
return; return;
} }
/* Clear the exception state while printing the exception. */ /* Clear the exception state while printing the exception. */
krk_currentThread.flags &= ~(KRK_THREAD_HAS_EXCEPTION); krk_currentThread.flags &= ~(KRK_THREAD_HAS_EXCEPTION);
fprintf(stderr, "%s", krk_typeName(krk_currentThread.currentException)); fprintf(stderr, "%s", krk_typeName(exception));
KrkValue result = krk_callDirect(krk_getType(krk_currentThread.currentException)->_tostr, 1); KrkValue result = krk_callDirect(krk_getType(exception)->_tostr, 1);
if (!IS_STRING(result)) { if (!IS_STRING(result)) {
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} else { } else {
fprintf(stderr, ": %s\n", AS_CSTRING(result)); fprintf(stderr, ": %s\n", AS_CSTRING(result));
} }
/* Turn the exception flag back on */ /* Turn the exception flag back on */
krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION; 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. */ } /* 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 * Raise an exception. Creates an exception object of the requested type
* and formats a message string to attach to it. Exception classes are * 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. */ /* Allocate an exception object of the requested type. */
KrkInstance * exceptionObject = krk_newInstance(type); KrkInstance * exceptionObject = krk_newInstance(type);
krk_push(OBJECT_VAL(exceptionObject)); krk_push(OBJECT_VAL(exceptionObject));
krk_push(OBJECT_VAL(S("arg"))); krk_attachNamedValue(&exceptionObject->fields, "arg", OBJECT_VAL(krk_copyString(buf, len)));
krk_push(OBJECT_VAL(krk_copyString(buf, len))); krk_attachNamedValue(&exceptionObject->fields, "__cause__", NONE_VAL());
/* Attach its argument */ krk_attachNamedValue(&exceptionObject->fields, "__context__", NONE_VAL());
krk_tableSet(&exceptionObject->fields, krk_peek(1), krk_peek(0));
krk_pop();
krk_pop();
krk_pop(); krk_pop();
/* Set the current exception to be picked up by handleException */ /* 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_INPLACE_POW: INPLACE_BINARY_OP(pow)
case OP_RAISE: { case OP_RAISE: {
if (IS_CLASS(krk_peek(0))) { raiseException(krk_peek(0), NONE_VAL());
krk_currentThread.currentException = krk_callStack(0); goto _finishException;
} else { }
krk_currentThread.currentException = krk_pop(); case OP_RAISE_FROM: {
} raiseException(krk_peek(1), krk_peek(0));
attachTraceback();
krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION;
goto _finishException; goto _finishException;
} }
case OP_CLOSE_UPVALUE: case OP_CLOSE_UPVALUE:
@ -3275,6 +3323,9 @@ _finishReturn: (void)0;
if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) {
_finishException: _finishException:
if (!handleException()) { if (!handleException()) {
if (!IS_NONE(krk_currentThread.stackTop[-2])) {
attachInnerException(krk_currentThread.stackTop[-2]);
}
frame = &krk_currentThread.frames[krk_currentThread.frameCount - 1]; frame = &krk_currentThread.frames[krk_currentThread.frameCount - 1];
frame->ip = frame->closure->function->chunk.code + AS_HANDLER_TARGET(krk_peek(0)); frame->ip = frame->closure->function->chunk.code + AS_HANDLER_TARGET(krk_peek(0));
/* Stick the exception into the exception slot */ /* Stick the exception into the exception slot */

View File

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

View File

@ -0,0 +1,4 @@
IndexError('c') ValueError('b') TypeError('a')
TypeError('a') ValueError('b') None
IndexError() ValueError() TypeError()
TypeError() ValueError() None