From 2e56ecf818cb8bf93d88f637757c63eeddfd8f91 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Sat, 14 Sep 2024 00:32:13 +0900 Subject: [PATCH] fix(gnovm): support `len` and `cap` on pointer to array (#2709) # Description closes #2707 The `GetLength()`, `GetCapacity()` method in `TypedValue` has been updated to handle pointer type.
Contributors' checklist... - [x] 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 - [x] 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: Morgan Bazalgette --- gnovm/pkg/gnolang/uverse_test.go | 4 +- gnovm/pkg/gnolang/values.go | 61 ++++++++++++++++++++-------- gnovm/pkg/gnolang/values_test.go | 70 ++++++++++++++++++++++++++++++++ gnovm/tests/files/cap1.gno | 10 +++++ gnovm/tests/files/cap10.gno | 8 ++++ gnovm/tests/files/cap2.gno | 9 ++++ gnovm/tests/files/cap3.gno | 9 ++++ gnovm/tests/files/cap4.gno | 9 ++++ gnovm/tests/files/cap5.gno | 12 ++++++ gnovm/tests/files/cap6.gno | 31 ++++++++++++++ gnovm/tests/files/cap7.gno | 9 ++++ gnovm/tests/files/cap8.gno | 9 ++++ gnovm/tests/files/cap9.gno | 9 ++++ gnovm/tests/files/len1.gno | 10 +++++ gnovm/tests/files/len2.gno | 9 ++++ gnovm/tests/files/len3.gno | 10 +++++ gnovm/tests/files/len4.gno | 12 ++++++ gnovm/tests/files/len5.gno | 9 ++++ gnovm/tests/files/len6.gno | 14 +++++++ gnovm/tests/files/len7.gno | 8 ++++ gnovm/tests/files/len8.gno | 10 +++++ 21 files changed, 313 insertions(+), 19 deletions(-) create mode 100644 gnovm/pkg/gnolang/values_test.go create mode 100644 gnovm/tests/files/cap1.gno create mode 100644 gnovm/tests/files/cap10.gno create mode 100644 gnovm/tests/files/cap2.gno create mode 100644 gnovm/tests/files/cap3.gno create mode 100644 gnovm/tests/files/cap4.gno create mode 100644 gnovm/tests/files/cap5.gno create mode 100644 gnovm/tests/files/cap6.gno create mode 100644 gnovm/tests/files/cap7.gno create mode 100644 gnovm/tests/files/cap8.gno create mode 100644 gnovm/tests/files/cap9.gno create mode 100644 gnovm/tests/files/len1.gno create mode 100644 gnovm/tests/files/len2.gno create mode 100644 gnovm/tests/files/len3.gno create mode 100644 gnovm/tests/files/len4.gno create mode 100644 gnovm/tests/files/len5.gno create mode 100644 gnovm/tests/files/len6.gno create mode 100644 gnovm/tests/files/len7.gno create mode 100644 gnovm/tests/files/len8.gno diff --git a/gnovm/pkg/gnolang/uverse_test.go b/gnovm/pkg/gnolang/uverse_test.go index 7a6c0567e45..76961b70ccb 100644 --- a/gnovm/pkg/gnolang/uverse_test.go +++ b/gnovm/pkg/gnolang/uverse_test.go @@ -4,14 +4,14 @@ import ( "testing" ) -type printlnTestCases struct { +type uverseTestCases struct { name string code string expected string } func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) { - test := []printlnTestCases{ + test := []uverseTestCases{ { name: "print empty slice", code: `package test diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 5da7c15bb05..bbf77bf19c7 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -2125,13 +2125,18 @@ func (tv *TypedValue) GetLength() int { switch bt := baseOf(tv.T).(type) { case PrimitiveType: if bt != StringType { - panic("should not happen") + panic(fmt.Sprintf("unexpected type for len(): %s", tv.T.String())) } return 0 case *ArrayType: return bt.Len case *SliceType: return 0 + case *PointerType: + if at, ok := bt.Elt.(*ArrayType); ok { + return at.Len + } + panic(fmt.Sprintf("unexpected type for len(): %s", tv.T.String())) default: panic(fmt.Sprintf( "unexpected type for len(): %s", @@ -2149,6 +2154,11 @@ func (tv *TypedValue) GetLength() int { return cv.GetLength() case *NativeValue: return cv.Value.Len() + case PointerValue: + if av, ok := cv.TV.V.(*ArrayValue); ok { + return av.GetLength() + } + panic(fmt.Sprintf("unexpected type for len(): %s", tv.T.String())) default: panic(fmt.Sprintf("unexpected type for len(): %s", tv.T.String())) @@ -2157,27 +2167,34 @@ func (tv *TypedValue) GetLength() int { func (tv *TypedValue) GetCapacity() int { if tv.V == nil { - if debug { - // assert acceptable type. - switch baseOf(tv.T).(type) { - // strings have no capacity. - case *ArrayType: - case *SliceType: - default: - panic("should not happen") + // assert acceptable type. + switch bt := baseOf(tv.T).(type) { + // strings have no capacity. + case *ArrayType: + return bt.Len + case *SliceType: + return 0 + case *PointerType: + if at, ok := bt.Elt.(*ArrayType); ok { + return at.Len } + panic(fmt.Sprintf("unexpected type for cap(): %s", tv.T.String())) + default: + panic(fmt.Sprintf("unexpected type for cap(): %s", tv.T.String())) } - return 0 } switch cv := tv.V.(type) { - case StringValue: - return len(string(cv)) case *ArrayValue: return cv.GetCapacity() case *SliceValue: return cv.GetCapacity() case *NativeValue: return cv.Value.Cap() + case PointerValue: + if av, ok := cv.TV.V.(*ArrayValue); ok { + return av.GetCapacity() + } + panic(fmt.Sprintf("unexpected type for cap(): %s", tv.T.String())) default: panic(fmt.Sprintf("unexpected type for cap(): %s", tv.T.String())) @@ -2200,13 +2217,13 @@ func (tv *TypedValue) GetSlice(alloc *Allocator, low, high int) TypedValue { "invalid slice index %d > %d", low, high)) } - if tv.GetCapacity() < high { - panic(fmt.Sprintf( - "slice bounds out of range [%d:%d] with capacity %d", - low, high, tv.GetCapacity())) - } switch t := baseOf(tv.T).(type) { case PrimitiveType: + if tv.GetLength() < high { + panic(fmt.Sprintf( + "slice bounds out of range [%d:%d] with string length %d", + low, high, tv.GetLength())) + } if t == StringType || t == UntypedStringType { return TypedValue{ T: tv.T, @@ -2215,6 +2232,11 @@ func (tv *TypedValue) GetSlice(alloc *Allocator, low, high int) TypedValue { } panic("non-string primitive type cannot be sliced") case *ArrayType: + if tv.GetLength() < high { + panic(fmt.Sprintf( + "slice bounds out of range [%d:%d] with array length %d", + low, high, tv.GetLength())) + } av := tv.V.(*ArrayValue) st := alloc.NewType(&SliceType{ Elt: t.Elt, @@ -2230,6 +2252,11 @@ func (tv *TypedValue) GetSlice(alloc *Allocator, low, high int) TypedValue { ), } case *SliceType: + if tv.GetCapacity() < high { + panic(fmt.Sprintf( + "slice bounds out of range [%d:%d] with capacity %d", + low, high, tv.GetCapacity())) + } if tv.V == nil { if low != 0 || high != 0 { panic("nil slice index out of range") diff --git a/gnovm/pkg/gnolang/values_test.go b/gnovm/pkg/gnolang/values_test.go new file mode 100644 index 00000000000..ce6edd0a2f9 --- /dev/null +++ b/gnovm/pkg/gnolang/values_test.go @@ -0,0 +1,70 @@ +package gnolang + +import ( + "fmt" + "testing" +) + +type mockTypedValueStruct struct { + field int +} + +func (m *mockTypedValueStruct) assertValue() {} + +func (m *mockTypedValueStruct) String() string { + return fmt.Sprintf("MockTypedValueStruct(%d)", m.field) +} + +func TestGetLengthPanic(t *testing.T) { + tests := []struct { + name string + tv TypedValue + expected string + }{ + { + name: "NonArrayPointer", + tv: TypedValue{ + T: &PointerType{Elt: &StructType{}}, + V: PointerValue{ + TV: &TypedValue{ + T: &StructType{}, + V: &mockTypedValueStruct{field: 42}, + }, + }, + }, + expected: "unexpected type for len(): *struct{}", + }, + { + name: "UnexpectedType", + tv: TypedValue{ + T: &StructType{}, + V: &mockTypedValueStruct{field: 42}, + }, + expected: "unexpected type for len(): struct{}", + }, + { + name: "UnexpectedPointerType", + tv: TypedValue{ + T: &PointerType{Elt: &StructType{}}, + V: nil, + }, + expected: "unexpected type for len(): *struct{}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("the code did not panic") + } else { + if r != tt.expected { + t.Errorf("expected panic message to be %q, got %q", tt.expected, r) + } + } + }() + + tt.tv.GetLength() + }) + } +} diff --git a/gnovm/tests/files/cap1.gno b/gnovm/tests/files/cap1.gno new file mode 100644 index 00000000000..e382357ca11 --- /dev/null +++ b/gnovm/tests/files/cap1.gno @@ -0,0 +1,10 @@ +package main + +func main() { + exp := [...]string{"HELLO"} + x := cap(&exp) + println(x) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/cap10.gno b/gnovm/tests/files/cap10.gno new file mode 100644 index 00000000000..a76c723f77a --- /dev/null +++ b/gnovm/tests/files/cap10.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println("cap", cap(struct{ A int }{})) +} + +// Error: +// unexpected type for cap(): struct{A int} diff --git a/gnovm/tests/files/cap2.gno b/gnovm/tests/files/cap2.gno new file mode 100644 index 00000000000..a834c17b474 --- /dev/null +++ b/gnovm/tests/files/cap2.gno @@ -0,0 +1,9 @@ +package main + +func main() { + exp := [...]int{1, 2, 3, 4, 5} + println(cap(exp)) +} + +// Output: +// 5 diff --git a/gnovm/tests/files/cap3.gno b/gnovm/tests/files/cap3.gno new file mode 100644 index 00000000000..c5b323338d8 --- /dev/null +++ b/gnovm/tests/files/cap3.gno @@ -0,0 +1,9 @@ +package main + +func main() { + slice := make([]int, 3, 5) + println(cap(slice)) +} + +// Output: +// 5 diff --git a/gnovm/tests/files/cap4.gno b/gnovm/tests/files/cap4.gno new file mode 100644 index 00000000000..758001358fa --- /dev/null +++ b/gnovm/tests/files/cap4.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var slice []int + println(cap(slice)) +} + +// Output: +// 0 diff --git a/gnovm/tests/files/cap5.gno b/gnovm/tests/files/cap5.gno new file mode 100644 index 00000000000..ce2b6be2c42 --- /dev/null +++ b/gnovm/tests/files/cap5.gno @@ -0,0 +1,12 @@ +package main + +func main() { + printCap(nil) +} + +func printCap(arr *[2]int) { + println(cap(arr)) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/cap6.gno b/gnovm/tests/files/cap6.gno new file mode 100644 index 00000000000..182279b7ec6 --- /dev/null +++ b/gnovm/tests/files/cap6.gno @@ -0,0 +1,31 @@ +package main + +func main() { + var arr [5]int + var nilArr *[5]int + var nilSlice []int + var nilArr2 *[8]struct{ A, B int } + var nilMatrix *[2][3]int + + println("cap(arr): ", cap(arr)) + println("cap(&arr): ", cap(&arr)) + println("cap(nilArr): ", cap(nilArr)) + println("cap(nilSlice): ", cap(nilSlice)) + println("cap(nilArr2): ", cap(nilArr2)) + println("cap(nilMatrix):", cap(nilMatrix)) + + printCap(nil) +} + +func printCap(arr *[3]string) { + println("printCap: ", cap(arr)) +} + +// Output: +// cap(arr): 5 +// cap(&arr): 5 +// cap(nilArr): 5 +// cap(nilSlice): 0 +// cap(nilArr2): 8 +// cap(nilMatrix): 2 +// printCap: 3 diff --git a/gnovm/tests/files/cap7.gno b/gnovm/tests/files/cap7.gno new file mode 100644 index 00000000000..73e2f11c147 --- /dev/null +++ b/gnovm/tests/files/cap7.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var s string + println("cap", cap(s)) +} + +// Error: +// unexpected type for cap(): string diff --git a/gnovm/tests/files/cap8.gno b/gnovm/tests/files/cap8.gno new file mode 100644 index 00000000000..7fe9b48e28b --- /dev/null +++ b/gnovm/tests/files/cap8.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var i *int + println("cap", cap(i)) +} + +// Error: +// unexpected type for cap(): *int diff --git a/gnovm/tests/files/cap9.gno b/gnovm/tests/files/cap9.gno new file mode 100644 index 00000000000..b7aad6037b4 --- /dev/null +++ b/gnovm/tests/files/cap9.gno @@ -0,0 +1,9 @@ +package main + +func main() { + i := new(int) + println("cap", cap(i)) +} + +// Error: +// unexpected type for cap(): *int diff --git a/gnovm/tests/files/len1.gno b/gnovm/tests/files/len1.gno new file mode 100644 index 00000000000..f627fba190f --- /dev/null +++ b/gnovm/tests/files/len1.gno @@ -0,0 +1,10 @@ +package main + +func main() { + exp := [...]string{"HELLO"} + x := len(&exp) + println(x) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/len2.gno b/gnovm/tests/files/len2.gno new file mode 100644 index 00000000000..377ff01851f --- /dev/null +++ b/gnovm/tests/files/len2.gno @@ -0,0 +1,9 @@ +package main + +func main() { + exp := [...]string{"HELLO", "WORLD"} + println(len(exp)) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/len3.gno b/gnovm/tests/files/len3.gno new file mode 100644 index 00000000000..89fe863b20b --- /dev/null +++ b/gnovm/tests/files/len3.gno @@ -0,0 +1,10 @@ +package main + +func main() { + exp := [...]int{1, 2, 3, 4, 5} + ptr := &exp + println(len(ptr)) +} + +// Output: +// 5 diff --git a/gnovm/tests/files/len4.gno b/gnovm/tests/files/len4.gno new file mode 100644 index 00000000000..8b24c755041 --- /dev/null +++ b/gnovm/tests/files/len4.gno @@ -0,0 +1,12 @@ +package main + +func main() { + printLen(nil) +} + +func printLen(arr *[2]int) { + println(len(arr)) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/len5.gno b/gnovm/tests/files/len5.gno new file mode 100644 index 00000000000..7daf3c2cc07 --- /dev/null +++ b/gnovm/tests/files/len5.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var arr *[3]string + println(cap(arr)) +} + +// Output: +// 3 diff --git a/gnovm/tests/files/len6.gno b/gnovm/tests/files/len6.gno new file mode 100644 index 00000000000..9656f65e08d --- /dev/null +++ b/gnovm/tests/files/len6.gno @@ -0,0 +1,14 @@ +package main + +func main() { + printLenCap(nil) +} + +func printLenCap(arr *[4]float64) { + println(len(arr)) + println(cap(arr)) +} + +// Output: +// 4 +// 4 diff --git a/gnovm/tests/files/len7.gno b/gnovm/tests/files/len7.gno new file mode 100644 index 00000000000..5deccdbf331 --- /dev/null +++ b/gnovm/tests/files/len7.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(len(new(int))) +} + +// Error: +// unexpected type for len(): *int diff --git a/gnovm/tests/files/len8.gno b/gnovm/tests/files/len8.gno new file mode 100644 index 00000000000..6ca5a6ae8fa --- /dev/null +++ b/gnovm/tests/files/len8.gno @@ -0,0 +1,10 @@ +package main + +func main() { + println(len(struct { + A, B int + }{})) +} + +// Error: +// unexpected type for len(): struct{A int;B int}