proc: make some type casts less counterintuitive

* proc: make some type casts less counterintuitive

The interaction of type casts with load configuration is sometimes
counterintuitive. This commit changes the way it is performed so that
when converting slices to strings and vice versa the maximum size
corresponding to the target type is used (i.e. MaxStringLen when
converting slices to strings and MaxArrayValues when converting slices
to strings).

This doesn't fully solve the problem (conversions to []rune are
problematic and multiple chained type casts will still be confusing)
but removes the problem for the majority of practical uses.

Fixes #3595, #3539
This commit is contained in:
Alessandro Arzilli 2023-12-12 20:43:41 +01:00 committed by GitHub
parent dda8f693e6
commit 57dad9342a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 22 deletions

@ -344,6 +344,7 @@ func main() {
runearray := [4]rune{116, 232, 115, 116}
longstr := "very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"
longbyteslice := []byte(longstr)
m5 := map[C]int{{longstr}: 1}
m6 := map[string]int{longstr: 123}
m7 := map[C]C{{longstr}: {"hello"}}
@ -403,5 +404,5 @@ func main() {
longslice := make([]int, 100, 100)
runtime.Breakpoint()
fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerReceiverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice, tim3, int3chan)
fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerReceiverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice, tim3, int3chan, longbyteslice)
}

@ -1316,36 +1316,44 @@ func (scope *EvalScope) evalTypeCast(op *evalop.TypeCast, stack *evalStack) {
if scope.loadCfg != nil {
cfg = *scope.loadCfg
}
argv.loadValue(cfg)
if argv.Unreadable != nil {
stack.err = argv.Unreadable
return
}
switch ttyp := typ.(type) {
case *godwarf.SliceType:
switch ttyp.ElemType.Common().ReflectKind {
case reflect.Uint8:
// string -> []uint8
if argv.Kind != reflect.String {
stack.err = converr
return
}
cfg.MaxStringLen = cfg.MaxArrayValues
argv.loadValue(cfg)
if argv.Unreadable != nil {
stack.err = argv.Unreadable
return
}
for i, ch := range []byte(constant.StringVal(argv.Value)) {
e := newVariable("", argv.Addr+uint64(i), typ.(*godwarf.SliceType).ElemType, scope.BinInfo, argv.mem)
e.loaded = true
e.Value = constant.MakeInt64(int64(ch))
v.Children = append(v.Children, *e)
}
v.Len = int64(len(v.Children))
v.Len = argv.Len
v.Cap = v.Len
stack.push(v)
return
case reflect.Int32:
// string -> []rune
if argv.Kind != reflect.String {
stack.err = converr
return
}
argv.loadValue(cfg)
if argv.Unreadable != nil {
stack.err = argv.Unreadable
return
}
for i, ch := range constant.StringVal(argv.Value) {
e := newVariable("", argv.Addr+uint64(i), typ.(*godwarf.SliceType).ElemType, scope.BinInfo, argv.mem)
e.loaded = true
@ -1361,12 +1369,18 @@ func (scope *EvalScope) evalTypeCast(op *evalop.TypeCast, stack *evalStack) {
case *godwarf.StringType:
switch argv.Kind {
case reflect.String:
s := constant.StringVal(argv.Value)
v.Value = constant.MakeString(s)
v.Len = int64(len(s))
stack.push(v)
// string -> string
argv.DwarfType = v.DwarfType
argv.RealType = v.RealType
stack.push(argv)
return
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
// integer -> string
argv.loadValue(cfg)
if argv.Unreadable != nil {
stack.err = argv.Unreadable
return
}
b, _ := constant.Int64Val(argv.Value)
s := string(rune(b))
v.Value = constant.MakeString(s)
@ -1382,34 +1396,51 @@ func (scope *EvalScope) evalTypeCast(op *evalop.TypeCast, stack *evalStack) {
}
switch elemType := elem.(type) {
case *godwarf.UintType:
// []uint8 -> string
if elemType.Name != "uint8" && elemType.Name != "byte" {
stack.err = converr
return
}
cfg.MaxArrayValues = cfg.MaxStringLen
argv.loadValue(cfg)
if argv.Unreadable != nil {
stack.err = argv.Unreadable
return
}
bytes := make([]byte, len(argv.Children))
for i := range argv.Children {
n, _ := constant.Int64Val(argv.Children[i].Value)
bytes[i] = byte(n)
}
v.Value = constant.MakeString(string(bytes))
v.Len = argv.Len
case *godwarf.IntType:
// []rune -> string
if elemType.Name != "int32" && elemType.Name != "rune" {
stack.err = converr
return
}
cfg.MaxArrayValues = cfg.MaxStringLen
argv.loadValue(cfg)
if argv.Unreadable != nil {
stack.err = argv.Unreadable
return
}
runes := make([]rune, len(argv.Children))
for i := range argv.Children {
n, _ := constant.Int64Val(argv.Children[i].Value)
runes[i] = rune(n)
}
v.Value = constant.MakeString(string(runes))
// The following line is wrong but the only way to get the correct value
// would be to decode the entire slice.
v.Len = int64(len(constant.StringVal(v.Value)))
default:
stack.err = converr
return
}
v.Len = int64(len(constant.StringVal(v.Value)))
stack.push(v)
return
}

@ -770,21 +770,23 @@ func getEvalExpressionTestCases() []varTest {
{"ptrinf2", false, `**(main.pptr)(…`, `(main.pptr)(…`, "main.pptr", nil},
// conversions between string/[]byte/[]rune (issue #548)
// conversions between string/[]byte/[]rune (issue #548, #3595, #3539)
{"runeslice", true, `[]int32 len: 4, cap: 4, [116,232,115,116]`, `[]int32 len: 4, cap: 4, [...]`, "[]int32", nil},
{"byteslice", true, `[]uint8 len: 5, cap: 5, [116,195,168,115,116]`, `[]uint8 len: 5, cap: 5, [...]`, "[]uint8", nil},
{"[]byte(str1)", false, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, "[]uint8", nil},
{"[]uint8(str1)", false, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, "[]uint8", nil},
{"[]byte(str1)", false, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]uint8 len: 11, cap: 11, nil`, "[]uint8", nil},
{"[]uint8(str1)", false, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]uint8 len: 11, cap: 11, nil`, "[]uint8", nil},
{"[]rune(str1)", false, `[]int32 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]int32 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, "[]int32", nil},
{"[]int32(str1)", false, `[]int32 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]int32 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, "[]int32", nil},
{"string(byteslice)", false, `"tèst"`, `""`, "string", nil},
{"[]int32(string(byteslice))", false, `[]int32 len: 4, cap: 4, [116,232,115,116]`, `[]int32 len: 0, cap: 0, nil`, "[]int32", nil},
{"string(runeslice)", false, `"tèst"`, `""`, "string", nil},
{"[]byte(string(runeslice))", false, `[]uint8 len: 5, cap: 5, [116,195,168,115,116]`, `[]uint8 len: 0, cap: 0, nil`, "[]uint8", nil},
{"string(byteslice)", false, `"tèst"`, `"tèst"`, "string", nil},
{"[]int32(string(byteslice))", false, `[]int32 len: 4, cap: 4, [116,232,115,116]`, `[]int32 len: 4, cap: 4, [116,232,115,116]`, "[]int32", nil},
{"string(runeslice)", false, `"tèst"`, `"tèst"`, "string", nil},
{"[]byte(string(runeslice))", false, `[]uint8 len: 5, cap: 5, [116,195,168,115,116]`, `[]uint8 len: 5, cap: 5, [116,195,168,115,116]`, "[]uint8", nil},
{"*(*[5]byte)(uintptr(&byteslice[0]))", false, `[5]uint8 [116,195,168,115,116]`, `[5]uint8 [...]`, "[5]uint8", nil},
{"string(bytearray)", false, `"tèst"`, `""`, "string", nil},
{"string(runearray)", false, `"tèst"`, `""`, "string", nil},
{"string(bytearray)", false, `"tèst"`, `"tèst"`, "string", nil},
{"string(runearray)", false, `"tèst"`, `"tèst"`, "string", nil},
{"string(str1)", false, `"01234567890"`, `"01234567890"`, "string", nil},
{"len([]byte(longstr))", false, `137`, `137`, "", nil},
{"len(string(longbyteslice))", false, `137`, `137`, "", nil},
// access to channel field members
{"ch1.qcount", false, "4", "4", "uint", nil},
@ -831,7 +833,7 @@ func getEvalExpressionTestCases() []varTest {
{`m6["very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"]`, false, `123`, `123`, "int", nil},
// issue #1423 - special typecasts everywhere
{`string(byteslice) == "tèst"`, false, `true`, `false`, "", nil},
{`string(byteslice) == "tèst"`, false, `true`, `true`, "", nil},
// issue #3138 - typecast to *interface{} breaking
{`*(*interface {})(uintptr(&iface1))`, false, `interface {}(*main.astruct) *{A: 1, B: 2}`, `interface {}(*main.astruct)…`, "interface {}", nil},

@ -1514,3 +1514,24 @@ func TestSubstitutePathAndList(t *testing.T) {
}
})
}
func TestDisplay(t *testing.T) {
// Tests that display command works. See issue #3595.
type testCase struct{ in, tgt string }
withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
term.MustExec("continue")
for _, tc := range []testCase{
{"string(byteslice)", `0: string(byteslice) = "tèst"`},
{"string(byteslice[1:])", `0: string(byteslice[1:]) = "èst"`},
{"%s string(byteslice)", `0: string(byteslice) = tèst`},
} {
out := term.MustExec("display -a " + tc.in)
t.Logf("%q -> %q", tc.in, out)
if !strings.Contains(out, tc.tgt) {
t.Errorf("wrong output for 'display -a %s':\n\tgot: %q\n\texpected: %q", tc.in, out, tc.tgt)
}
term.MustExec("display -d 0")
}
})
}