Skip to content

Commit

Permalink
proc: make some type casts less counterintuitive
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
aarzilli authored Dec 12, 2023
1 parent dda8f69 commit 57dad93
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 22 deletions.
3 changes: 2 additions & 1 deletion _fixtures/testvariables2.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}
Expand Down Expand Up @@ -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)
}
53 changes: 42 additions & 11 deletions pkg/proc/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
}
Expand Down
22 changes: 12 additions & 10 deletions pkg/proc/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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},
Expand Down
21 changes: 21 additions & 0 deletions pkg/terminal/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
})
}

0 comments on commit 57dad93

Please sign in to comment.