Skip to content

Commit

Permalink
feat(gnolang): print nil slices as undefined (#1380)
Browse files Browse the repository at this point in the history
The `println` function was encountering issues when trying to process
nil values(e.g. nil slice). This was causing memory errors. The issue
has been addressed by adding checks for nil values and handling them
appropriately to prevent memory errors.

related with: #1377
  • Loading branch information
notJoon authored Dec 5, 2023
1 parent 5bc611a commit 71ba96d
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 19 deletions.
160 changes: 160 additions & 0 deletions gnovm/pkg/gnolang/uverse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package gnolang

import (
"testing"
)

type printlnTestCases struct {
name string
code string
expected string
}

func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) {
test := []printlnTestCases{
{
name: "print empty slice",
code: `package test
func main() {
emptySlice1 := make([]int, 0)
emptySlice2 := []int{}
println(emptySlice1)
println(emptySlice2)
}`,
expected: "slice[]\nslice[]\n",
},
{
name: "nil slice",
code: `package test
func main() {
println(nil)
}`,
expected: "undefined\n",
},
{
name: "print empty string slice",
code: `package test
func main() {
var a []string
println(a)
}`,
expected: "nil []string\n",
},
{
name: "print non-empty slice",
code: `package test
func main() {
a := []string{"a", "b"}
println(a)
}`,
expected: "slice[(\"a\" string),(\"b\" string)]\n",
},
{
name: "print empty map",
code: `package test
func main() {
var a map[string]string
println(a)
}`,
expected: "nil map[string]string\n",
},
{
name: "print non-empty map",
code: `package test
func main() {
a := map[string]string{"a": "b"}
println(a)
}`,
expected: "map{(\"a\" string):(\"b\" string)}\n",
},
{
name: "print nil struct",
code: `package test
func main() {
var a struct{}
println(a)
}`,
expected: "struct{}\n",
},
{
name: "print function",
code: `package test
func foo(a, b int) int {
return a + b
}
func main() {
println(foo(1, 3))
}`,
expected: "4\n",
},
{
name: "print composite slice",
code: `package test
func main() {
a, b, c, d := 1, 2, 3, 4
x := []int{
a: b,
c: d,
}
println(x)
}`,
expected: "slice[(0 int),(2 int),(0 int),(4 int)]\n",
},
{
name: "simple recover case",
code: `package test
func main() {
defer func() { println("recover", recover()) }()
println("simple panic")
}`,
expected: "simple panic\nrecover undefined\n",
},
{
name: "nested recover",
code: `package test
func main() {
defer func() { println("outer recover", recover()) }()
defer func() { println("nested panic") }()
println("simple panic")
}`,
expected: "simple panic\nnested panic\nouter recover undefined\n",
},
{
name: "print non-nil function",
code: `package test
func f() int {
return 1
}
func main() {
g := f
println(g)
}`,
expected: "f\n",
},
{
name: "print primitive types",
code: `package test
func main() {
println(1)
println(1.1)
println(true)
println("hello")
}`,
expected: "1\n1.1\ntrue\nhello\n",
},
}

for _, tc := range test {
t.Run(tc.name, func(t *testing.T) {
m := NewMachine("test", nil)
n := MustParseFile("main.go", tc.code)
m.RunFiles(n)
m.RunMain()
assertOutput(t, tc.code, tc.expected)
})
}
}
3 changes: 2 additions & 1 deletion gnovm/pkg/gnolang/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ func (*Block) assertValue() {}
func (RefValue) assertValue() {}

const (
nilStr = "nil"
nilStr = "nil"
undefinedStr = "undefined"
)

var (
Expand Down
32 changes: 14 additions & 18 deletions gnovm/pkg/gnolang/values_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,9 @@ func (v RefValue) String() string {
func (tv *TypedValue) Sprint(m *Machine) string {
// if undefined, just "undefined".
if tv == nil || tv.T == nil {
return "undefined"
return undefinedStr
}

// if implements .String(), return it.
if IsImplementedBy(gStringerType, tv.T) {
res := m.Eval(Call(Sel(&ConstExpr{TypedValue: *tv}, "String")))
Expand All @@ -180,6 +181,7 @@ func (tv *TypedValue) Sprint(m *Machine) string {
if _, ok := tv.T.(*DeclaredType); ok {
return tv.String()
}

// otherwise, default behavior.
switch bt := baseOf(tv.T).(type) {
case PrimitiveType:
Expand Down Expand Up @@ -224,22 +226,14 @@ func (tv *TypedValue) Sprint(m *Machine) string {
return "invalid-pointer"
}
return tv.V.(PointerValue).String()
case *ArrayType:
return tv.V.(*ArrayValue).String()
case *SliceType:
return tv.V.(*SliceValue).String()
case *StructType:
return tv.V.(*StructValue).String()
case *MapType:
return tv.V.(*MapValue).String()
case *ArrayType, *SliceType, *StructType, *MapType, *TypeType, *NativeType:
return printNilOrValue(tv, tv.V)
case *FuncType:
switch fv := tv.V.(type) {
case nil:
ft := tv.T.String()
return "nil " + ft
case *FuncValue:
return fv.String()
case *BoundMethodValue:
return nilStr + " " + ft
case *FuncValue, *BoundMethodValue:
return fv.String()
default:
panic(fmt.Sprintf(
Expand All @@ -253,18 +247,13 @@ func (tv *TypedValue) Sprint(m *Machine) string {
}
}
return nilStr
case *TypeType:
return tv.V.(TypeValue).String()
case *DeclaredType:
panic("should not happen")
case *PackageType:
return tv.V.(*PackageValue).String()
case *ChanType:
panic("not yet implemented")
// return tv.V.(*ChanValue).String()
case *NativeType:
return fmt.Sprintf("%v",
tv.V.(*NativeValue).Value.Interface())
default:
if debug {
panic(fmt.Sprintf(
Expand All @@ -276,6 +265,13 @@ func (tv *TypedValue) Sprint(m *Machine) string {
}
}

func printNilOrValue(tv *TypedValue, valueType interface{}) string {
if tv.V == nil {
return nilStr + " " + tv.T.String()
}
return fmt.Sprintf("%v", valueType)
}

// ----------------------------------------
// TypedValue.String()

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

func main() {
var a []string
println(a)
}

// Output:
// nil []string

0 comments on commit 71ba96d

Please sign in to comment.