Merge pull request #700 from nspcc-dev/fix/forloop

compiler: do not short-circuit in complex conditions
This commit is contained in:
Roman Khimov 2020-02-28 20:14:26 +03:00 committed by GitHub
commit ff551d2015
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 64 additions and 2 deletions

View file

@ -505,15 +505,27 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.BinaryExpr: case *ast.BinaryExpr:
switch n.Op { switch n.Op {
case token.LAND: case token.LAND:
next := c.newLabel()
end := c.newLabel()
ast.Walk(c, n.X) ast.Walk(c, n.X)
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, uint16(len(c.l)-1)) emit.Jmp(c.prog.BinWriter, opcode.JMPIF, next)
emit.Opcode(c.prog.BinWriter, opcode.PUSHF)
emit.Jmp(c.prog.BinWriter, opcode.JMP, end)
c.setLabel(next)
ast.Walk(c, n.Y) ast.Walk(c, n.Y)
c.setLabel(end)
return nil return nil
case token.LOR: case token.LOR:
next := c.newLabel()
end := c.newLabel()
ast.Walk(c, n.X) ast.Walk(c, n.X)
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, uint16(len(c.l)-3)) emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, next)
emit.Opcode(c.prog.BinWriter, opcode.PUSHT)
emit.Jmp(c.prog.BinWriter, opcode.JMP, end)
c.setLabel(next)
ast.Walk(c, n.Y) ast.Walk(c, n.Y)
c.setLabel(end)
return nil return nil
default: default:

View file

@ -1,6 +1,7 @@
package compiler_test package compiler_test
import ( import (
"fmt"
"math/big" "math/big"
"strings" "strings"
"testing" "testing"
@ -704,3 +705,52 @@ func TestForLoopRangeCompilerError(t *testing.T) {
_, err := compiler.Compile(strings.NewReader(src)) _, err := compiler.Compile(strings.NewReader(src))
require.Error(t, err) require.Error(t, err)
} }
func TestForLoopComplexConditions(t *testing.T) {
src := `
package foo
func Main() int {
var ok bool
_ = ok
i := 0
j := 0
%s
for %s {
i++
j++
%s
}
return i
}`
tests := []struct {
Name string
Cond string
Assign string
Result int64
}{
{Cond: "i < 3 && j < 2", Result: 2},
{Cond: "i < 3 || j < 2", Result: 3},
{Cond: "i < 3 && (j < 2 || i < 1)", Result: 2},
{Cond: "i < 3 && (j < 2 && i < 1)", Result: 1},
{Cond: "(i < 1 || j < 3) && (i < 3 || j < 1)", Result: 3},
{Cond: "(i < 2 && j < 4) || (i < 4 && j < 2)", Result: 2},
{Cond: "ok", Assign: "ok = i < 3 && j < 2", Result: 2},
{Cond: "ok", Assign: "ok = i < 3 || j < 2", Result: 3},
{Cond: "ok", Assign: "ok = i < 3 && (j < 2 || i < 1)", Result: 2},
{Cond: "ok", Assign: "ok = i < 3 && (j < 2 && i < 1)", Result: 1},
{Cond: "ok", Assign: "ok = (i < 1 || j < 3) && (i < 3 || j < 1)", Result: 3},
{Cond: "ok", Assign: "ok = (i < 2 && j < 4) || (i < 4 && j < 2)", Result: 2},
}
for _, tc := range tests {
name := tc.Cond
if tc.Assign != "" {
name = tc.Assign
}
t.Run(name, func(t *testing.T) {
s := fmt.Sprintf(src, tc.Assign, tc.Cond, tc.Assign)
eval(t, s, big.NewInt(tc.Result))
})
}
}