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.
This commit is contained in:
Evgenii Stratonikov 2020-03-31 16:57:35 +03:00
parent 24fef35ead
commit 5d3da26e1e
3 changed files with 52 additions and 1 deletions

View file

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

View file

@ -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],
}
}

View file

@ -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",