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:
parent
24fef35ead
commit
5d3da26e1e
3 changed files with 52 additions and 1 deletions
|
@ -52,6 +52,11 @@ type codegen struct {
|
||||||
// A label to be used in the next statement.
|
// A label to be used in the next statement.
|
||||||
nextLabel string
|
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.
|
// Label table for recording jump destinations.
|
||||||
l []int
|
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 this function returns the void (no return stmt) we will cleanup its junk on the stack.
|
||||||
if !hasReturnStmt(decl) {
|
if !hasReturnStmt(decl) {
|
||||||
|
c.saveSequencePoint(decl.Body)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
|
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||||
|
@ -303,7 +309,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
case *ast.AssignStmt:
|
case *ast.AssignStmt:
|
||||||
multiRet := len(n.Rhs) != len(n.Lhs)
|
multiRet := len(n.Rhs) != len(n.Lhs)
|
||||||
|
c.saveSequencePoint(n)
|
||||||
for i := 0; i < len(n.Lhs); i++ {
|
for i := 0; i < len(n.Lhs); i++ {
|
||||||
switch t := n.Lhs[i].(type) {
|
switch t := n.Lhs[i].(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
|
@ -407,6 +413,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
ast.Walk(c, n.Results[i])
|
ast.Walk(c, n.Results[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.saveSequencePoint(n)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
|
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack.
|
emit.Opcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack.
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||||
|
@ -648,6 +655,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.saveSequencePoint(n)
|
||||||
|
|
||||||
args := transformArgs(n.Fun, n.Args)
|
args := transformArgs(n.Fun, n.Args)
|
||||||
|
|
||||||
// Handle the arguments
|
// Handle the arguments
|
||||||
|
@ -1298,6 +1307,8 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
|
||||||
funcs: map[string]*funcScope{},
|
funcs: map[string]*funcScope{},
|
||||||
labels: map[labelWithType]uint16{},
|
labels: map[labelWithType]uint16{},
|
||||||
typeInfo: &pkg.Info,
|
typeInfo: &pkg.Info,
|
||||||
|
|
||||||
|
sequencePoints: make(map[string][]DebugSeqPoint),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,19 @@ type DebugParam struct {
|
||||||
Type string
|
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 {
|
func (c *codegen) emitDebugInfo() *DebugInfo {
|
||||||
d := &DebugInfo{
|
d := &DebugInfo{
|
||||||
EntryPoint: mainIdent,
|
EntryPoint: mainIdent,
|
||||||
|
@ -108,6 +121,7 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu
|
||||||
Range: scope.rng,
|
Range: scope.rng,
|
||||||
Parameters: params,
|
Parameters: params,
|
||||||
ReturnType: c.scReturnTypeFromScope(scope),
|
ReturnType: c.scReturnTypeFromScope(scope),
|
||||||
|
SeqPoints: c.sequencePoints[name],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
func TestDebugInfo_MarshalJSON(t *testing.T) {
|
||||||
d := &DebugInfo{
|
d := &DebugInfo{
|
||||||
EntryPoint: "main",
|
EntryPoint: "main",
|
||||||
|
|
Loading…
Reference in a new issue