From ee83eaf0740a8ba506aed412f9ac8b7030648a95 Mon Sep 17 00:00:00 2001
From: 6h057 <15034695+omarsy@users.noreply.github.com>
Date: Tue, 30 Jul 2024 19:12:47 +0200
Subject: [PATCH] fix(gnovm): add missing length check in DeclStmt and
AssignStmt (#2206)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Closes #2137
Contributors' checklist...
- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---------
Co-authored-by: Miloš Živković
---
gnovm/pkg/gnolang/preprocess.go | 23 +++++++++---
gnovm/pkg/gnolang/type_check.go | 64 +++++++++++++++++++++++++++++++++
gnovm/tests/files/assign24.gno | 8 +++++
gnovm/tests/files/assign25.gno | 14 ++++++++
gnovm/tests/files/assign25b.gno | 14 ++++++++
gnovm/tests/files/assign26.gno | 9 +++++
gnovm/tests/files/assign27.gno | 9 +++++
gnovm/tests/files/assign28.gno | 11 ++++++
gnovm/tests/files/var18.gno | 4 +--
gnovm/tests/files/var19.gno | 11 ++++++
gnovm/tests/files/var20.gno | 12 +++++++
gnovm/tests/files/var21.gno | 14 ++++++++
gnovm/tests/files/var22.gno | 14 ++++++++
gnovm/tests/files/var22b.gno | 14 ++++++++
gnovm/tests/files/var23.gno | 9 +++++
gnovm/tests/files/var24.gno | 9 +++++
gnovm/tests/files/var25.gno | 9 +++++
gnovm/tests/files/var26.gno | 11 ++++++
18 files changed, 252 insertions(+), 7 deletions(-)
create mode 100644 gnovm/tests/files/assign24.gno
create mode 100644 gnovm/tests/files/assign25.gno
create mode 100644 gnovm/tests/files/assign25b.gno
create mode 100644 gnovm/tests/files/assign26.gno
create mode 100644 gnovm/tests/files/assign27.gno
create mode 100644 gnovm/tests/files/assign28.gno
create mode 100644 gnovm/tests/files/var19.gno
create mode 100644 gnovm/tests/files/var20.gno
create mode 100644 gnovm/tests/files/var21.gno
create mode 100644 gnovm/tests/files/var22.gno
create mode 100644 gnovm/tests/files/var22b.gno
create mode 100644 gnovm/tests/files/var23.gno
create mode 100644 gnovm/tests/files/var24.gno
create mode 100644 gnovm/tests/files/var25.gno
create mode 100644 gnovm/tests/files/var26.gno
diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go
index d21e9bf0efd..5a710723b86 100644
--- a/gnovm/pkg/gnolang/preprocess.go
+++ b/gnovm/pkg/gnolang/preprocess.go
@@ -427,6 +427,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
switch n := n.(type) {
// TRANS_ENTER -----------------------
case *AssignStmt:
+ checkValDefineMismatch(n)
+
if n.Op == DEFINE {
for _, lx := range n.Lhs {
ln := lx.(*NameExpr).Name
@@ -445,7 +447,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
} else {
// nothing defined.
}
-
// TRANS_ENTER -----------------------
case *ImportDecl, *ValueDecl, *TypeDecl, *FuncDecl:
// NOTE func decl usually must happen with a
@@ -457,8 +458,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
// skip declarations already predefined
// (e.g. through recursion for a dependent)
} else {
+ d := n.(Decl)
+ if cd, ok := d.(*ValueDecl); ok {
+ checkValDefineMismatch(cd)
+ }
// recursively predefine dependencies.
- d2, ppd := predefineNow(store, last, n.(Decl))
+ d2, ppd := predefineNow(store, last, d)
if ppd {
return d2, TRANS_SKIP
} else {
@@ -2162,8 +2167,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
// special case if `var a, b, c T? = f()` form.
cx := n.Values[0].(*CallExpr)
tt := evalStaticTypeOfRaw(store, last, cx).(*tupleType)
- if len(tt.Elts) != numNames {
- panic("should not happen")
+ if rLen := len(tt.Elts); rLen != numNames {
+ panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %s returns %d value(s)", numNames, cx.Func.String(), rLen))
}
if n.Type != nil {
// only a single type can be specified.
@@ -2182,8 +2187,16 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
}
}
} else if len(n.Values) != 0 && numNames != len(n.Values) {
- panic("should not happen")
+ panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, len(n.Values)))
} else { // general case
+ for _, v := range n.Values {
+ if cx, ok := v.(*CallExpr); ok {
+ tt, ok := evalStaticTypeOfRaw(store, last, cx).(*tupleType)
+ if ok && len(tt.Elts) != 1 {
+ panic(fmt.Sprintf("multiple-value %s (value of type %s) in single-value context", cx.Func.String(), tt.Elts))
+ }
+ }
+ }
// evaluate types and convert consts.
if n.Type != nil {
// only a single type can be specified.
diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go
index 3f25667f353..31025fef152 100644
--- a/gnovm/pkg/gnolang/type_check.go
+++ b/gnovm/pkg/gnolang/type_check.go
@@ -215,6 +215,70 @@ func assertAssignableTo(xt, dt Type, autoNative bool) {
}
}
+// checkValDefineMismatch checks for mismatch between the number of variables and values in a ValueDecl or AssignStmt.
+func checkValDefineMismatch(n Node) {
+ var (
+ valueDecl *ValueDecl
+ assign *AssignStmt
+ values []Expr
+ numNames int
+ numValues int
+ )
+
+ switch x := n.(type) {
+ case *ValueDecl:
+ valueDecl = x
+ numNames = len(valueDecl.NameExprs)
+ numValues = len(valueDecl.Values)
+ values = valueDecl.Values
+ case *AssignStmt:
+ if x.Op != DEFINE {
+ return
+ }
+
+ assign = x
+ numNames = len(assign.Lhs)
+ numValues = len(assign.Rhs)
+ values = assign.Rhs
+ default:
+ panic(fmt.Sprintf("unexpected node type %T", n))
+ }
+
+ if numValues == 0 || numValues == numNames {
+ return
+ }
+
+ // Special case for single value.
+ // If the value is a call expression, type assertion, or index expression,
+ // it can be assigned to multiple variables.
+ if numValues == 1 {
+ switch values[0].(type) {
+ case *CallExpr:
+ return
+ case *TypeAssertExpr:
+ if numNames != 2 {
+ panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues))
+ }
+ return
+ case *IndexExpr:
+ if numNames != 2 {
+ panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues))
+ }
+ return
+ }
+ }
+
+ if valueDecl != nil {
+ if numNames > numValues {
+ panic(fmt.Sprintf("missing init expr for %s", valueDecl.NameExprs[numValues].String()))
+ }
+
+ panic(fmt.Sprintf("extra init expr %s", values[numNames].String()))
+ }
+
+ panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues))
+}
+
// Assert that xt can be assigned as dt (dest type).
// If autoNative is true, a broad range of xt can match against
// a target native dt type, if and only if dt is a native type.
diff --git a/gnovm/tests/files/assign24.gno b/gnovm/tests/files/assign24.gno
new file mode 100644
index 00000000000..408258def92
--- /dev/null
+++ b/gnovm/tests/files/assign24.gno
@@ -0,0 +1,8 @@
+package main
+
+func main() {
+ a, b := 1
+}
+
+// Error:
+// main/files/assign24.gno:4:2: assignment mismatch: 2 variable(s) but 1 value(s)
diff --git a/gnovm/tests/files/assign25.gno b/gnovm/tests/files/assign25.gno
new file mode 100644
index 00000000000..b945afc3b1f
--- /dev/null
+++ b/gnovm/tests/files/assign25.gno
@@ -0,0 +1,14 @@
+package main
+
+func foo() (int, bool) {
+ return 1, true
+}
+
+func main() {
+ a, b, c := 2, foo()
+
+ println(a, b, c)
+}
+
+// Error:
+// main/files/assign25.gno:8:2: assignment mismatch: 3 variable(s) but 2 value(s)
diff --git a/gnovm/tests/files/assign25b.gno b/gnovm/tests/files/assign25b.gno
new file mode 100644
index 00000000000..886777324f5
--- /dev/null
+++ b/gnovm/tests/files/assign25b.gno
@@ -0,0 +1,14 @@
+package main
+
+func foo() (int, bool) {
+ return 1, true
+}
+
+func main() {
+ a, b, c := 2, 3, 4, foo()
+
+ println(a, b, c)
+}
+
+// Error:
+// main/files/assign25b.gno:8:2: assignment mismatch: 3 variable(s) but 4 value(s)
diff --git a/gnovm/tests/files/assign26.gno b/gnovm/tests/files/assign26.gno
new file mode 100644
index 00000000000..f974ae0b174
--- /dev/null
+++ b/gnovm/tests/files/assign26.gno
@@ -0,0 +1,9 @@
+package main
+
+func main() {
+ var i interface{} = 1
+ a, b, c := i.(int)
+}
+
+// Error:
+// main/files/assign26.gno:5:2: assignment mismatch: 3 variable(s) but 1 value(s)
diff --git a/gnovm/tests/files/assign27.gno b/gnovm/tests/files/assign27.gno
new file mode 100644
index 00000000000..14fc8bf4e37
--- /dev/null
+++ b/gnovm/tests/files/assign27.gno
@@ -0,0 +1,9 @@
+package main
+
+func main() {
+ s := []string{"1", "2"}
+ a, b, c := s[0]
+}
+
+// Error:
+// main/files/assign27.gno:5:2: assignment mismatch: 3 variable(s) but 1 value(s)
diff --git a/gnovm/tests/files/assign28.gno b/gnovm/tests/files/assign28.gno
new file mode 100644
index 00000000000..a0afe4f0e36
--- /dev/null
+++ b/gnovm/tests/files/assign28.gno
@@ -0,0 +1,11 @@
+package main
+
+import "fmt"
+
+func main() {
+ a, c := 1, 2, 3
+ fmt.Println(a, c)
+}
+
+// Error:
+// main/files/assign28.gno:6:2: assignment mismatch: 2 variable(s) but 3 value(s)
diff --git a/gnovm/tests/files/var18.gno b/gnovm/tests/files/var18.gno
index 771ddcdffb5..f01d3871d39 100644
--- a/gnovm/tests/files/var18.gno
+++ b/gnovm/tests/files/var18.gno
@@ -1,8 +1,8 @@
package main
func main() {
- a, b, c := 1, 2
+ var a, b, c = 1, 2
}
// Error:
-// main/files/var18.gno:4:2: assignment mismatch: 3 variables but 2 values
+// main/files/var18.gno:4:6: missing init expr for c
diff --git a/gnovm/tests/files/var19.gno b/gnovm/tests/files/var19.gno
new file mode 100644
index 00000000000..cbdce802e0a
--- /dev/null
+++ b/gnovm/tests/files/var19.gno
@@ -0,0 +1,11 @@
+package main
+
+func main() {
+ var a, b, c = 1, a+1
+ println(a)
+ println(b)
+ println(c)
+}
+
+// Error:
+// main/files/var19.gno:4:6: missing init expr for c
diff --git a/gnovm/tests/files/var20.gno b/gnovm/tests/files/var20.gno
new file mode 100644
index 00000000000..e2455cbaed8
--- /dev/null
+++ b/gnovm/tests/files/var20.gno
@@ -0,0 +1,12 @@
+package main
+
+func r() int {
+ return 1
+}
+
+func main() {
+ var a, b, c = r()
+}
+
+// Error:
+// main/files/var20.gno:8:6: assignment mismatch: 3 variable(s) but r returns 1 value(s)
diff --git a/gnovm/tests/files/var21.gno b/gnovm/tests/files/var21.gno
new file mode 100644
index 00000000000..b593984aa87
--- /dev/null
+++ b/gnovm/tests/files/var21.gno
@@ -0,0 +1,14 @@
+package main
+
+func foo() (int, bool) {
+ return 1, true
+}
+
+func main() {
+ var a, b = 2, foo()
+
+ println(a, b)
+}
+
+// Error:
+// main/files/var21.gno:8:6: multiple-value foo (value of type [int bool]) in single-value context
diff --git a/gnovm/tests/files/var22.gno b/gnovm/tests/files/var22.gno
new file mode 100644
index 00000000000..3f85f0f156d
--- /dev/null
+++ b/gnovm/tests/files/var22.gno
@@ -0,0 +1,14 @@
+package main
+
+func foo() (int, bool) {
+ return 1, true
+}
+
+func main() {
+ var a, b, c = 2, foo()
+
+ println(a, b, c)
+}
+
+// Error:
+// main/files/var22.gno:8:6: missing init expr for c
diff --git a/gnovm/tests/files/var22b.gno b/gnovm/tests/files/var22b.gno
new file mode 100644
index 00000000000..22a52fcc811
--- /dev/null
+++ b/gnovm/tests/files/var22b.gno
@@ -0,0 +1,14 @@
+package main
+
+func foo() (int, bool) {
+ return 1, true
+}
+
+func main() {
+ var a, b, c = 2, 3, 4, foo()
+
+ println(a, b, c)
+}
+
+// Error:
+// main/files/var22b.gno:8:6: extra init expr foo()
diff --git a/gnovm/tests/files/var23.gno b/gnovm/tests/files/var23.gno
new file mode 100644
index 00000000000..b9f98311411
--- /dev/null
+++ b/gnovm/tests/files/var23.gno
@@ -0,0 +1,9 @@
+package main
+
+var a, b, c = 1, 2
+
+func main() {
+}
+
+// Error:
+// main/files/var23.gno:3:5: assignment mismatch: 3 variable(s) but 2 value(s)
diff --git a/gnovm/tests/files/var24.gno b/gnovm/tests/files/var24.gno
new file mode 100644
index 00000000000..ddfa82a1e4e
--- /dev/null
+++ b/gnovm/tests/files/var24.gno
@@ -0,0 +1,9 @@
+package main
+
+func main() {
+ var i interface{} = 1
+ var a, b, c = i.(int)
+}
+
+// Error:
+// main/files/var24.gno:5:6: assignment mismatch: 3 variable(s) but 1 value(s)
diff --git a/gnovm/tests/files/var25.gno b/gnovm/tests/files/var25.gno
new file mode 100644
index 00000000000..999979dcf71
--- /dev/null
+++ b/gnovm/tests/files/var25.gno
@@ -0,0 +1,9 @@
+package main
+
+func main() {
+ s := []string{"1", "2"}
+ var a, b, c = s[0]
+}
+
+// Error:
+// main/files/var25.gno:5:6: assignment mismatch: 3 variable(s) but 1 value(s)
diff --git a/gnovm/tests/files/var26.gno b/gnovm/tests/files/var26.gno
new file mode 100644
index 00000000000..71b6f3a4eb0
--- /dev/null
+++ b/gnovm/tests/files/var26.gno
@@ -0,0 +1,11 @@
+package main
+
+import "fmt"
+
+func main() {
+ var a, c = 1, 2, 3
+ fmt.Println(a, c)
+}
+
+// Error:
+// main/files/var26.gno:6:6: extra init expr 3