From f5b5481794b14468f527c33427ce79e64e9217f0 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Sat, 23 Sep 2023 13:24:05 +0300 Subject: [PATCH] interp: Record function names in panic Currently, yaegi only records source positions when writing panic trace to stderr. This change adds function names to the panic output. Unfortunately, yaegi walks the trace in reverse order, comparing to Go runtime. This can be improved in the future. --- _test/panic0.go | 20 ++++++++++++++++++++ interp/interp_consistent_test.go | 21 ++++++++++++++++++++- interp/run.go | 23 ++++++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 _test/panic0.go diff --git a/_test/panic0.go b/_test/panic0.go new file mode 100644 index 00000000..54fa0a3b --- /dev/null +++ b/_test/panic0.go @@ -0,0 +1,20 @@ +package main + +func main() { + foo() +} + +func foo() { + bar() +} + +func bar() { + baz() +} + +func baz() { + panic("stop!") +} + +// Error: +// stop! diff --git a/interp/interp_consistent_test.go b/interp/interp_consistent_test.go index 3fa8c9b9..b7d4817d 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 b4e9803f..b4225ed0 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)