Skip to content

Commit

Permalink
interp: Record function names in panic
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dennwc committed Sep 23, 2023
1 parent 79b7420 commit f5b5481
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 2 deletions.
20 changes: 20 additions & 0 deletions _test/panic0.go
@@ -0,0 +1,20 @@
package main

func main() {
foo()
}

func foo() {
bar()
}

func bar() {
baz()
}

func baz() {
panic("stop!")
}

// Error:
// stop!
21 changes: 20 additions & 1 deletion interp/interp_consistent_test.go
Expand Up @@ -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
Expand Down Expand Up @@ -188,6 +189,7 @@ func TestInterpErrorConsistency(t *testing.T) {
testCases := []struct {
fileName string
expectedInterp string
expectedStderr string
expectedExec string
}{
{
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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()
Expand Down
23 changes: 22 additions & 1 deletion interp/run.go
Expand Up @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit f5b5481

Please sign in to comment.