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.
This commit is contained in:
Denys Smirnov
2023-09-23 13:24:05 +03:00
committed by GitHub
parent 79b7420ee1
commit f5b5481794
3 changed files with 62 additions and 2 deletions

20
_test/panic0.go Normal file
View File

@@ -0,0 +1,20 @@
package main
func main() {
foo()
}
func foo() {
bar()
}
func bar() {
baz()
}
func baz() {
panic("stop!")
}
// Error:
// stop!

View File

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

View File

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