diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index aff9a8eca41..f2391480106 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1197,6 +1197,18 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { ift := evalStaticTypeOf(store, last, n.Func) switch cft := baseOf(ift).(type) { case *FuncType: + fn := extractFunctionName(n) + if fn == "len" { //nolint + at := evalStaticTypeOf(store, last, n.Args[0]) + validateLenArg(at) + } + if fn == "cap" { //nolint + at := evalStaticTypeOf(store, last, n.Args[0]) + validateCapArg(at) + } + if fn == "make" { + validateMakeArg(store, last, n) + } ft = cft case *TypeType: if len(n.Args) != 1 { @@ -1205,11 +1217,9 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { n.NumArgs = 1 ct := evalStaticType(store, last, n.Func) at := evalStaticTypeOf(store, last, n.Args[0]) - if _, isIface := baseOf(ct).(*InterfaceType); isIface { assertAssignableTo(n, at, ct, false) } - var constConverted bool switch arg0 := n.Args[0].(type) { case *ConstExpr: diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 8df92f0d0e0..e09634c622c 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -2,7 +2,9 @@ package gnolang import ( "fmt" + "math" "reflect" + "strconv" "github.com/gnolang/gno/tm2/pkg/errors" ) @@ -1142,3 +1144,162 @@ func isBlankIdentifier(x Expr) bool { } return false } + +// Check for invalid arguments in builtin functions +// len, cap, make +func validateMakeArg(store Store, last BlockNode, currExpr Expr) { + switch currExpr := currExpr.(type) { + case *CallExpr: + ift := evalStaticTypeOf(store, last, currExpr.Func) + switch baseOf(ift).(type) { + case *FuncType: + assertValidMakeArg(store, last, currExpr) + } + } +} + +// Validate argument for len() function +func validateLenArg(at Type) { + if at == nil { + panic("invalid argument: nil for built-in len") + } + + if pt, ok := at.(*PointerType); ok { + if pt.Elt == IntType { + panic(fmt.Sprintf("unexpected type for len(): %v", pt)) + } + } + + switch at := unwrapPointerType(baseOf(at)).(type) { + case PrimitiveType: + if at.Kind() != StringKind { + panic(fmt.Sprintf("unexpected type for len(): %v", baseOf(at).String())) + } + case *ArrayType, *SliceType, *MapType, *ChanType: + // Valid types for len() + default: + panic(fmt.Sprintf("unexpected type for len(): %v", baseOf(at).String())) + } +} + +// Validate argument for cap() function +func validateCapArg(at Type) { + if at == nil { + panic("invalid argument: nil for built-in cap") + } + + switch unwrapPointerType(baseOf(at)).(type) { + case *ArrayType, *SliceType, *ChanType: + // Valid types for cap() + default: + panic(fmt.Sprintf("unexpected type for cap(): %v", at.String())) + } +} + +// Validate arguments for make() function +func assertValidMakeArg(store Store, last BlockNode, currExpr *CallExpr) { + if len(currExpr.Args) < 1 || len(currExpr.Args) > 3 { + panic(fmt.Sprintf("invalid number of arguments for built-in make: expected 1 to 3, got %d", len(currExpr.Args))) + } + + // Validate first argument: must be a slice, map, or channel type + at := evalStaticType(store, last, currExpr.Args[0]) + validateMakeType(baseOf(at)) + + var length int + + // Validate length argument (second argument for make) + if len(currExpr.Args) > 1 { + at1 := evalStaticTypeOf(store, last, currExpr.Args[1]) + if at1 == nil { + panic("invalid argument 2: nil for built-in make") + } + + if at1 == Float64Type { + panic(fmt.Sprintf("invalid argument: index length (variable of type float64) must be integer")) + } + + l, err := parseIntValue(currExpr.Args[1]) + if err != nil { + panic(fmt.Sprintf("invalid length argument for built-in make: %s", err.Error())) + } + if l < 0 { + panic(fmt.Sprintf("invalid length argument for built-in make: cannot be negative, got %d", length)) + } + length = l + } + + // Validate capacity argument (third argument for make) + if len(currExpr.Args) > 2 { + at2 := evalStaticTypeOf(store, last, currExpr.Args[2]) + if at2 == nil { + panic("invalid argument 3: nil for built-in make") + } + capacity, err := parseIntValue(currExpr.Args[2]) + if err != nil { + panic(fmt.Sprintf("invalid capacity argument for built-in make: %s", err.Error())) + } + if capacity < 0 { + panic(fmt.Sprintf("invalid capacity argument for built-in make: cannot be negative, got %d", capacity)) + } + if _, ok := currExpr.Args[2].(*BasicLitExpr); ok { + if capacity < length { + panic(fmt.Sprintf("invalid argument: length and capacity swapped")) + } + } + } +} + +// Ensures that the first argument to make() is a valid type +func validateMakeType(tp Type) { + switch tp.(type) { + case *SliceType, *MapType, *ChanType: + // Valid types for make() + default: + panic(fmt.Sprintf("invalid type for built-in make: %s (must be slice, map, or channel)", tp.String())) + } +} + +func parseIntValue(expr Expr) (int, error) { + switch e := expr.(type) { + case *ConstExpr: + if ble, ok := e.Source.(*BasicLitExpr); ok { + if ble.Kind == FLOAT { + f, err := strconv.ParseFloat(ble.Value, 64) + if err != nil { + return 0, err + } + intPart := math.Trunc(f) + if f != intPart { + return 0, fmt.Errorf("%g (untyped float constant) truncated to int", f) + } + if f > float64(math.MaxInt) { + return 0, errors.New("value out of range for int type") + } + return int(f), nil + } + n, err := strconv.ParseInt(ble.Value, 0, 64) + if err != nil { + return 0, fmt.Errorf("cannot convert %s to int", ble.Value) + } + if n > int64(math.MaxInt) { + return 0, errors.New("value out of range for int type") + } + return int(n), nil + } + } + return 0, nil +} + +func extractFunctionName(n *CallExpr) string { + ce, ok := n.Func.(*ConstExpr) + if !ok { + return "" + } + + ne, ok := ce.Source.(*NameExpr) + if !ok { + return "" + } + return string(ne.Name) +} diff --git a/gnovm/tests/files/cap10.gno b/gnovm/tests/files/cap10.gno index a76c723f77a..5da06f5b573 100644 --- a/gnovm/tests/files/cap10.gno +++ b/gnovm/tests/files/cap10.gno @@ -5,4 +5,4 @@ func main() { } // Error: -// unexpected type for cap(): struct{A int} +// main/files/cap10.gno:4:17: unexpected type for cap(): struct{A int} diff --git a/gnovm/tests/files/cap7.gno b/gnovm/tests/files/cap7.gno index 73e2f11c147..8e5ecbc7c77 100644 --- a/gnovm/tests/files/cap7.gno +++ b/gnovm/tests/files/cap7.gno @@ -6,4 +6,4 @@ func main() { } // Error: -// unexpected type for cap(): string +// main/files/cap7.gno:5:17: unexpected type for cap(): string diff --git a/gnovm/tests/files/cap8.gno b/gnovm/tests/files/cap8.gno index 7fe9b48e28b..c0bc31922db 100644 --- a/gnovm/tests/files/cap8.gno +++ b/gnovm/tests/files/cap8.gno @@ -6,4 +6,4 @@ func main() { } // Error: -// unexpected type for cap(): *int +// main/files/cap8.gno:5:17: unexpected type for cap(): *int diff --git a/gnovm/tests/files/cap9.gno b/gnovm/tests/files/cap9.gno index b7aad6037b4..832660ff071 100644 --- a/gnovm/tests/files/cap9.gno +++ b/gnovm/tests/files/cap9.gno @@ -6,4 +6,4 @@ func main() { } // Error: -// unexpected type for cap(): *int +// main/files/cap9.gno:5:17: unexpected type for cap(): *int \ No newline at end of file diff --git a/gnovm/tests/files/len0.gno b/gnovm/tests/files/len0.gno index b5bbee62b14..370964b832e 100644 --- a/gnovm/tests/files/len0.gno +++ b/gnovm/tests/files/len0.gno @@ -1,6 +1,6 @@ package main -func f(a []int) any { +func f(a []int) interface{} { return len(a) } diff --git a/gnovm/tests/files/len7.gno b/gnovm/tests/files/len7.gno index 5deccdbf331..0571f8cc0cc 100644 --- a/gnovm/tests/files/len7.gno +++ b/gnovm/tests/files/len7.gno @@ -5,4 +5,4 @@ func main() { } // Error: -// unexpected type for len(): *int +// main/files/len7.gno:4:10: unexpected type for len(): *int diff --git a/gnovm/tests/files/len8.gno b/gnovm/tests/files/len8.gno index e48f276e394..af91a6df43f 100644 --- a/gnovm/tests/files/len8.gno +++ b/gnovm/tests/files/len8.gno @@ -1,4 +1,4 @@ -package main + package main func main() { println(len(struct { @@ -7,4 +7,4 @@ func main() { } // Error: -// unexpected type for len(): struct{A int; B int} +// main/files/len8.gno:4:10: unexpected type for len(): struct{A int; B int} diff --git a/gnovm/tests/files/len9.gno b/gnovm/tests/files/len9.gno index 5dcc861e677..42684f25cf6 100644 --- a/gnovm/tests/files/len9.gno +++ b/gnovm/tests/files/len9.gno @@ -1,10 +1,9 @@ package main func main() { - var a map[string]string - - println(len(a)) + a := len(nil) + println(a) } -// Output: -// 0 +// Error: +// main/files/len9.gno:4:10: invalid argument: nil for built-in len \ No newline at end of file diff --git a/gnovm/tests/files/make0.gno b/gnovm/tests/files/make0.gno index 104c429db1a..d3484198c34 100644 --- a/gnovm/tests/files/make0.gno +++ b/gnovm/tests/files/make0.gno @@ -1,6 +1,6 @@ package main -func f() any { +func f() interface{} { return make([]int, 2) } diff --git a/gnovm/tests/files/make1.gno b/gnovm/tests/files/make1.gno index c7fa68825ff..139c68f514a 100644 --- a/gnovm/tests/files/make1.gno +++ b/gnovm/tests/files/make1.gno @@ -2,7 +2,7 @@ package main import "fmt" -func f() any { +func f() interface{} { return make(map[int]int) }