diff --git a/_test/panic0.go b/_test/panic0.go new file mode 100644 index 000000000..b8639652c --- /dev/null +++ b/_test/panic0.go @@ -0,0 +1,23 @@ +package main + +func main() { + foo() +} + +func foo() { + bar() +} + +func bar() { + baz() +} + +func baz() { + panic("stop!") +} + +// Error: +// ../_test/panic0.go:16:2: panic: main.baz(...) +// ../_test/panic0.go:12:2: panic: main.bar(...) +// ../_test/panic0.go:8:2: panic: main.foo(...) +// ../_test/panic0.go:4:2: panic: main.main(...) diff --git a/interp/interp_consistent_test.go b/interp/interp_consistent_test.go index 3fa8c9b9e..b7d4817d5 100644 --- a/interp/interp_consistent_test.go +++ b/interp/interp_consistent_test.go @@ -90,6 +90,7 @@ func TestInterpConsistencyBuild(t *testing.T) { file.Name() == "redeclaration-global5.go" || // expect error file.Name() == "redeclaration-global6.go" || // expect error file.Name() == "redeclaration-global7.go" || // expect error + file.Name() == "panic0.go" || // expect error file.Name() == "pkgname0.go" || // has deps file.Name() == "pkgname1.go" || // expect error file.Name() == "pkgname2.go" || // has deps @@ -188,6 +189,7 @@ func TestInterpErrorConsistency(t *testing.T) { testCases := []struct { fileName string expectedInterp string + expectedStderr string expectedExec string }{ { @@ -285,6 +287,16 @@ func TestInterpErrorConsistency(t *testing.T) { expectedInterp: "37:2: duplicate case Bir in type switch", expectedExec: "37:7: duplicate case Bir in type switch", }, + { + fileName: "panic0.go", + expectedInterp: "stop!", + expectedStderr: ` +../_test/panic0.go:16:2: panic: main.baz(...) +../_test/panic0.go:12:2: panic: main.bar(...) +../_test/panic0.go:8:2: panic: main.foo(...) +../_test/panic0.go:4:2: panic: main.main(...) +`, + }, } for _, test := range testCases { @@ -295,7 +307,8 @@ func TestInterpErrorConsistency(t *testing.T) { filePath := filepath.Join("..", "_test", test.fileName) - i := interp.New(interp.Options{GoPath: build.Default.GOPATH}) + var stderr bytes.Buffer + i := interp.New(interp.Options{GoPath: build.Default.GOPATH, Stderr: &stderr}) if err := i.Use(stdlib.Symbols); err != nil { t.Fatal(err) } @@ -308,6 +321,12 @@ func TestInterpErrorConsistency(t *testing.T) { if !strings.Contains(errEval.Error(), test.expectedInterp) { t.Errorf("got %q, want: %q", errEval.Error(), test.expectedInterp) } + if test.expectedStderr != "" { + exp, got := strings.TrimSpace(test.expectedStderr), strings.TrimSpace(stderr.String()) + if exp != got { + t.Errorf("got %q, want: %q", got, exp) + } + } cmd := exec.Command("go", "run", filePath) outRun, errExec := cmd.CombinedOutput() diff --git a/interp/run.go b/interp/run.go index b4e9803f7..b4225ed0e 100644 --- a/interp/run.go +++ b/interp/run.go @@ -180,6 +180,27 @@ var errAbortHandler = errors.New("net/http: abort Handler") // Functions set to run during execution of CFG. +func panicFunc(s *scope) string { + if s == nil { + return "" + } + def := s.def + if def == nil { + return s.pkgID + } + switch def.kind { + case funcDecl: + if c := def.child[1]; c.kind == identExpr { + return s.pkgID + "." + c.ident + } + case funcLit: + if def.anc != nil { + return panicFunc(def.anc.scope) + ".func" + } + } + return s.pkgID +} + // runCfg executes a node AST by walking its CFG and running node builtin at each step. func runCfg(n *node, f *frame, funcNode, callNode *node) { var exec bltn @@ -199,7 +220,7 @@ func runCfg(n *node, f *frame, funcNode, callNode *node) { // suppress the logging here accordingly, to get a similar and consistent // behavior. if !ok || errorer.Error() != errAbortHandler.Error() { - fmt.Fprintln(n.interp.stderr, oNode.cfgErrorf("panic")) + fmt.Fprintln(n.interp.stderr, oNode.cfgErrorf("panic: %s(...)", panicFunc(oNode.scope))) } f.mutex.Unlock() panic(f.recovered)