From 5856d132b6c693281c5daab1680c332e6a5cf646 Mon Sep 17 00:00:00 2001 From: Dylan Trotter Date: Mon, 3 Jul 2017 11:18:20 -0700 Subject: [PATCH] Use traceback.format_exc for top level exceptions (#344) This change makes it so that all Grumpy binaries import the traceback module and then updates grumpy.FormatException (now FormatExc) to use traceback.format_exc if the traceback module is present in SysModules. --- compiler/stmt_test.py | 4 ++-- runtime/core.go | 39 ++++++++++++++++++++++++++------------- runtime/core_test.go | 32 +++++++++++++++++--------------- runtime/module.go | 10 ++++------ runtime/weakref.go | 6 +----- tools/grumpc | 13 ++++++++++++- 6 files changed, 62 insertions(+), 42 deletions(-) diff --git a/compiler/stmt_test.py b/compiler/stmt_test.py index f9412615..967a4881 100644 --- a/compiler/stmt_test.py +++ b/compiler/stmt_test.py @@ -452,8 +452,8 @@ def testTryFinally(self): finally: print 'bar'""")) self.assertEqual(1, result[0]) - # Some platforms show "exit status 1" message so don't test strict equality. - self.assertIn('foo bar\nfoo bar\nException\n', result[1]) + self.assertIn('foo bar\nfoo bar\n', result[1]) + self.assertIn('Exception\n', result[1]) def testWhile(self): self.assertEqual((0, '2\n1\n'), _GrumpRun(textwrap.dedent("""\ diff --git a/runtime/core.go b/runtime/core.go index 966a2dc9..ca9a58bf 100644 --- a/runtime/core.go +++ b/runtime/core.go @@ -182,17 +182,34 @@ func FloorDiv(f *Frame, v, w *Object) (*Object, *BaseException) { return binaryOp(f, v, w, v.typ.slots.FloorDiv, v.typ.slots.RFloorDiv, w.typ.slots.RFloorDiv, "//") } -// FormatException returns a single-line exception string for the given -// exception object, e.g. "NameError: name 'x' is not defined\n". -func FormatException(f *Frame, e *BaseException) (string, *BaseException) { - s, raised := ToStr(f, e.ToObject()) +// FormatExc calls traceback.format_exc, falling back to the single line +// exception message if that fails, e.g. "NameError: name 'x' is not defined\n". +func FormatExc(f *Frame) (s string) { + exc, tb := f.ExcInfo() + defer func() { + if s == "" { + strResult, raised := ToStr(f, exc.ToObject()) + if raised == nil && strResult.Value() != "" { + s = fmt.Sprintf("%s: %s\n", exc.typ.Name(), strResult.Value()) + } else { + s = exc.typ.Name() + "\n" + } + } + f.RestoreExc(exc, tb) + }() + tbMod, raised := SysModules.GetItemString(f, "traceback") + if raised != nil || tbMod == nil { + return + } + formatExc, raised := GetAttr(f, tbMod, NewStr("format_exc"), nil) if raised != nil { - return "", raised + return } - if len(s.Value()) == 0 { - return e.typ.Name() + "\n", nil + result, raised := formatExc.Call(f, nil, nil) + if raised != nil || !result.isInstance(StrType) { + return } - return fmt.Sprintf("%s: %s\n", e.typ.Name(), s.Value()), nil + return toStrUnsafe(result).Value() } // GE returns the result of operation v >= w. @@ -783,11 +800,7 @@ func StartThread(callable *Object) { f := NewRootFrame() _, raised := callable.Call(f, nil, nil) if raised != nil { - s, raised := FormatException(f, raised) - if raised != nil { - s = raised.String() - } - Stderr.writeString(s) + Stderr.writeString(FormatExc(f)) } }() } diff --git a/runtime/core_test.go b/runtime/core_test.go index 72c55695..5a451f5d 100644 --- a/runtime/core_test.go +++ b/runtime/core_test.go @@ -327,23 +327,25 @@ func TestDelItem(t *testing.T) { } func TestFormatException(t *testing.T) { - f := NewRootFrame() - cases := []struct { - o *Object - want string - }{ - {mustNotRaise(ExceptionType.Call(f, nil, nil)), "Exception\n"}, - {mustNotRaise(AttributeErrorType.Call(f, wrapArgs(""), nil)), "AttributeError\n"}, - {mustNotRaise(TypeErrorType.Call(f, wrapArgs(123), nil)), "TypeError: 123\n"}, - {mustNotRaise(AttributeErrorType.Call(f, wrapArgs("hello", "there"), nil)), "AttributeError: ('hello', 'there')\n"}, + fun := wrapFuncForTest(func(f *Frame, t *Type, args ...*Object) (string, *BaseException) { + e, raised := t.Call(f, args, nil) + if raised != nil { + return "", raised + } + f.Raise(e, nil, nil) + s := FormatExc(f) + f.RestoreExc(nil, nil) + return s, nil + }) + cases := []invokeTestCase{ + {args: wrapArgs(ExceptionType), want: NewStr("Exception\n").ToObject()}, + {args: wrapArgs(AttributeErrorType, ""), want: NewStr("AttributeError\n").ToObject()}, + {args: wrapArgs(TypeErrorType, 123), want: NewStr("TypeError: 123\n").ToObject()}, + {args: wrapArgs(AttributeErrorType, "hello", "there"), want: NewStr("AttributeError: ('hello', 'there')\n").ToObject()}, } for _, cas := range cases { - if !cas.o.isInstance(BaseExceptionType) { - t.Errorf("expected FormatException() input to be BaseException, got %s", cas.o.typ.Name()) - } else if got, raised := FormatException(f, toBaseExceptionUnsafe(cas.o)); raised != nil { - t.Errorf("FormatException(%v) raised %v, want nil", cas.o, raised) - } else if got != cas.want { - t.Errorf("FormatException(%v) = %q, want %q", cas.o, got, cas.want) + if err := runInvokeTestCase(fun, &cas); err != "" { + t.Error(err) } } } diff --git a/runtime/module.go b/runtime/module.go index 791118de..2a91e566 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -288,19 +288,17 @@ func RunMain(code *Code) int { m := newModule("__main__", code.filename) m.state = moduleStateInitializing f := NewRootFrame() + f.code = code + f.globals = m.Dict() if raised := SysModules.SetItemString(f, "__main__", m.ToObject()); raised != nil { Stderr.writeString(raised.String()) } - _, e := code.Eval(f, m.Dict(), nil, nil) + _, e := code.fn(f, nil) if e == nil { return 0 } if !e.isInstance(SystemExitType) { - s, raised := FormatException(f, e) - if raised != nil { - s = e.String() - } - Stderr.writeString(s) + Stderr.writeString(FormatExc(f)) return 1 } f.RestoreExc(nil, nil) diff --git a/runtime/weakref.go b/runtime/weakref.go index 036fda41..fc8f3e11 100644 --- a/runtime/weakref.go +++ b/runtime/weakref.go @@ -189,11 +189,7 @@ func weakRefFinalizeReferent(o *Object) { for i := numCallbacks - 1; i >= 0; i-- { f := NewRootFrame() if _, raised := callbacks[i].Call(f, Args{r.ToObject()}, nil); raised != nil { - s, raised := FormatException(f, raised) - if raised != nil { - s = raised.String() - } - Stderr.writeString(s) + Stderr.writeString(FormatExc(f)) } } } diff --git a/tools/grumpc b/tools/grumpc index ae440754..626f868e 100755 --- a/tools/grumpc +++ b/tools/grumpc @@ -69,6 +69,13 @@ def main(args): mod_block = block.ModuleBlock(importer, full_package_name, args.script, py_contents, future_features) mod_block.add_native_import('grumpy') + + # Import the traceback module in __main__ so that it's present in sys.modules. + traceback_package = None + has_main = args.modname == '__main__' + if has_main: + traceback_package = mod_block.add_import('traceback') + visitor = stmt.StatementVisitor(mod_block, future_node) # Indent so that the module body is aligned with the goto labels. with visitor.writer.indent_block(): @@ -79,7 +86,6 @@ def main(args): return 2 imports = dict(mod_block.imports) - has_main = args.modname == '__main__' if has_main: imports['os'] = block.Package('os') @@ -93,6 +99,11 @@ def main(args): writer.write('func initModule(πF *πg.Frame, ' '_ []*πg.Object) (*πg.Object, *πg.BaseException) {') with writer.indent_block(): + if traceback_package: + writer.write_tmpl(textwrap.dedent("""\ + if _, e := πg.ImportModule(πF, "traceback", []*πg.Code{$tb.Code}); e != nil { + \treturn nil, e + }"""), tb=traceback_package.alias) for s in sorted(mod_block.strings): writer.write('ß{} := πg.InternStr({})'.format(s, util.go_str(s))) writer.write_temp_decls(mod_block)