Repository: go-delve/delve (go)

Issue:
pkg/proc: failed to call method on struct type: write out of bounds
1. What version of Delve are you using (`dlv version`)?

```
Delve Debugger
Version: 1.20.2
Build: $Id: e0c278ad8e0126a312b553b8e171e81bcbd37f60 $
```

2. What version of Go are you using? (`go version`)?

```
go version go1.20.4 linux/amd64
```
3. What operating system and processor architecture are you using?

```
linux/amd64
```
5. What did you do?

Take the following program:

```go
package main

import (
	"fmt"
	"runtime"
)

type T struct {
	a int
	b uint32
}

func (t T) String() string {
	return fmt.Sprintf("%d %d", t.a, t.b)
}

func main() {
	t := T{a: 1, b: 2}
	runtime.Breakpoint()
	fmt.Println(t.String())
}
```

and ran:

```
dlv debug
(dlv) c
(dlv) call t.String()
```

6. What did you expect to see?

```
(dlv) call t.String()
> main.main() ./main.go:20 (PC: 0x49ecbf)
Values returned:
	~r0: "1 2"
```

7. What did you see instead?

```
(dlv) call t.String()
> main.main() ./main.go:20 (PC: 0x49ecbf)
Command failed: write out of bounds
```

**PS**:

This bug is based on a real use case to print the value of  `github.com/shopspring/decimal.Decimal`. This is [the definition of `decimal.Decimal`](https://github.com/shopspring/decimal/blob/f55dd564545cec84cf84f7a53fb3025cdbec1c4f/decimal.go#L75-L84):

```go
type Decimal struct {
	value *big.Int
	exp int32
}
```

See also this question https://stackoverflow.com/questions/76222724/is-it-possible-to-preview-github-com-shopspring-decimal-values-in-vscode-during.

Pull request description:
pkg/proc: pad variable mem in extractVarInfoFromEntry
On 64 bit system, the byte size of the following struct is 16:

```go
type myStruct struct {
    a int
    b uint32
}
```
But `extractVarInfoFromEntry` only allocates a mem of 12 bytes for it. When calling method of this struct with the `call` command, it will result in this error:

````
write out of bounds
````

This patch extends the mem by adding padding bytes to the end of the mem.

Fixes #3364.

Patch:
diff --git a/_fixtures/fncall.go b/_fixtures/fncall.go
index f1839ca4..d5e8ef85 100644
--- a/_fixtures/fncall.go
+++ b/_fixtures/fncall.go
@@ -199,6 +199,15 @@ func (i Issue2698) String() string {
 	return fmt.Sprintf("%d %d %d %d", i.a, i.b, i.c, i.d)
 }
 
+type Issue3364 struct {
+	a int
+	b uint32
+}
+
+func (i Issue3364) String() string {
+	return fmt.Sprintf("%d %d", i.a, i.b)
+}
+
 func main() {
 	one, two := 1, 2
 	intslice := []int{1, 2, 3}
@@ -222,6 +231,10 @@ func main() {
 		c: 3,
 		d: 4,
 	}
+	issue3364 := Issue3364{
+		a: 1,
+		b: 2,
+	}
 
 	fn2clos := makeclos(pa)
 	fn2glob := call1
@@ -241,5 +254,5 @@ func main() {
 	d.Method()
 	d.Base.Method()
 	x.CallMe()
-	fmt.Println(one, two, zero, call, call0, call2, callexit, callpanic, callbreak, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr, pa2, noreturncall, str, d, x, x2.CallMe(5), longstrs, regabistacktest, regabistacktest2, issue2698.String(), regabistacktest3, rast3, floatsum, ref)
+	fmt.Println(one, two, zero, call, call0, call2, callexit, callpanic, callbreak, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr, pa2, noreturncall, str, d, x, x2.CallMe(5), longstrs, regabistacktest, regabistacktest2, issue2698.String(), issue3364.String(), regabistacktest3, rast3, floatsum, ref)
 }
diff --git a/_scripts/rtype-out.txt b/_scripts/rtype-out.txt
index 8799bfa8..7783cf44 100644
--- a/_scripts/rtype-out.txt
+++ b/_scripts/rtype-out.txt
@@ -15,7 +15,7 @@ type bmap struct {
 }
 
 type eface struct {
-	_type *_type
+	_type *_type|*internal/abi.Type
 	data unsafe.Pointer
 }
 
@@ -50,7 +50,7 @@ type iface struct {
 }
 
 type itab struct {
-	_type *_type
+	_type *_type|*internal/abi.Type
 }
 
 type moduledata struct {
diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go
index 54b61ab9..44ab2ae2 100644
--- a/cmd/dlv/cmds/commands.go
+++ b/cmd/dlv/cmds/commands.go
@@ -818,16 +818,18 @@ func connectCmd(cmd *cobra.Command, args []string) {
 		os.Exit(1)
 		return
 	}
-	defer logflags.Close()
 	if loadConfErr != nil {
 		logflags.DebuggerLogger().Errorf("%v", loadConfErr)
 	}
 	addr := args[0]
 	if addr == "" {
 		fmt.Fprint(os.Stderr, "An empty address was provided. You must provide an address as the first argument.\n")
+		logflags.Close()
 		os.Exit(1)
 	}
-	os.Exit(connect(addr, nil, conf, debugger.ExecutingOther))
+	ec := connect(addr, nil, conf, debugger.ExecutingOther)
+	logflags.Close()
+	os.Exit(ec)
 }
 
 // waitForDisconnectSignal is a blocking function that waits for either
diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index a12aac8a..4cd41dc4 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -900,6 +900,13 @@ func (bi *BinaryInfo) typeToImage(typ godwarf.Type) *Image {
 	return bi.Images[typ.Common().Index]
 }
 
+func (bi *BinaryInfo) runtimeTypeTypename() string {
+	if goversion.ProducerAfterOrEqual(bi.Producer(), 1, 21) {
+		return "internal/abi.Type"
+	}
+	return "runtime._type"
+}
+
 var errBinaryInfoClose = errors.New("multiple errors closing executable files")
 
 // Close closes all internal readers.
@@ -2130,10 +2137,11 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB
 		if fn != nil && fn.cu.image == image {
 			tree, err := image.getDwarfTree(fn.offset)
 			if err == nil {
-				tree.Children, err = regabiMallocgcWorkaround(bi)
+				children, err := regabiMallocgcWorkaround(bi)
 				if err != nil {
-					bi.logger.Errorf("could not patch runtime.mallogc: %v", err)
+					bi.logger.Errorf("could not patch runtime.mallocgc: %v", err)
 				} else {
+					tree.Children = children
 					image.runtimeMallocgcTree = tree
 				}
 			}
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index f5dd79a9..bd0df515 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -1263,6 +1263,8 @@ func (e fakeEntry) AttrField(attr dwarf.Attr) *dwarf.Field {
 }
 
 func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
+	ptrToRuntimeType := "*" + bi.runtimeTypeTypename()
+
 	var err1 error
 
 	t := func(name string) godwarf.Type {
@@ -1298,7 +1300,7 @@ func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
 	case "amd64":
 		r := []*godwarf.Tree{
 			m("size", t("uintptr"), regnum.AMD64_Rax, false),
-			m("typ", t("*runtime._type"), regnum.AMD64_Rbx, false),
+			m("typ", t(ptrToRuntimeType), regnum.AMD64_Rbx, false),
 			m("needzero", t("bool"), regnum.AMD64_Rcx, false),
 			m("~r1", t("unsafe.Pointer"), regnum.AMD64_Rax, true),
 		}
@@ -1306,7 +1308,7 @@ func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
 	case "arm64":
 		r := []*godwarf.Tree{
 			m("size", t("uintptr"), regnum.ARM64_X0, false),
-			m("typ", t("*runtime._type"), regnum.ARM64_X0+1, false),
+			m("typ", t(ptrToRuntimeType), regnum.ARM64_X0+1, false),
 			m("needzero", t("bool"), regnum.ARM64_X0+2, false),
 			m("~r1", t("unsafe.Pointer"), regnum.ARM64_X0, true),
 		}
diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go
index cb2a35f6..a215d274 100644
--- a/pkg/proc/mem.go
+++ b/pkg/proc/mem.go
@@ -98,17 +98,17 @@ type compositeMemory struct {
 
 // CreateCompositeMemory created a new composite memory type using the provided MemoryReadWriter as the
 // underlying memory buffer.
-func CreateCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters, pieces []op.Piece) (*compositeMemory, error) {
+func CreateCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters, pieces []op.Piece, size int64) (*compositeMemory, error) {
 	// This is basically a small wrapper to avoid having to change all callers
 	// of newCompositeMemory since it existed first.
-	cm, err := newCompositeMemory(mem, arch, regs, pieces)
+	cm, err := newCompositeMemory(mem, arch, regs, pieces, size)
 	if cm != nil {
 		cm.base = fakeAddressUnresolv
 	}
 	return cm, err
 }
 
-func newCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters, pieces []op.Piece) (*compositeMemory, error) {
+func newCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters, pieces []op.Piece, size int64) (*compositeMemory, error) {
 	cmem := &compositeMemory{realmem: mem, arch: arch, regs: regs, pieces: pieces, data: []byte{}}
 	for i := range pieces {
 		piece := &pieces[i]
@@ -147,6 +147,11 @@ func newCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters
 			panic("unsupported piece kind")
 		}
 	}
+	paddingBytes := int(size) - len(cmem.data)
+	if paddingBytes > 0 && paddingBytes < arch.ptrSize {
+		padding := make([]byte, paddingBytes)
+		cmem.data = append(cmem.data, padding...)
+	}
 	return cmem, nil
 }
 
diff --git a/pkg/proc/target.go b/pkg/proc/target.go
index 079a99d9..0a773448 100644
--- a/pkg/proc/target.go
+++ b/pkg/proc/target.go
@@ -454,7 +454,7 @@ func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult {
 		v.Kind = ip.Kind
 
 		cachedMem := CreateLoadedCachedMemory(ip.Data)
-		compMem, _ := CreateCompositeMemory(cachedMem, t.BinInfo().Arch, op.DwarfRegisters{}, ip.Pieces)
+		compMem, _ := CreateCompositeMemory(cachedMem, t.BinInfo().Arch, op.DwarfRegisters{}, ip.Pieces, ip.RealType.Common().ByteSize)
 		v.mem = compMem
 
 		// Load the value here so that we don't have to export
@@ -506,7 +506,7 @@ const (
 // This caching is primarily done so that registerized variables don't get a
 // different address every time they are evaluated, which would be confusing
 // and leak memory.
-func (t *Target) newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece, descr *locationExpr) (int64, *compositeMemory, error) {
+func (t *Target) newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece, descr *locationExpr, size int64) (int64, *compositeMemory, error) {
 	var key string
 	if regs.CFA != 0 && len(pieces) > 0 {
 		// key is created by concatenating the location expression with the CFA,
@@ -521,7 +521,7 @@ func (t *Target) newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters
 		}
 	}
 
-	cmem, err := newCompositeMemory(mem, t.BinInfo().Arch, regs, pieces)
+	cmem, err := newCompositeMemory(mem, t.BinInfo().Arch, regs, pieces, size)
 	if err != nil {
 		return 0, cmem, err
 	}
diff --git a/pkg/proc/types.go b/pkg/proc/types.go
index 1940b26a..125bff12 100644
--- a/pkg/proc/types.go
+++ b/pkg/proc/types.go
@@ -122,6 +122,8 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind
 				if rtdie.kind == -1 {
 					if kindField := _type.loadFieldNamed("kind"); kindField != nil && kindField.Value != nil {
 						rtdie.kind, _ = constant.Int64Val(kindField.Value)
+					} else if kindField := _type.loadFieldNamed("Kind_"); kindField != nil && kindField.Value != nil {
+						rtdie.kind, _ = constant.Int64Val(kindField.Value)
 					}
 				}
 				return typ, rtdie.kind, nil
@@ -146,7 +148,7 @@ func resolveParametricType(bi *BinaryInfo, mem MemoryReadWriter, t godwarf.Type,
 	if err != nil {
 		return ptyp.TypedefType.Type, err
 	}
-	runtimeType, err := bi.findType("runtime._type")
+	runtimeType, err := bi.findType(bi.runtimeTypeTypename())
 	if err != nil {
 		return ptyp.TypedefType.Type, err
 	}
@@ -189,13 +191,16 @@ func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type)
 
 	typeAddr = uint64(md.types) + off
 
-	rtyp, err := bi.findType("runtime._type")
+	rtyp, err := bi.findType(bi.runtimeTypeTypename())
 	if err != nil {
 		return 0, 0, false, err
 	}
 	_type := newVariable("", typeAddr, rtyp, bi, mem)
 	kindv := _type.loadFieldNamed("kind")
-	if kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
+	if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
+		kindv = _type.loadFieldNamed("Kind_")
+	}
+	if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
 		return 0, 0, false, fmt.Errorf("unreadable interface type: %v", kindv.Unreadable)
 	}
 	typeKind, _ = constant.Uint64Val(kindv.Value)
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index e77515ff..65f5040d 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -671,7 +671,7 @@ func newVariable(name string, addr uint64, dwarfType godwarf.Type, bi *BinaryInf
 		v.stride = 1
 		v.fieldType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "byte", ReflectKind: reflect.Uint8}, BitSize: 8, BitOffset: 0}}
 		if v.Addr != 0 {
-			v.Base, v.Len, v.Unreadable = readStringInfo(v.mem, v.bi.Arch, v.Addr)
+			v.Base, v.Len, v.Unreadable = readStringInfo(v.mem, v.bi.Arch, v.Addr, t)
 		}
 	case *godwarf.SliceType:
 		v.Kind = reflect.Slice
@@ -1198,9 +1198,9 @@ func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op.
 	if pieces != nil {
 		var cmem *compositeMemory
 		if tgt != nil {
-			addr, cmem, err = tgt.newCompositeMemory(mem, regs, pieces, descr)
+			addr, cmem, err = tgt.newCompositeMemory(mem, regs, pieces, descr, t.Common().ByteSize)
 		} else {
-			cmem, err = newCompositeMemory(mem, bi.Arch, regs, pieces)
+			cmem, err = newCompositeMemory(mem, bi.Arch, regs, pieces, t.Common().ByteSize)
 			if cmem != nil {
 				cmem.base = fakeAddressUnresolv
 				addr = int64(cmem.base)
@@ -1455,30 +1455,38 @@ func convertToEface(srcv, dstv *Variable) error {
 	return dstv.writeEmptyInterface(typeAddr, srcv)
 }
 
-func readStringInfo(mem MemoryReadWriter, arch *Arch, addr uint64) (uint64, int64, error) {
+func readStringInfo(mem MemoryReadWriter, arch *Arch, addr uint64, typ *godwarf.StringType) (uint64, int64, error) {
 	// string data structure is always two ptrs in size. Addr, followed by len
 	// http://research.swtch.com/godata
 
 	mem = cacheMemory(mem, addr, arch.PtrSize()*2)
 
-	// read len
-	strlen, err := readIntRaw(mem, addr+uint64(arch.PtrSize()), int64(arch.PtrSize()))
-	if err != nil {
-		return 0, 0, fmt.Errorf("could not read string len %s", err)
-	}
-	if strlen < 0 {
-		return 0, 0, fmt.Errorf("invalid length: %d", strlen)
-	}
+	var strlen int64
+	var outaddr uint64
+	var err error
 
-	// read addr
-	addr, err = readUintRaw(mem, addr, int64(arch.PtrSize()))
-	if err != nil {
-		return 0, 0, fmt.Errorf("could not read string pointer %s", err)
-	}
-	if addr == 0 {
-		return 0, 0, nil
+	for _, field := range typ.StructType.Field {
+		switch field.Name {
+		case "len":
+			strlen, err = readIntRaw(mem, addr+uint64(field.ByteOffset), int64(arch.PtrSize()))
+			if err != nil {
+				return 0, 0, fmt.Errorf("could not read string len %s", err)
+			}
+			if strlen < 0 {
+				return 0, 0, fmt.Errorf("invalid length: %d", strlen)
+			}
+		case "str":
+			outaddr, err = readUintRaw(mem, addr+uint64(field.ByteOffset), int64(arch.PtrSize()))
+			if err != nil {
+				return 0, 0, fmt.Errorf("could not read string pointer %s", err)
+			}
+			if addr == 0 {
+				return 0, 0, nil
+			}
+		}
 	}
-	return addr, strlen, nil
+
+	return outaddr, strlen, nil
 }
 
 func readStringValue(mem MemoryReadWriter, addr uint64, strlen int64, cfg LoadConfig) (string, error) {
@@ -2248,7 +2256,7 @@ func (v *Variable) readInterface() (_type, data *Variable, isnil bool) {
 
 	// +rtype -field iface.tab *itab
 	// +rtype -field iface.data unsafe.Pointer
-	// +rtype -field eface._type *_type
+	// +rtype -field eface._type *_type|*internal/abi.Type
 	// +rtype -field eface.data unsafe.Pointer
 
 	for _, f := range ityp.Field {
@@ -2259,7 +2267,7 @@ func (v *Variable) readInterface() (_type, data *Variable, isnil bool) {
 			isnil = tab.Addr == 0
 			if !isnil {
 				var err error
-				_type, err = tab.structMember("_type") // +rtype *_type
+				_type, err = tab.structMember("_type") // +rtype *_type|*internal/abi.Type
 				if err != nil {
 					v.Unreadable = fmt.Errorf("invalid interface type: %v", err)
 					return


Test patch:
diff --git a/pkg/proc/dwarf_export_test.go b/pkg/proc/dwarf_export_test.go
index 191fde56..7add16c7 100644
--- a/pkg/proc/dwarf_export_test.go
+++ b/pkg/proc/dwarf_export_test.go
@@ -20,7 +20,7 @@ func NewCompositeMemory(p *Target, pieces []op.Piece, base uint64) (*compositeMe
 	dwarfregs := arch.RegistersToDwarfRegisters(0, regs)
 	dwarfregs.ChangeFunc = p.CurrentThread().SetReg
 
-	mem, err := newCompositeMemory(p.Memory(), arch, *dwarfregs, pieces)
+	mem, err := newCompositeMemory(p.Memory(), arch, *dwarfregs, pieces, 0)
 	if mem != nil {
 		mem.base = base
 	}
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 9706ace1..b6852d3d 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -4647,7 +4647,10 @@ func TestCgoStacktrace2(t *testing.T) {
 	// If a panic happens during cgo execution the stacktrace should show the C
 	// function that caused the problem.
 	withTestProcess("cgosigsegvstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
-		grp.Continue()
+		err := grp.Continue()
+		if _, exited := err.(proc.ErrProcessExited); exited {
+			t.Fatal("process exited")
+		}
 		frames, err := proc.ThreadStacktrace(p.CurrentThread(), 100)
 		assertNoError(err, t, "Stacktrace()")
 		logStacktrace(t, p, frames)
@@ -5099,6 +5102,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) {
 		candg := []*proc.G{}
 		bestg := []*proc.G{}
 		for _, g := range gs {
+			t.Logf("stacktracing goroutine %d (%v)\n", g.ID, g.CurrentLoc)
 			frames, err := g.Stacktrace(20, 0)
 			assertNoError(err, t, "Stacktrace")
 			for _, frame := range frames {
diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go
index 00f7f7b0..7cc2dc5e 100644
--- a/pkg/proc/variables_test.go
+++ b/pkg/proc/variables_test.go
@@ -212,7 +212,7 @@ func TestSetVariable(t *testing.T) {
 			assertNoError(err, t, "EvalVariable()")
 			assertVariable(t, variable, varTest{tc.name, true, tc.startVal, "", tc.typ, nil})
 
-			assertNoError(setVariable(p, tc.name, tc.expr), t, "SetVariable()")
+			assertNoError(setVariable(p, tc.name, tc.expr), t, fmt.Sprintf("SetVariable(%q, %q)", tc.name, tc.expr))
 
 			variable, err = evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
 			assertNoError(err, t, "EvalVariable()")
@@ -856,6 +856,7 @@ func TestEvalExpression(t *testing.T) {
 		assertNoError(grp.Continue(), t, "Continue() returned an error")
 		for i, tc := range testcases {
 			t.Run(strconv.Itoa(i), func(t *testing.T) {
+				t.Logf("%q", tc.name)
 				variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
 				if err != nil && err.Error() == "evaluating methods not supported on this version of Go" {
 					// this type of eval is unsupported with the current version of Go.
@@ -1266,6 +1267,7 @@ func TestCallFunction(t *testing.T) {
 		{`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil},
 		{`regabistacktest2(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)`, []string{":int:3", ":int:5", ":int:7", ":int:9", ":int:11", ":int:13", ":int:15", ":int:17", ":int:19", ":int:11"}, nil},
 		{`issue2698.String()`, []string{`:string:"1 2 3 4"`}, nil},
+		{`issue3364.String()`, []string{`:string:"1 2"`}, nil},
 		{`regabistacktest3(rast3, 5)`, []string{`:[10]string:[10]string ["onetwo","twothree","threefour","fourfive","fivesix","sixseven","sevenheight","heightnine","nineten","tenone"]`, ":uint8:15"}, nil},
 		{`floatsum(1, 2)`, []string{":float64:3"}, nil},
 	}