Skip to content

Commit

Permalink
fix(gnovm): add missing length check in DeclStmt and AssignStmt (#2206)
Browse files Browse the repository at this point in the history
Closes #2137 
<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] 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).
</details>

---------

Co-authored-by: Miloš Živković <milos@zmilos.com>
  • Loading branch information
omarsy and zivkovicmilos authored Jul 30, 2024
1 parent e844fc1 commit ee83eaf
Show file tree
Hide file tree
Showing 18 changed files with 252 additions and 7 deletions.
23 changes: 18 additions & 5 deletions gnovm/pkg/gnolang/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
64 changes: 64 additions & 0 deletions gnovm/pkg/gnolang/type_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions gnovm/tests/files/assign24.gno
Original file line number Diff line number Diff line change
@@ -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)
14 changes: 14 additions & 0 deletions gnovm/tests/files/assign25.gno
Original file line number Diff line number Diff line change
@@ -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)
14 changes: 14 additions & 0 deletions gnovm/tests/files/assign25b.gno
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions gnovm/tests/files/assign26.gno
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions gnovm/tests/files/assign27.gno
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 11 additions & 0 deletions gnovm/tests/files/assign28.gno
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions gnovm/tests/files/var18.gno
Original file line number Diff line number Diff line change
@@ -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<VPUverse(0)>
11 changes: 11 additions & 0 deletions gnovm/tests/files/var19.gno
Original file line number Diff line number Diff line change
@@ -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<VPUverse(0)>
12 changes: 12 additions & 0 deletions gnovm/tests/files/var20.gno
Original file line number Diff line number Diff line change
@@ -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<VPBlock(3,0)> returns 1 value(s)
14 changes: 14 additions & 0 deletions gnovm/tests/files/var21.gno
Original file line number Diff line number Diff line change
@@ -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<VPBlock(3,0)> (value of type [int bool]) in single-value context
14 changes: 14 additions & 0 deletions gnovm/tests/files/var22.gno
Original file line number Diff line number Diff line change
@@ -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<VPUverse(0)>
14 changes: 14 additions & 0 deletions gnovm/tests/files/var22b.gno
Original file line number Diff line number Diff line change
@@ -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<VPUverse(0)>()
9 changes: 9 additions & 0 deletions gnovm/tests/files/var23.gno
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions gnovm/tests/files/var24.gno
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions gnovm/tests/files/var25.gno
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 11 additions & 0 deletions gnovm/tests/files/var26.gno
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit ee83eaf

Please sign in to comment.