From 5d3da26e1e4fa58474636669e2ae1fc211372104 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 31 Mar 2020 16:57:35 +0300 Subject: [PATCH] compiler: support sequence points in debug info Sequence points is a way to map a specific instruction offset from a compiled contract to a text span in a source file. This commit implements mapping only for `return` statements. Further improvements are straight-forward. --- pkg/compiler/codegen.go | 13 ++++++++++++- pkg/compiler/debug.go | 14 ++++++++++++++ pkg/compiler/debug_test.go | 26 ++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index ada41de1a..95752627e 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -52,6 +52,11 @@ type codegen struct { // A label to be used in the next statement. nextLabel string + // sequencePoints is mapping from method name to a slice + // containing info about mapping from opcode's offset + // to a text span in the source file. + sequencePoints map[string][]DebugSeqPoint + // Label table for recording jump destinations. l []int } @@ -258,6 +263,7 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { // If this function returns the void (no return stmt) we will cleanup its junk on the stack. if !hasReturnStmt(decl) { + c.saveSequencePoint(decl.Body) emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK) emit.Opcode(c.prog.BinWriter, opcode.DROP) emit.Opcode(c.prog.BinWriter, opcode.RET) @@ -303,7 +309,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.AssignStmt: multiRet := len(n.Rhs) != len(n.Lhs) - + c.saveSequencePoint(n) for i := 0; i < len(n.Lhs); i++ { switch t := n.Lhs[i].(type) { case *ast.Ident: @@ -407,6 +413,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, n.Results[i]) } + c.saveSequencePoint(n) emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK) emit.Opcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack. emit.Opcode(c.prog.BinWriter, opcode.RET) @@ -648,6 +655,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { return nil } + c.saveSequencePoint(n) + args := transformArgs(n.Fun, n.Args) // Handle the arguments @@ -1298,6 +1307,8 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen { funcs: map[string]*funcScope{}, labels: map[labelWithType]uint16{}, typeInfo: &pkg.Info, + + sequencePoints: make(map[string][]DebugSeqPoint), } } diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 178b47e3f..101da8dbc 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -76,6 +76,19 @@ type DebugParam struct { Type string } +func (c *codegen) saveSequencePoint(n ast.Node) { + fset := c.buildInfo.program.Fset + start := fset.Position(n.Pos()) + end := fset.Position(n.End()) + c.sequencePoints[c.scope.name] = append(c.sequencePoints[c.scope.name], DebugSeqPoint{ + Opcode: c.prog.Len(), + StartLine: start.Line, + StartCol: start.Offset, + EndLine: end.Line, + EndCol: end.Offset, + }) +} + func (c *codegen) emitDebugInfo() *DebugInfo { d := &DebugInfo{ EntryPoint: mainIdent, @@ -108,6 +121,7 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu Range: scope.rng, Parameters: params, ReturnType: c.scReturnTypeFromScope(scope), + SeqPoints: c.sequencePoints[name], } } diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 25b2b157f..07bd2a517 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -64,6 +64,32 @@ func methodStruct() struct{} { return struct{}{} } } } +func TestSequencePoints(t *testing.T) { + src := `package foo + func Main(op string) bool { + if op == "123" { + return true + } + return false + }` + + info, err := getBuildInfo(src) + require.NoError(t, err) + + pkg := info.program.Package(info.initialPackage) + c := newCodegen(info, pkg) + require.NoError(t, c.compile(info, pkg)) + + d := c.emitDebugInfo() + require.NotNil(t, d) + + // Main func has 2 return on 4-th and 6-th lines. + ps := d.Methods[0].SeqPoints + require.Equal(t, 2, len(ps)) + require.Equal(t, 4, ps[0].StartLine) + require.Equal(t, 6, ps[1].StartLine) +} + func TestDebugInfo_MarshalJSON(t *testing.T) { d := &DebugInfo{ EntryPoint: "main",