From 02c7baefd37ace155c367d633c2283ac119de8a5 Mon Sep 17 00:00:00 2001 From: deelawn Date: Sat, 28 Oct 2023 11:35:18 -0400 Subject: [PATCH 01/17] added protected string methods for complex value types to avoid infinite recursion --- gnovm/pkg/gnolang/values_string.go | 95 +++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index f9a0128d7f9..52497cc3ca7 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -7,6 +7,8 @@ import ( "strings" ) +const recursed string = "" + func (v StringValue) String() string { return strconv.Quote(string(v)) } @@ -24,10 +26,19 @@ func (dbv DataByteValue) String() string { } func (av *ArrayValue) String() string { + return av.ProtectedString(map[Value]struct{}{}) +} + +func (av *ArrayValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[av]; ok { + return recursed + } + + seen[av] = struct{}{} ss := make([]string, len(av.List)) if av.Data == nil { for i, e := range av.List { - ss[i] = e.String() + ss[i] = e.ProtectedString(seen) } // NOTE: we may want to unify the representation, // but for now tests expect this to be different. @@ -41,17 +52,28 @@ func (av *ArrayValue) String() string { } func (sv *SliceValue) String() string { + return sv.ProtectedString(map[Value]struct{}{}) +} + +func (sv *SliceValue) ProtectedString(seen map[Value]struct{}) string { if sv.Base == nil { return "nil-slice" } + + if _, ok := seen[sv]; ok { + return recursed + } + if ref, ok := sv.Base.(RefValue); ok { return fmt.Sprintf("slice[%v]", ref) } + + seen[sv] = struct{}{} vbase := sv.Base.(*ArrayValue) if vbase.Data == nil { ss := make([]string, sv.Length) for i, e := range vbase.List[sv.Offset : sv.Offset+sv.Length] { - ss[i] = e.String() + ss[i] = e.ProtectedString(seen) } return "slice[" + strings.Join(ss, ",") + "]" } @@ -62,16 +84,31 @@ func (sv *SliceValue) String() string { } func (pv PointerValue) String() string { - // NOTE: cannot do below, due to recursion problems. - // TODO: create a different String2(...) function. - // return fmt.Sprintf("&%s", v.TypedValue.String()) - return fmt.Sprintf("&%p.(*%s)", pv.TV, pv.TV.T.String()) + return pv.ProtectedString(map[Value]struct{}{}) +} + +func (pv PointerValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[pv]; ok { + return recursed + } + + seen[pv] = struct{}{} + return fmt.Sprintf("&%s", pv.TV.ProtectedString(seen)) } func (sv *StructValue) String() string { + return sv.ProtectedString(map[Value]struct{}{}) +} + +func (sv *StructValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[sv]; ok { + return recursed + } + + seen[sv] = struct{}{} ss := make([]string, len(sv.Fields)) for i, f := range sv.Fields { - ss[i] = f.String() + ss[i] = f.ProtectedString(seen) } return "struct{" + strings.Join(ss, ",") + "}" } @@ -104,9 +141,19 @@ func (v *BoundMethodValue) String() string { } func (mv *MapValue) String() string { + return mv.ProtectedString(map[Value]struct{}{}) +} + +func (mv *MapValue) ProtectedString(seen map[Value]struct{}) string { if mv.List == nil { return "zero-map" } + + if _, ok := seen[mv]; ok { + return recursed + } + + seen[mv] = struct{}{} ss := make([]string, 0, mv.GetLength()) next := mv.List.Head for next != nil { @@ -176,10 +223,21 @@ func (tv *TypedValue) Sprint(m *Machine) string { res := m.Eval(Call(Sel(&ConstExpr{TypedValue: *tv}, "Error"))) return res[0].GetString() } + + return tv.ProtectedSprint(map[Value]struct{}{}, true) +} + +func (tv *TypedValue) ProtectedSprint(seen map[Value]struct{}, considerDeclaredType bool) string { + + if _, ok := seen[tv.V]; ok { + return recursed + } + // print declared type - if _, ok := tv.T.(*DeclaredType); ok { - return tv.String() + if _, ok := tv.T.(*DeclaredType); ok && considerDeclaredType { + return tv.ProtectedString(seen) } + // otherwise, default behavior. switch bt := baseOf(tv.T).(type) { case PrimitiveType: @@ -223,15 +281,15 @@ func (tv *TypedValue) Sprint(m *Machine) string { if tv.V == nil { return "invalid-pointer" } - return tv.V.(PointerValue).String() + return tv.V.(PointerValue).ProtectedString(seen) case *ArrayType: - return tv.V.(*ArrayValue).String() + return tv.V.(*ArrayValue).ProtectedString(seen) case *SliceType: - return tv.V.(*SliceValue).String() + return tv.V.(*SliceValue).ProtectedString(seen) case *StructType: - return tv.V.(*StructValue).String() + return tv.V.(*StructValue).ProtectedString(seen) case *MapType: - return tv.V.(*MapValue).String() + return tv.V.(*MapValue).ProtectedString(seen) case *FuncType: switch fv := tv.V.(type) { case nil: @@ -281,6 +339,10 @@ func (tv *TypedValue) Sprint(m *Machine) string { // For gno debugging/testing. func (tv TypedValue) String() string { + return tv.ProtectedString(map[Value]struct{}{}) +} + +func (tv TypedValue) ProtectedString(seen map[Value]struct{}) string { if tv.IsUndefined() { return "(undefined)" } @@ -317,12 +379,15 @@ func (tv TypedValue) String() string { vs = fmt.Sprintf("%v", tv.GetFloat32()) case Float64Type: vs = fmt.Sprintf("%v", tv.GetFloat64()) + // Complex types that require recusion protection. default: vs = nilStr } } else { - vs = fmt.Sprintf("%v", tv.V) + // vs = fmt.Sprintf("%v", tv.V) + vs = tv.ProtectedSprint(seen, false) } + ts := tv.T.String() return fmt.Sprintf("(%s %s)", vs, ts) // TODO improve } From 25455af1479b1ed1a6629ba1dfce392851d13108 Mon Sep 17 00:00:00 2001 From: deelawn Date: Sat, 28 Oct 2023 11:39:07 -0400 Subject: [PATCH 02/17] consistently renamed receivers of value type string methods --- gnovm/pkg/gnolang/values_string.go | 116 ++++++++++++++--------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 52497cc3ca7..ed997734c89 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -13,31 +13,31 @@ func (v StringValue) String() string { return strconv.Quote(string(v)) } -func (bv BigintValue) String() string { - return bv.V.String() +func (v BigintValue) String() string { + return v.V.String() } -func (bv BigdecValue) String() string { - return bv.V.String() +func (v BigdecValue) String() string { + return v.V.String() } -func (dbv DataByteValue) String() string { - return fmt.Sprintf("(%0X)", (dbv.GetByte())) +func (v DataByteValue) String() string { + return fmt.Sprintf("(%0X)", (v.GetByte())) } -func (av *ArrayValue) String() string { - return av.ProtectedString(map[Value]struct{}{}) +func (v *ArrayValue) String() string { + return v.ProtectedString(map[Value]struct{}{}) } -func (av *ArrayValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[av]; ok { +func (v *ArrayValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[v]; ok { return recursed } - seen[av] = struct{}{} - ss := make([]string, len(av.List)) - if av.Data == nil { - for i, e := range av.List { + seen[v] = struct{}{} + ss := make([]string, len(v.List)) + if v.Data == nil { + for i, e := range v.List { ss[i] = e.ProtectedString(seen) } // NOTE: we may want to unify the representation, @@ -45,77 +45,77 @@ func (av *ArrayValue) ProtectedString(seen map[Value]struct{}) string { // This may be helpful for testing implementation behavior. return "array[" + strings.Join(ss, ",") + "]" } - if len(av.Data) > 256 { - return fmt.Sprintf("array[0x%X...]", av.Data[:256]) + if len(v.Data) > 256 { + return fmt.Sprintf("array[0x%X...]", v.Data[:256]) } - return fmt.Sprintf("array[0x%X]", av.Data) + return fmt.Sprintf("array[0x%X]", v.Data) } -func (sv *SliceValue) String() string { - return sv.ProtectedString(map[Value]struct{}{}) +func (v *SliceValue) String() string { + return v.ProtectedString(map[Value]struct{}{}) } -func (sv *SliceValue) ProtectedString(seen map[Value]struct{}) string { - if sv.Base == nil { +func (v *SliceValue) ProtectedString(seen map[Value]struct{}) string { + if v.Base == nil { return "nil-slice" } - if _, ok := seen[sv]; ok { + if _, ok := seen[v]; ok { return recursed } - if ref, ok := sv.Base.(RefValue); ok { + if ref, ok := v.Base.(RefValue); ok { return fmt.Sprintf("slice[%v]", ref) } - seen[sv] = struct{}{} - vbase := sv.Base.(*ArrayValue) + seen[v] = struct{}{} + vbase := v.Base.(*ArrayValue) if vbase.Data == nil { - ss := make([]string, sv.Length) - for i, e := range vbase.List[sv.Offset : sv.Offset+sv.Length] { + ss := make([]string, v.Length) + for i, e := range vbase.List[v.Offset : v.Offset+v.Length] { ss[i] = e.ProtectedString(seen) } return "slice[" + strings.Join(ss, ",") + "]" } - if sv.Length > 256 { - return fmt.Sprintf("slice[0x%X...(%d)]", vbase.Data[sv.Offset:sv.Offset+256], sv.Length) + if v.Length > 256 { + return fmt.Sprintf("slice[0x%X...(%d)]", vbase.Data[v.Offset:v.Offset+256], v.Length) } - return fmt.Sprintf("slice[0x%X]", vbase.Data[sv.Offset:sv.Offset+sv.Length]) + return fmt.Sprintf("slice[0x%X]", vbase.Data[v.Offset:v.Offset+v.Length]) } -func (pv PointerValue) String() string { - return pv.ProtectedString(map[Value]struct{}{}) +func (v PointerValue) String() string { + return v.ProtectedString(map[Value]struct{}{}) } -func (pv PointerValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[pv]; ok { +func (v PointerValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[v]; ok { return recursed } - seen[pv] = struct{}{} - return fmt.Sprintf("&%s", pv.TV.ProtectedString(seen)) + seen[v] = struct{}{} + return fmt.Sprintf("&%s", v.TV.ProtectedString(seen)) } -func (sv *StructValue) String() string { - return sv.ProtectedString(map[Value]struct{}{}) +func (v *StructValue) String() string { + return v.ProtectedString(map[Value]struct{}{}) } -func (sv *StructValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[sv]; ok { +func (v *StructValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[v]; ok { return recursed } - seen[sv] = struct{}{} - ss := make([]string, len(sv.Fields)) - for i, f := range sv.Fields { + seen[v] = struct{}{} + ss := make([]string, len(v.Fields)) + for i, f := range v.Fields { ss[i] = f.ProtectedString(seen) } return "struct{" + strings.Join(ss, ",") + "}" } -func (fv *FuncValue) String() string { - name := string(fv.Name) - if fv.Type == nil { +func (v *FuncValue) String() string { + name := string(v.Name) + if v.Type == nil { return fmt.Sprintf("incomplete-func ?%s(?)?", name) } return name @@ -140,22 +140,22 @@ func (v *BoundMethodValue) String() string { recvT, name, params, results) } -func (mv *MapValue) String() string { - return mv.ProtectedString(map[Value]struct{}{}) +func (v *MapValue) String() string { + return v.ProtectedString(map[Value]struct{}{}) } -func (mv *MapValue) ProtectedString(seen map[Value]struct{}) string { - if mv.List == nil { +func (v *MapValue) ProtectedString(seen map[Value]struct{}) string { + if v.List == nil { return "zero-map" } - if _, ok := seen[mv]; ok { + if _, ok := seen[v]; ok { return recursed } - seen[mv] = struct{}{} - ss := make([]string, 0, mv.GetLength()) - next := mv.List.Head + seen[v] = struct{}{} + ss := make([]string, 0, v.GetLength()) + next := v.List.Head for next != nil { ss = append(ss, next.Key.String()+":"+ @@ -180,13 +180,13 @@ func (v TypeValue) String() string { v.Type.String(), ptr) } -func (pv *PackageValue) String() string { - return fmt.Sprintf("package(%s %s)", pv.PkgName, pv.PkgPath) +func (v *PackageValue) String() string { + return fmt.Sprintf("package(%s %s)", v.PkgName, v.PkgPath) } -func (nv *NativeValue) String() string { +func (v *NativeValue) String() string { return fmt.Sprintf("gonative{%v}", - nv.Value.Interface()) + v.Value.Interface()) /* return fmt.Sprintf("gonative{%v (%s)}", v.Value.Interface(), From cc76cbd0fc04b033ced904099f958ceda5d026b8 Mon Sep 17 00:00:00 2001 From: deelawn Date: Sat, 28 Oct 2023 12:10:05 -0700 Subject: [PATCH 03/17] quote the value if it is a string --- gnovm/pkg/gnolang/values_string.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index ed997734c89..31e8e1dc354 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -386,6 +386,9 @@ func (tv TypedValue) ProtectedString(seen map[Value]struct{}) string { } else { // vs = fmt.Sprintf("%v", tv.V) vs = tv.ProtectedSprint(seen, false) + if base := baseOf(tv.T); base == StringType || base == UntypedStringType { + vs = strconv.Quote(vs) + } } ts := tv.T.String() From b2a07da96ccc11db7c43c988cc95621bc8e93e8c Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 1 Nov 2023 13:35:19 -0700 Subject: [PATCH 04/17] print the address instead of "recursed" --- gnovm/pkg/gnolang/values_string.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 31e8e1dc354..81285f8b698 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -7,8 +7,6 @@ import ( "strings" ) -const recursed string = "" - func (v StringValue) String() string { return strconv.Quote(string(v)) } @@ -31,7 +29,7 @@ func (v *ArrayValue) String() string { func (v *ArrayValue) ProtectedString(seen map[Value]struct{}) string { if _, ok := seen[v]; ok { - return recursed + return fmt.Sprintf("%p", v) } seen[v] = struct{}{} @@ -61,7 +59,7 @@ func (v *SliceValue) ProtectedString(seen map[Value]struct{}) string { } if _, ok := seen[v]; ok { - return recursed + return fmt.Sprintf("%p", v) } if ref, ok := v.Base.(RefValue); ok { @@ -89,7 +87,7 @@ func (v PointerValue) String() string { func (v PointerValue) ProtectedString(seen map[Value]struct{}) string { if _, ok := seen[v]; ok { - return recursed + return fmt.Sprintf("%p", &v) } seen[v] = struct{}{} @@ -102,7 +100,7 @@ func (v *StructValue) String() string { func (v *StructValue) ProtectedString(seen map[Value]struct{}) string { if _, ok := seen[v]; ok { - return recursed + return fmt.Sprintf("%p", v) } seen[v] = struct{}{} @@ -150,7 +148,7 @@ func (v *MapValue) ProtectedString(seen map[Value]struct{}) string { } if _, ok := seen[v]; ok { - return recursed + return fmt.Sprintf("%p", v) } seen[v] = struct{}{} @@ -230,7 +228,7 @@ func (tv *TypedValue) Sprint(m *Machine) string { func (tv *TypedValue) ProtectedSprint(seen map[Value]struct{}, considerDeclaredType bool) string { if _, ok := seen[tv.V]; ok { - return recursed + return fmt.Sprintf("%p", tv) } // print declared type From cab383de2d1cbd23cd83aa06e6eb525b3f2fa7c4 Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 3 Nov 2023 13:15:16 -0700 Subject: [PATCH 05/17] Revert "consistently renamed receivers of value type string methods" This reverts commit 25455af1479b1ed1a6629ba1dfce392851d13108. Renaming receivers is not trivial due to linting rules --- gnovm/pkg/gnolang/values_string.go | 126 ++++++++++++++--------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 81285f8b698..31f65d13e17 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -11,31 +11,31 @@ func (v StringValue) String() string { return strconv.Quote(string(v)) } -func (v BigintValue) String() string { - return v.V.String() +func (bv BigintValue) String() string { + return bv.V.String() } -func (v BigdecValue) String() string { - return v.V.String() +func (bv BigdecValue) String() string { + return bv.V.String() } -func (v DataByteValue) String() string { - return fmt.Sprintf("(%0X)", (v.GetByte())) +func (dbv DataByteValue) String() string { + return fmt.Sprintf("(%0X)", (dbv.GetByte())) } -func (v *ArrayValue) String() string { - return v.ProtectedString(map[Value]struct{}{}) +func (av *ArrayValue) String() string { + return av.ProtectedString(map[Value]struct{}{}) } -func (v *ArrayValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[v]; ok { - return fmt.Sprintf("%p", v) +func (av *ArrayValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[av]; ok { + return fmt.Sprintf("%p", av) } - seen[v] = struct{}{} - ss := make([]string, len(v.List)) - if v.Data == nil { - for i, e := range v.List { + seen[av] = struct{}{} + ss := make([]string, len(av.List)) + if av.Data == nil { + for i, e := range av.List { ss[i] = e.ProtectedString(seen) } // NOTE: we may want to unify the representation, @@ -43,77 +43,77 @@ func (v *ArrayValue) ProtectedString(seen map[Value]struct{}) string { // This may be helpful for testing implementation behavior. return "array[" + strings.Join(ss, ",") + "]" } - if len(v.Data) > 256 { - return fmt.Sprintf("array[0x%X...]", v.Data[:256]) + if len(av.Data) > 256 { + return fmt.Sprintf("array[0x%X...]", av.Data[:256]) } - return fmt.Sprintf("array[0x%X]", v.Data) + return fmt.Sprintf("array[0x%X]", av.Data) } -func (v *SliceValue) String() string { - return v.ProtectedString(map[Value]struct{}{}) +func (sv *SliceValue) String() string { + return sv.ProtectedString(map[Value]struct{}{}) } -func (v *SliceValue) ProtectedString(seen map[Value]struct{}) string { - if v.Base == nil { +func (sv *SliceValue) ProtectedString(seen map[Value]struct{}) string { + if sv.Base == nil { return "nil-slice" } - if _, ok := seen[v]; ok { - return fmt.Sprintf("%p", v) + if _, ok := seen[sv]; ok { + return fmt.Sprintf("%p", sv) } - if ref, ok := v.Base.(RefValue); ok { + if ref, ok := sv.Base.(RefValue); ok { return fmt.Sprintf("slice[%v]", ref) } - seen[v] = struct{}{} - vbase := v.Base.(*ArrayValue) + seen[sv] = struct{}{} + vbase := sv.Base.(*ArrayValue) if vbase.Data == nil { - ss := make([]string, v.Length) - for i, e := range vbase.List[v.Offset : v.Offset+v.Length] { + ss := make([]string, sv.Length) + for i, e := range vbase.List[sv.Offset : sv.Offset+sv.Length] { ss[i] = e.ProtectedString(seen) } return "slice[" + strings.Join(ss, ",") + "]" } - if v.Length > 256 { - return fmt.Sprintf("slice[0x%X...(%d)]", vbase.Data[v.Offset:v.Offset+256], v.Length) + if sv.Length > 256 { + return fmt.Sprintf("slice[0x%X...(%d)]", vbase.Data[sv.Offset:sv.Offset+256], sv.Length) } - return fmt.Sprintf("slice[0x%X]", vbase.Data[v.Offset:v.Offset+v.Length]) + return fmt.Sprintf("slice[0x%X]", vbase.Data[sv.Offset:sv.Offset+sv.Length]) } -func (v PointerValue) String() string { - return v.ProtectedString(map[Value]struct{}{}) +func (pv PointerValue) String() string { + return pv.ProtectedString(map[Value]struct{}{}) } -func (v PointerValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[v]; ok { - return fmt.Sprintf("%p", &v) +func (pv PointerValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[pv]; ok { + return fmt.Sprintf("%p", &pv) } - seen[v] = struct{}{} - return fmt.Sprintf("&%s", v.TV.ProtectedString(seen)) + seen[pv] = struct{}{} + return fmt.Sprintf("&%s", pv.TV.ProtectedString(seen)) } -func (v *StructValue) String() string { - return v.ProtectedString(map[Value]struct{}{}) +func (sv *StructValue) String() string { + return sv.ProtectedString(map[Value]struct{}{}) } -func (v *StructValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[v]; ok { - return fmt.Sprintf("%p", v) +func (sv *StructValue) ProtectedString(seen map[Value]struct{}) string { + if _, ok := seen[sv]; ok { + return fmt.Sprintf("%p", sv) } - seen[v] = struct{}{} - ss := make([]string, len(v.Fields)) - for i, f := range v.Fields { + seen[sv] = struct{}{} + ss := make([]string, len(sv.Fields)) + for i, f := range sv.Fields { ss[i] = f.ProtectedString(seen) } return "struct{" + strings.Join(ss, ",") + "}" } -func (v *FuncValue) String() string { - name := string(v.Name) - if v.Type == nil { +func (fv *FuncValue) String() string { + name := string(fv.Name) + if fv.Type == nil { return fmt.Sprintf("incomplete-func ?%s(?)?", name) } return name @@ -138,22 +138,22 @@ func (v *BoundMethodValue) String() string { recvT, name, params, results) } -func (v *MapValue) String() string { - return v.ProtectedString(map[Value]struct{}{}) +func (mv *MapValue) String() string { + return mv.ProtectedString(map[Value]struct{}{}) } -func (v *MapValue) ProtectedString(seen map[Value]struct{}) string { - if v.List == nil { +func (mv *MapValue) ProtectedString(seen map[Value]struct{}) string { + if mv.List == nil { return "zero-map" } - if _, ok := seen[v]; ok { - return fmt.Sprintf("%p", v) + if _, ok := seen[mv]; ok { + return fmt.Sprintf("%p", mv) } - seen[v] = struct{}{} - ss := make([]string, 0, v.GetLength()) - next := v.List.Head + seen[mv] = struct{}{} + ss := make([]string, 0, mv.GetLength()) + next := mv.List.Head for next != nil { ss = append(ss, next.Key.String()+":"+ @@ -178,13 +178,13 @@ func (v TypeValue) String() string { v.Type.String(), ptr) } -func (v *PackageValue) String() string { - return fmt.Sprintf("package(%s %s)", v.PkgName, v.PkgPath) +func (pv *PackageValue) String() string { + return fmt.Sprintf("package(%s %s)", pv.PkgName, pv.PkgPath) } -func (v *NativeValue) String() string { +func (nv *NativeValue) String() string { return fmt.Sprintf("gonative{%v}", - v.Value.Interface()) + nv.Value.Interface()) /* return fmt.Sprintf("gonative{%v (%s)}", v.Value.Interface(), From b1bd02e3c80b14ca1b948978e841ec66d3e2b38e Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 3 Nov 2023 14:00:03 -0700 Subject: [PATCH 06/17] properly handle ref and pointer values --- gnovm/pkg/gnolang/values_string.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 31f65d13e17..733a66b0b72 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -91,6 +91,14 @@ func (pv PointerValue) ProtectedString(seen map[Value]struct{}) string { } seen[pv] = struct{}{} + + // This method was limited and not working correctly previously. Allowing for it to work as + // intended means that it needs to ensure the type value is not nil before attempting to + // convert it to a string. + if pv.TV == nil { + return "&" + } + return fmt.Sprintf("&%s", pv.TV.ProtectedString(seen)) } @@ -226,7 +234,6 @@ func (tv *TypedValue) Sprint(m *Machine) string { } func (tv *TypedValue) ProtectedSprint(seen map[Value]struct{}, considerDeclaredType bool) string { - if _, ok := seen[tv.V]; ok { return fmt.Sprintf("%p", tv) } @@ -236,6 +243,12 @@ func (tv *TypedValue) ProtectedSprint(seen map[Value]struct{}, considerDeclaredT return tv.ProtectedString(seen) } + // This is a special case that became necessary after adding `ProtectedString()` methods to + // reliably prevent recursive print loops. + if v, ok := tv.V.(RefValue); ok { + return v.String() + } + // otherwise, default behavior. switch bt := baseOf(tv.T).(type) { case PrimitiveType: From b9c45abb4543b3881415bb001ec1777104166a3f Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 3 Nov 2023 14:29:26 -0700 Subject: [PATCH 07/17] fixed gonative type printing --- gnovm/pkg/gnolang/values_string.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 733a66b0b72..88ef2a4c79c 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -332,8 +332,7 @@ func (tv *TypedValue) ProtectedSprint(seen map[Value]struct{}, considerDeclaredT panic("not yet implemented") // return tv.V.(*ChanValue).String() case *NativeType: - return fmt.Sprintf("%v", - tv.V.(*NativeValue).Value.Interface()) + return tv.V.(*NativeValue).String() default: if debug { panic(fmt.Sprintf( From a4acd86efa951d0f9c332494042e6f7ab923be35 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 30 Nov 2023 08:53:20 -0800 Subject: [PATCH 08/17] use slice instead of map --- gnovm/pkg/gnolang/values_string.go | 70 +++++++++++++++++++----------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 88ef2a4c79c..52574e68738 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -3,10 +3,30 @@ package gnolang import ( "fmt" "reflect" + "slices" "strconv" "strings" ) +// This indicates the maximum ancticipated depth of the stack when printing a Value type. +const defaultSeenValuesSize = 16 + +type seenValues struct { + values []Value +} + +func (sv *seenValues) Put(v Value) { + sv.values = append(sv.values, v) +} + +func (sv *seenValues) Contains(v Value) bool { + return slices.Contains(sv.values, v) +} + +func newSeenValues() *seenValues { + return &seenValues{values: make([]Value, 0, defaultSeenValuesSize)} +} + func (v StringValue) String() string { return strconv.Quote(string(v)) } @@ -24,15 +44,15 @@ func (dbv DataByteValue) String() string { } func (av *ArrayValue) String() string { - return av.ProtectedString(map[Value]struct{}{}) + return av.ProtectedString(newSeenValues()) } -func (av *ArrayValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[av]; ok { +func (av *ArrayValue) ProtectedString(seen *seenValues) string { + if seen.Contains(av) { return fmt.Sprintf("%p", av) } - seen[av] = struct{}{} + seen.Put(av) ss := make([]string, len(av.List)) if av.Data == nil { for i, e := range av.List { @@ -50,15 +70,15 @@ func (av *ArrayValue) ProtectedString(seen map[Value]struct{}) string { } func (sv *SliceValue) String() string { - return sv.ProtectedString(map[Value]struct{}{}) + return sv.ProtectedString(newSeenValues()) } -func (sv *SliceValue) ProtectedString(seen map[Value]struct{}) string { +func (sv *SliceValue) ProtectedString(seen *seenValues) string { if sv.Base == nil { return "nil-slice" } - if _, ok := seen[sv]; ok { + if seen.Contains(sv) { return fmt.Sprintf("%p", sv) } @@ -66,7 +86,7 @@ func (sv *SliceValue) ProtectedString(seen map[Value]struct{}) string { return fmt.Sprintf("slice[%v]", ref) } - seen[sv] = struct{}{} + seen.Put(sv) vbase := sv.Base.(*ArrayValue) if vbase.Data == nil { ss := make([]string, sv.Length) @@ -82,15 +102,15 @@ func (sv *SliceValue) ProtectedString(seen map[Value]struct{}) string { } func (pv PointerValue) String() string { - return pv.ProtectedString(map[Value]struct{}{}) + return pv.ProtectedString(newSeenValues()) } -func (pv PointerValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[pv]; ok { +func (pv PointerValue) ProtectedString(seen *seenValues) string { + if seen.Contains(pv) { return fmt.Sprintf("%p", &pv) } - seen[pv] = struct{}{} + seen.Put(pv) // This method was limited and not working correctly previously. Allowing for it to work as // intended means that it needs to ensure the type value is not nil before attempting to @@ -103,15 +123,15 @@ func (pv PointerValue) ProtectedString(seen map[Value]struct{}) string { } func (sv *StructValue) String() string { - return sv.ProtectedString(map[Value]struct{}{}) + return sv.ProtectedString(newSeenValues()) } -func (sv *StructValue) ProtectedString(seen map[Value]struct{}) string { - if _, ok := seen[sv]; ok { +func (sv *StructValue) ProtectedString(seen *seenValues) string { + if seen.Contains(sv) { return fmt.Sprintf("%p", sv) } - seen[sv] = struct{}{} + seen.Put(sv) ss := make([]string, len(sv.Fields)) for i, f := range sv.Fields { ss[i] = f.ProtectedString(seen) @@ -147,19 +167,19 @@ func (v *BoundMethodValue) String() string { } func (mv *MapValue) String() string { - return mv.ProtectedString(map[Value]struct{}{}) + return mv.ProtectedString(newSeenValues()) } -func (mv *MapValue) ProtectedString(seen map[Value]struct{}) string { +func (mv *MapValue) ProtectedString(seen *seenValues) string { if mv.List == nil { return "zero-map" } - if _, ok := seen[mv]; ok { + if seen.Contains(mv) { return fmt.Sprintf("%p", mv) } - seen[mv] = struct{}{} + seen.Put(mv) ss := make([]string, 0, mv.GetLength()) next := mv.List.Head for next != nil { @@ -230,11 +250,11 @@ func (tv *TypedValue) Sprint(m *Machine) string { return res[0].GetString() } - return tv.ProtectedSprint(map[Value]struct{}{}, true) + return tv.ProtectedSprint(newSeenValues(), true) } -func (tv *TypedValue) ProtectedSprint(seen map[Value]struct{}, considerDeclaredType bool) string { - if _, ok := seen[tv.V]; ok { +func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType bool) string { + if seen.Contains(tv.V) { return fmt.Sprintf("%p", tv) } @@ -349,10 +369,10 @@ func (tv *TypedValue) ProtectedSprint(seen map[Value]struct{}, considerDeclaredT // For gno debugging/testing. func (tv TypedValue) String() string { - return tv.ProtectedString(map[Value]struct{}{}) + return tv.ProtectedString(newSeenValues()) } -func (tv TypedValue) ProtectedString(seen map[Value]struct{}) string { +func (tv TypedValue) ProtectedString(seen *seenValues) string { if tv.IsUndefined() { return "(undefined)" } From 7615717e4832531ff65b762eac40fd0da48c47b8 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 30 Nov 2023 08:57:13 -0800 Subject: [PATCH 09/17] removed slice package --- gnovm/pkg/gnolang/values_string.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 52574e68738..0cf719bfa32 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -3,7 +3,6 @@ package gnolang import ( "fmt" "reflect" - "slices" "strconv" "strings" ) @@ -20,7 +19,13 @@ func (sv *seenValues) Put(v Value) { } func (sv *seenValues) Contains(v Value) bool { - return slices.Contains(sv.values, v) + for _, vv := range sv.values { + if vv == v { + return true + } + } + + return false } func newSeenValues() *seenValues { From 65b52d52fc5b029d1c5da19794e3534f08585386 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 30 Nov 2023 09:03:53 -0800 Subject: [PATCH 10/17] make default values 32 --- gnovm/pkg/gnolang/values_string.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 0cf719bfa32..6ca68d6bc92 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -8,7 +8,7 @@ import ( ) // This indicates the maximum ancticipated depth of the stack when printing a Value type. -const defaultSeenValuesSize = 16 +const defaultSeenValuesSize = 32 type seenValues struct { values []Value From ace93925cc9ecc2586bd2e0c7c3ff61b99dd5e78 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 30 Nov 2023 09:58:38 -0800 Subject: [PATCH 11/17] defer pop after each put --- gnovm/pkg/gnolang/values_string.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 6ca68d6bc92..7650a1b0aa9 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -28,6 +28,18 @@ func (sv *seenValues) Contains(v Value) bool { return false } +// Pop could be called by using a defer after each Put. +// Consider why this is necessary: +// - we are printing an array of structs +// - each invocation of struct.ProtectedString adds the value to the seenValues +// - without calling Pop before exiting struct.ProtectedString, the next call to +// struct.ProtectedString in the array.ProtectedString loop will not result in the value +// being printed if the value has already been print +// - this is NOT recursion and SHOULD be printed +func (sv *seenValues) Pop() { + sv.values = sv.values[:len(sv.values)-1] +} + func newSeenValues() *seenValues { return &seenValues{values: make([]Value, 0, defaultSeenValuesSize)} } @@ -58,6 +70,8 @@ func (av *ArrayValue) ProtectedString(seen *seenValues) string { } seen.Put(av) + defer seen.Pop() + ss := make([]string, len(av.List)) if av.Data == nil { for i, e := range av.List { @@ -92,6 +106,8 @@ func (sv *SliceValue) ProtectedString(seen *seenValues) string { } seen.Put(sv) + defer seen.Pop() + vbase := sv.Base.(*ArrayValue) if vbase.Data == nil { ss := make([]string, sv.Length) @@ -116,6 +132,7 @@ func (pv PointerValue) ProtectedString(seen *seenValues) string { } seen.Put(pv) + defer seen.Pop() // This method was limited and not working correctly previously. Allowing for it to work as // intended means that it needs to ensure the type value is not nil before attempting to @@ -137,6 +154,8 @@ func (sv *StructValue) ProtectedString(seen *seenValues) string { } seen.Put(sv) + defer seen.Pop() + ss := make([]string, len(sv.Fields)) for i, f := range sv.Fields { ss[i] = f.ProtectedString(seen) @@ -185,6 +204,8 @@ func (mv *MapValue) ProtectedString(seen *seenValues) string { } seen.Put(mv) + defer seen.Pop() + ss := make([]string, 0, mv.GetLength()) next := mv.List.Head for next != nil { From 9db301c2ada0f258470bd0c47126e3c738b6011b Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 30 Nov 2023 09:59:39 -0800 Subject: [PATCH 12/17] typo --- gnovm/pkg/gnolang/values_string.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 7650a1b0aa9..f750c390431 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -28,7 +28,7 @@ func (sv *seenValues) Contains(v Value) bool { return false } -// Pop could be called by using a defer after each Put. +// Pop should be called by using a defer after each Put. // Consider why this is necessary: // - we are printing an array of structs // - each invocation of struct.ProtectedString adds the value to the seenValues From fc3f8606788af6307951d0c7e10dedf87962a8cf Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 5 Dec 2023 16:23:12 -0800 Subject: [PATCH 13/17] added missing type --- gnovm/pkg/gnolang/values_string.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 49c5baa8b4a..15afb7bfa36 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -383,6 +383,8 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo return tv.V.(*MapValue).ProtectedString(seen) case *NativeType: return tv.V.(*NativeValue).String() + case *TypeType: + return tv.V.(TypeValue).String() default: if debug { panic(fmt.Sprintf( From 5ae1b27f3a3e1b1457ae7e13ed3c26c39ecc9b65 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Dec 2023 20:56:28 +0000 Subject: [PATCH 14/17] Update gnovm/pkg/gnolang/values_string.go Co-authored-by: Morgan --- gnovm/pkg/gnolang/values_string.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 15afb7bfa36..b93143893ca 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -134,9 +134,7 @@ func (pv PointerValue) ProtectedString(seen *seenValues) string { seen.Put(pv) defer seen.Pop() - // This method was limited and not working correctly previously. Allowing for it to work as - // intended means that it needs to ensure the type value is not nil before attempting to - // convert it to a string. + // Handle nil TV's, avoiding a nil pointer deref below. if pv.TV == nil { return "&" } From 96a6331c6d5c6058e6dc8182414b76ca744f3a81 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Dec 2023 20:57:25 +0000 Subject: [PATCH 15/17] Update gnovm/pkg/gnolang/values_string.go Co-authored-by: Morgan --- gnovm/pkg/gnolang/values_string.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index b93143893ca..c92def64cb8 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -445,7 +445,6 @@ func (tv TypedValue) ProtectedString(seen *seenValues) string { vs = nilStr } } else { - // vs = fmt.Sprintf("%v", tv.V) vs = tv.ProtectedSprint(seen, false) if base := baseOf(tv.T); base == StringType || base == UntypedStringType { vs = strconv.Quote(vs) From c47b018bc8fad640f895784ffd413cc9dc0cbb8e Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Dec 2023 13:04:56 -0800 Subject: [PATCH 16/17] moved typetype case --- gnovm/pkg/gnolang/values_string.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index c92def64cb8..a8e0237b085 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -365,6 +365,8 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo return tv.V.(*PackageValue).String() case *ChanType: panic("not yet implemented") + case *TypeType: + return tv.V.(TypeValue).String() default: if tv.V == nil { return nilStr + " " + tv.T.String() @@ -381,8 +383,6 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo return tv.V.(*MapValue).ProtectedString(seen) case *NativeType: return tv.V.(*NativeValue).String() - case *TypeType: - return tv.V.(TypeValue).String() default: if debug { panic(fmt.Sprintf( From aadbe3abb6dfdbbda33aca8feedd60c8d3d98a2d Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Dec 2023 13:09:48 -0800 Subject: [PATCH 17/17] use type inference to avoid further type switches --- gnovm/pkg/gnolang/values_string.go | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index a8e0237b085..34187e32879 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -7,6 +7,10 @@ import ( "strings" ) +type protectedStringer interface { + ProtectedString(*seenValues) string +} + // This indicates the maximum ancticipated depth of the stack when printing a Value type. const defaultSeenValuesSize = 32 @@ -368,29 +372,25 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo case *TypeType: return tv.V.(TypeValue).String() default: + // The remaining types may have a nil value. if tv.V == nil { return nilStr + " " + tv.T.String() } - switch bt.(type) { - case *ArrayType: - return tv.V.(*ArrayValue).ProtectedString(seen) - case *SliceType: - return tv.V.(*SliceValue).ProtectedString(seen) - case *StructType: - return tv.V.(*StructValue).ProtectedString(seen) - case *MapType: - return tv.V.(*MapValue).ProtectedString(seen) - case *NativeType: - return tv.V.(*NativeValue).String() - default: - if debug { - panic(fmt.Sprintf( - "unexpected type %s", - tv.T.String())) - } else { - panic("should not happen") - } + // *ArrayType, *SliceType, *StructType, *MapType + if ps, ok := tv.V.(protectedStringer); ok { + return ps.ProtectedString(seen) + } else if s, ok := tv.V.(fmt.Stringer); ok { + // *NativeType + return s.String() + } + + if debug { + panic(fmt.Sprintf( + "unexpected type %s", + tv.T.String())) + } else { + panic("should not happen") } } }