From 379dc51d4cfa530cf0cfa86fb023a33119ccc4d1 Mon Sep 17 00:00:00 2001 From: ltzmaxwell Date: Fri, 16 Aug 2024 17:49:26 +0800 Subject: [PATCH] fix(gnovm): correct comparison between different types (#1890) Fixes https://github.com/gnolang/gno/issues/1376, fixes https://github.com/gnolang/gno/issues/2386. --------- Co-authored-by: deelawn --- examples/gno.land/p/demo/uassert/uassert.gno | 97 +++++++++++++------ .../gno.land/p/demo/uassert/uassert_test.gno | 2 + gnovm/pkg/gnolang/op_binary.go | 4 +- gnovm/pkg/gnolang/op_expressions.go | 7 +- gnovm/pkg/gnolang/preprocess.go | 13 ++- gnovm/tests/files/types/cmp_databyte.gno | 12 +++ .../tests/files/types/cmp_iface_0_stdlibs.gno | 27 ++++++ gnovm/tests/files/types/cmp_iface_1.gno | 29 ++++++ gnovm/tests/files/types/cmp_iface_2.gno | 32 ++++++ .../tests/files/types/cmp_iface_3_stdlibs.gno | 27 ++++++ gnovm/tests/files/types/cmp_iface_4.gno | 24 +++++ .../tests/files/types/cmp_iface_5_stdlibs.gno | 27 ++++++ gnovm/tests/files/types/cmp_iface_6.gno | 31 ++++++ gnovm/tests/files/types/cmp_iface_7.gno | 24 +++++ gnovm/tests/files/types/cmp_primitive_0.gno | 24 +++++ gnovm/tests/files/types/cmp_primitive_1.gno | 22 +++++ gnovm/tests/files/types/cmp_primitive_2.gno | 15 +++ gnovm/tests/files/types/cmp_primitive_3.gno | 23 +++++ gnovm/tests/files/types/cmp_slice_0.gno | 20 ++++ gnovm/tests/files/types/cmp_slice_1.gno | 10 ++ gnovm/tests/files/types/cmp_slice_2.gno | 14 +++ gnovm/tests/files/types/cmp_slice_3.gno | 16 +++ gnovm/tests/files/types/cmp_slice_4.gno | 10 ++ gnovm/tests/files/types/cmp_typeswitch.gno | 18 ++++ gnovm/tests/files/types/cmp_typeswitch_a.gno | 18 ++++ gnovm/tests/files/types/iface_eql.gno | 9 ++ 26 files changed, 522 insertions(+), 33 deletions(-) create mode 100644 gnovm/tests/files/types/cmp_databyte.gno create mode 100644 gnovm/tests/files/types/cmp_iface_0_stdlibs.gno create mode 100644 gnovm/tests/files/types/cmp_iface_1.gno create mode 100644 gnovm/tests/files/types/cmp_iface_2.gno create mode 100644 gnovm/tests/files/types/cmp_iface_3_stdlibs.gno create mode 100644 gnovm/tests/files/types/cmp_iface_4.gno create mode 100644 gnovm/tests/files/types/cmp_iface_5_stdlibs.gno create mode 100644 gnovm/tests/files/types/cmp_iface_6.gno create mode 100644 gnovm/tests/files/types/cmp_iface_7.gno create mode 100644 gnovm/tests/files/types/cmp_primitive_0.gno create mode 100644 gnovm/tests/files/types/cmp_primitive_1.gno create mode 100644 gnovm/tests/files/types/cmp_primitive_2.gno create mode 100644 gnovm/tests/files/types/cmp_primitive_3.gno create mode 100644 gnovm/tests/files/types/cmp_slice_0.gno create mode 100644 gnovm/tests/files/types/cmp_slice_1.gno create mode 100644 gnovm/tests/files/types/cmp_slice_2.gno create mode 100644 gnovm/tests/files/types/cmp_slice_3.gno create mode 100644 gnovm/tests/files/types/cmp_slice_4.gno create mode 100644 gnovm/tests/files/types/cmp_typeswitch.gno create mode 100644 gnovm/tests/files/types/cmp_typeswitch_a.gno create mode 100644 gnovm/tests/files/types/iface_eql.gno diff --git a/examples/gno.land/p/demo/uassert/uassert.gno b/examples/gno.land/p/demo/uassert/uassert.gno index 7b3254ea505..2776e93dca9 100644 --- a/examples/gno.land/p/demo/uassert/uassert.gno +++ b/examples/gno.land/p/demo/uassert/uassert.gno @@ -379,46 +379,85 @@ func NotEqual(t TestingT, expected, actual interface{}, msgs ...string) bool { return true } +func isNumberEmpty(n interface{}) (isNumber, isEmpty bool) { + switch n := n.(type) { + // NOTE: the cases are split individually, so that n becomes of the + // asserted type; the type of '0' was correctly inferred and converted + // to the corresponding type, int, int8, etc. + case int: + return true, n == 0 + case int8: + return true, n == 0 + case int16: + return true, n == 0 + case int32: + return true, n == 0 + case int64: + return true, n == 0 + case uint: + return true, n == 0 + case uint8: + return true, n == 0 + case uint16: + return true, n == 0 + case uint32: + return true, n == 0 + case uint64: + return true, n == 0 + case float32: + return true, n == 0 + case float64: + return true, n == 0 + } + return false, false +} func Empty(t TestingT, obj interface{}, msgs ...string) bool { t.Helper() - switch val := obj.(type) { - case string: - if val != "" { - return fail(t, msgs, "uassert.Empty: not empty string: %s", val) - } - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: - if val != 0 { - return fail(t, msgs, "uassert.Empty: not empty number: %d", val) + + isNumber, isEmpty := isNumberEmpty(obj) + if isNumber { + if !isEmpty { + return fail(t, msgs, "uassert.Empty: not empty number: %d", obj) } - case std.Address: - var zeroAddr std.Address - if val != zeroAddr { - return fail(t, msgs, "uassert.Empty: not empty std.Address: %s", string(val)) + } else { + switch val := obj.(type) { + case string: + if val != "" { + return fail(t, msgs, "uassert.Empty: not empty string: %s", val) + } + case std.Address: + var zeroAddr std.Address + if val != zeroAddr { + return fail(t, msgs, "uassert.Empty: not empty std.Address: %s", string(val)) + } + default: + return fail(t, msgs, "uassert.Empty: unsupported type") } - default: - return fail(t, msgs, "uassert.Empty: unsupported type") } return true } func NotEmpty(t TestingT, obj interface{}, msgs ...string) bool { t.Helper() - switch val := obj.(type) { - case string: - if val == "" { - return fail(t, msgs, "uassert.NotEmpty: empty string: %s", val) - } - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: - if val == 0 { - return fail(t, msgs, "uassert.NotEmpty: empty number: %d", val) - } - case std.Address: - var zeroAddr std.Address - if val == zeroAddr { - return fail(t, msgs, "uassert.NotEmpty: empty std.Address: %s", string(val)) + isNumber, isEmpty := isNumberEmpty(obj) + if isNumber { + if isEmpty { + return fail(t, msgs, "uassert.NotEmpty: empty number: %d", obj) + } + } else { + switch val := obj.(type) { + case string: + if val == "" { + return fail(t, msgs, "uassert.NotEmpty: empty string: %s", val) + } + case std.Address: + var zeroAddr std.Address + if val == zeroAddr { + return fail(t, msgs, "uassert.NotEmpty: empty std.Address: %s", string(val)) + } + default: + return fail(t, msgs, "uassert.NotEmpty: unsupported type") } - default: - return fail(t, msgs, "uassert.NotEmpty: unsupported type") } return true } diff --git a/examples/gno.land/p/demo/uassert/uassert_test.gno b/examples/gno.land/p/demo/uassert/uassert_test.gno index 5ead848fd15..7862eca7305 100644 --- a/examples/gno.land/p/demo/uassert/uassert_test.gno +++ b/examples/gno.land/p/demo/uassert/uassert_test.gno @@ -218,6 +218,7 @@ func TestEmpty(t *testing.T) { {"", true}, {0, true}, {int(0), true}, + {int32(0), true}, {int64(0), true}, {uint(0), true}, // XXX: continue @@ -335,6 +336,7 @@ func TestNotEmpty(t *testing.T) { {"", false}, {0, false}, {int(0), false}, + {int32(0), false}, {int64(0), false}, {uint(0), false}, {std.Address(""), false}, diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 0d4581377c2..db3c1e5695c 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -79,7 +79,6 @@ func (m *Machine) doOpEql() { if debug { debugAssertEqualityTypes(lv.T, rv.T) } - // set result in lv. res := isEql(m.Store, lv, rv) lv.T = UntypedBoolType @@ -344,6 +343,9 @@ func isEql(store Store, lv, rv *TypedValue) bool { } else if rvu { return false } + if err := checkSame(lv.T, rv.T, ""); err != nil { + return false + } if lnt, ok := lv.T.(*NativeType); ok { if rnt, ok := rv.T.(*NativeType); ok { if lnt.Type != rnt.Type { diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index 36130ccbf4d..8ff0b5bd538 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -194,8 +194,13 @@ func (m *Machine) doOpRef() { nv.Value = rv2 } } + // when obtaining a pointer of the databyte type, use the ElemType of databyte + elt := xv.TV.T + if elt == DataByteType { + elt = xv.TV.V.(DataByteValue).ElemType + } m.PushValue(TypedValue{ - T: m.Alloc.NewType(&PointerType{Elt: xv.TV.T}), + T: m.Alloc.NewType(&PointerType{Elt: elt}), V: xv, }) } diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 34898e1e9fe..13ade4cfd99 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1506,6 +1506,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { checkOrConvertIntegerKind(store, last, n.High) checkOrConvertIntegerKind(store, last, n.Max) + // if n.X is untyped, convert to corresponding type + t := evalStaticTypeOf(store, last, n.X) + if isUntyped(t) { + dt := defaultTypeOf(t) + checkOrConvertType(store, last, &n.X, dt, false) + } + // TRANS_LEAVE ----------------------- case *TypeAssertExpr: if n.Type == nil { @@ -2797,8 +2804,10 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative // push t into bx.Left checkOrConvertType(store, last, &bx.Left, t, autoNative) return - // case EQL, LSS, GTR, NEQ, LEQ, GEQ: - // default: + case EQL, LSS, GTR, NEQ, LEQ, GEQ: + // do nothing + default: + // do nothing } } } diff --git a/gnovm/tests/files/types/cmp_databyte.gno b/gnovm/tests/files/types/cmp_databyte.gno new file mode 100644 index 00000000000..0583ed9a259 --- /dev/null +++ b/gnovm/tests/files/types/cmp_databyte.gno @@ -0,0 +1,12 @@ +package main + +import "bytes" + +func main() { + cmp := bytes.Compare([]byte("hello"), []byte("hey")) + println(cmp) + +} + +// Output: +// -1 diff --git a/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno new file mode 100644 index 00000000000..fb4ac682243 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(0) == errCmp { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_1.gno b/gnovm/tests/files/types/cmp_iface_1.gno new file mode 100644 index 00000000000..551b4acf0f1 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_1.gno @@ -0,0 +1,29 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// typed +var errCmp error = errors.New("XXXX") + +// special case: +// one is interface +func main() { + const e Error = Error(0) // typed const + if e == errCmp { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_2.gno b/gnovm/tests/files/types/cmp_iface_2.gno new file mode 100644 index 00000000000..5ad121f515b --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_2.gno @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "strconv" +) + +type E interface { + Error() string +} +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// special case: +// one is interface +func main() { + var e0 E + e0 = Error(0) + fmt.Printf("%T \n", e0) + if e0 == Error(0) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// int64 +// what the firetruck? diff --git a/gnovm/tests/files/types/cmp_iface_3_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_3_stdlibs.gno new file mode 100644 index 00000000000..9c4cb0e5ea0 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_3_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(1) == errCmp { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_4.gno b/gnovm/tests/files/types/cmp_iface_4.gno new file mode 100644 index 00000000000..a4ae0463291 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_4.gno @@ -0,0 +1,24 @@ +package main + +import ( + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var l interface{} + if l == Error(0) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno new file mode 100644 index 00000000000..e706c74808e --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if errCmp == int64(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/cmp_iface_5_stdlibs.gno:19:5: int64 does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/cmp_iface_6.gno b/gnovm/tests/files/types/cmp_iface_6.gno new file mode 100644 index 00000000000..6abc84992ea --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_6.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int64 + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 == e2) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/cmp_iface_7.gno b/gnovm/tests/files/types/cmp_iface_7.gno new file mode 100644 index 00000000000..a0ba3e8a0d3 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_7.gno @@ -0,0 +1,24 @@ +package main + +import "fmt" + +func check(v1, v2 interface{}) bool { + return v1 == v2 +} + +func main() { + type t1 int + type t2 int + v1 := t1(1) + v2 := t2(1) + v3 := t2(3) + + fmt.Println("v1, v2", v1, v2, check(v1, v2)) + fmt.Println("v1, v3", v1, v3, check(v1, v3)) + fmt.Println("v2, v3", v2, v3, check(v2, v3)) +} + +// Output: +// v1, v2 1 1 false +// v1, v3 1 3 false +// v2, v3 1 3 false diff --git a/gnovm/tests/files/types/cmp_primitive_0.gno b/gnovm/tests/files/types/cmp_primitive_0.gno new file mode 100644 index 00000000000..2c968b5158f --- /dev/null +++ b/gnovm/tests/files/types/cmp_primitive_0.gno @@ -0,0 +1,24 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// left is untyped const, right is typed const +// left is assignable to right +func main() { + if 1 == Error(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// what the firetruck? diff --git a/gnovm/tests/files/types/cmp_primitive_1.gno b/gnovm/tests/files/types/cmp_primitive_1.gno new file mode 100644 index 00000000000..2f2e1c94ef1 --- /dev/null +++ b/gnovm/tests/files/types/cmp_primitive_1.gno @@ -0,0 +1,22 @@ +package main + +type Error string + +func (e Error) Error() string { + return "error: " + string(e) +} + +// left is untyped const, right is typed const +// left is not assignable to right +// a) it's (untyped) bigint +// b) base type of right is string +func main() { + if 1 == Error(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/cmp_primitive_1.gno:14:5: cannot use untyped Bigint as StringKind diff --git a/gnovm/tests/files/types/cmp_primitive_2.gno b/gnovm/tests/files/types/cmp_primitive_2.gno new file mode 100644 index 00000000000..34c8a24cba2 --- /dev/null +++ b/gnovm/tests/files/types/cmp_primitive_2.gno @@ -0,0 +1,15 @@ +package main + +var a int8 + +func main() { + a = 1 + if 1 == a { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// what the firetruck? diff --git a/gnovm/tests/files/types/cmp_primitive_3.gno b/gnovm/tests/files/types/cmp_primitive_3.gno new file mode 100644 index 00000000000..c1692c8019c --- /dev/null +++ b/gnovm/tests/files/types/cmp_primitive_3.gno @@ -0,0 +1,23 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// left is typed const, right is untyped const +func main() { + if Error(1) == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// what the firetruck? diff --git a/gnovm/tests/files/types/cmp_slice_0.gno b/gnovm/tests/files/types/cmp_slice_0.gno new file mode 100644 index 00000000000..1db537a4d8c --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_0.gno @@ -0,0 +1,20 @@ +package main + +type S struct { + expected string +} + +// special case when RHS is result of slice operation, its type is determined in runtime +func main() { + s := S{ + expected: `hello`[:], // this is not converted + } + + a := "hello" + + println(a == s.expected) + +} + +// Output: +// true diff --git a/gnovm/tests/files/types/cmp_slice_1.gno b/gnovm/tests/files/types/cmp_slice_1.gno new file mode 100644 index 00000000000..76f2db8d7d8 --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_1.gno @@ -0,0 +1,10 @@ +package main + +func main() { + expected := `hello`[:] + a := "hello" + println(a == expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/cmp_slice_2.gno b/gnovm/tests/files/types/cmp_slice_2.gno new file mode 100644 index 00000000000..018d53fa81a --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_2.gno @@ -0,0 +1,14 @@ +package main + +type S struct { + expected string +} + +func main() { + println("hello" == S{ + expected: `hello`[:], + }.expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/cmp_slice_3.gno b/gnovm/tests/files/types/cmp_slice_3.gno new file mode 100644 index 00000000000..2795d618e91 --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_3.gno @@ -0,0 +1,16 @@ +package main + +type S struct { + expected string +} + +func main() { + var s = S{ + expected: `hello`[:], + } + a := "hello" + println(a == s.expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/cmp_slice_4.gno b/gnovm/tests/files/types/cmp_slice_4.gno new file mode 100644 index 00000000000..2bdd3191eff --- /dev/null +++ b/gnovm/tests/files/types/cmp_slice_4.gno @@ -0,0 +1,10 @@ +package main + +func main() { + expected := `hello`[:] + a := 1 + println(a == expected) // both typed +} + +// Error: +// main/files/types/cmp_slice_4.gno:6:10: cannot use int as string diff --git a/gnovm/tests/files/types/cmp_typeswitch.gno b/gnovm/tests/files/types/cmp_typeswitch.gno new file mode 100644 index 00000000000..721dfc9579a --- /dev/null +++ b/gnovm/tests/files/types/cmp_typeswitch.gno @@ -0,0 +1,18 @@ +package main + +func main() { + var l interface{} + l = int64(0) + + switch val := l.(type) { + case int64, int: + if val == 0 { + println("l is zero") + } else { + println("NOT zero") + } + } +} + +// Output: +// NOT zero diff --git a/gnovm/tests/files/types/cmp_typeswitch_a.gno b/gnovm/tests/files/types/cmp_typeswitch_a.gno new file mode 100644 index 00000000000..f70fcb3d3d6 --- /dev/null +++ b/gnovm/tests/files/types/cmp_typeswitch_a.gno @@ -0,0 +1,18 @@ +package main + +func main() { + var l interface{} + l = int(0) + + switch val := l.(type) { + case int64, int: + if val == 0 { + println("l is zero") + } else { + println("NOT zero") + } + } +} + +// Output: +// l is zero diff --git a/gnovm/tests/files/types/iface_eql.gno b/gnovm/tests/files/types/iface_eql.gno new file mode 100644 index 00000000000..97daa27c184 --- /dev/null +++ b/gnovm/tests/files/types/iface_eql.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var l interface{} = 1 + println(int8(1) == l) +} + +// Output: +// false