compiler: support using return in some branches

When `return` is used in one codepath, but not the other,
we do not clean altstack properly. This commit fixes it.
This commit is contained in:
Evgenii Stratonikov 2020-05-06 15:39:25 +03:00
parent 770cff8b91
commit 156a2eddc5
3 changed files with 37 additions and 12 deletions

View file

@ -133,16 +133,13 @@ func (f funcUsage) funcUsed(name string) bool {
return ok return ok
} }
// hasReturnStmt looks if the given FuncDecl has a return statement. // lastStmtIsReturn checks if last statement of the declaration was return statement..
func hasReturnStmt(decl ast.Node) (b bool) { func lastStmtIsReturn(decl *ast.FuncDecl) (b bool) {
ast.Inspect(decl, func(node ast.Node) bool { if l := len(decl.Body.List); l != 0 {
if _, ok := node.(*ast.ReturnStmt); ok { _, ok := decl.Body.List[l-1].(*ast.ReturnStmt)
b = true return ok
return false
} }
return true return false
})
return
} }
func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage { func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {

View file

@ -263,8 +263,10 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
ast.Walk(c, decl.Body) ast.Walk(c, decl.Body)
// If this function returns the void (no return stmt) we will cleanup its junk on the stack. // If we have reached the end of the function without encountering `return` statement,
if !hasReturnStmt(decl) { // we should clean alt.stack manually.
// This can be the case with void and named-return functions.
if !lastStmtIsReturn(decl) {
c.saveSequencePoint(decl.Body) 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)

View file

@ -1,6 +1,7 @@
package compiler_test package compiler_test
import ( import (
"fmt"
"math/big" "math/big"
"testing" "testing"
) )
@ -132,3 +133,28 @@ func TestFunctionWithVoidReturn(t *testing.T) {
eval(t, src, big.NewInt(6)) eval(t, src, big.NewInt(6))
}) })
} }
func TestFunctionWithVoidReturnBranch(t *testing.T) {
src := `
package testcase
func Main() int {
x := %t
f(x)
return 2
}
func f(x bool) {
if x {
return
}
}
`
t.Run("ReturnBranch", func(t *testing.T) {
src := fmt.Sprintf(src, true)
eval(t, src, big.NewInt(2))
})
t.Run("NoReturn", func(t *testing.T) {
src := fmt.Sprintf(src, false)
eval(t, src, big.NewInt(2))
})
}