Skip to content

Commit

Permalink
fix(gnovm): support len and cap on pointer to array (gnolang#2709)
Browse files Browse the repository at this point in the history
# Description

closes gnolang#2707

The `GetLength()`, `GetCapacity()` method in `TypedValue` has been
updated to handle pointer type.

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

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

---------

Co-authored-by: Morgan Bazalgette <morgan@morganbaz.com>
  • Loading branch information
notJoon and thehowl authored Sep 13, 2024
1 parent 22ce48c commit 2e56ecf
Show file tree
Hide file tree
Showing 21 changed files with 313 additions and 19 deletions.
4 changes: 2 additions & 2 deletions gnovm/pkg/gnolang/uverse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 44 additions & 17 deletions gnovm/pkg/gnolang/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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()))
Expand All @@ -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()))
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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")
Expand Down
70 changes: 70 additions & 0 deletions gnovm/pkg/gnolang/values_test.go
Original file line number Diff line number Diff line change
@@ -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()
})
}
}
10 changes: 10 additions & 0 deletions gnovm/tests/files/cap1.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

func main() {
exp := [...]string{"HELLO"}
x := cap(&exp)
println(x)
}

// Output:
// 1
8 changes: 8 additions & 0 deletions gnovm/tests/files/cap10.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

func main() {
println("cap", cap(struct{ A int }{}))
}

// Error:
// unexpected type for cap(): struct{A int}
9 changes: 9 additions & 0 deletions gnovm/tests/files/cap2.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
exp := [...]int{1, 2, 3, 4, 5}
println(cap(exp))
}

// Output:
// 5
9 changes: 9 additions & 0 deletions gnovm/tests/files/cap3.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
slice := make([]int, 3, 5)
println(cap(slice))
}

// Output:
// 5
9 changes: 9 additions & 0 deletions gnovm/tests/files/cap4.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
var slice []int
println(cap(slice))
}

// Output:
// 0
12 changes: 12 additions & 0 deletions gnovm/tests/files/cap5.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

func main() {
printCap(nil)
}

func printCap(arr *[2]int) {
println(cap(arr))
}

// Output:
// 2
31 changes: 31 additions & 0 deletions gnovm/tests/files/cap6.gno
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions gnovm/tests/files/cap7.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
var s string
println("cap", cap(s))
}

// Error:
// unexpected type for cap(): string
9 changes: 9 additions & 0 deletions gnovm/tests/files/cap8.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
var i *int
println("cap", cap(i))
}

// Error:
// unexpected type for cap(): *int
9 changes: 9 additions & 0 deletions gnovm/tests/files/cap9.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
i := new(int)
println("cap", cap(i))
}

// Error:
// unexpected type for cap(): *int
10 changes: 10 additions & 0 deletions gnovm/tests/files/len1.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

func main() {
exp := [...]string{"HELLO"}
x := len(&exp)
println(x)
}

// Output:
// 1
9 changes: 9 additions & 0 deletions gnovm/tests/files/len2.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
exp := [...]string{"HELLO", "WORLD"}
println(len(exp))
}

// Output:
// 2
10 changes: 10 additions & 0 deletions gnovm/tests/files/len3.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

func main() {
exp := [...]int{1, 2, 3, 4, 5}
ptr := &exp
println(len(ptr))
}

// Output:
// 5
12 changes: 12 additions & 0 deletions gnovm/tests/files/len4.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

func main() {
printLen(nil)
}

func printLen(arr *[2]int) {
println(len(arr))
}

// Output:
// 2
9 changes: 9 additions & 0 deletions gnovm/tests/files/len5.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
var arr *[3]string
println(cap(arr))
}

// Output:
// 3
Loading

0 comments on commit 2e56ecf

Please sign in to comment.