diff --git a/_fixtures/testvariables_generic.go b/_fixtures/testvariables_generic.go new file mode 100644 index 00000000..25860026 --- /dev/null +++ b/_fixtures/testvariables_generic.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "runtime" +) + +type astruct struct { + x, y int +} + +func testfn[T any, K comparable](arg1 T, arg2 K) { + m := make(map[K]T) + m[arg2] = arg1 + runtime.Breakpoint() + fmt.Println(arg1, arg2, m) +} + +func main() { + testfn[int, float32](3, 2.1) + testfn(&astruct{0, 1}, astruct{2, 3}) +} diff --git a/pkg/dwarf/godwarf/type.go b/pkg/dwarf/godwarf/type.go index ba189115..49ff1c0a 100644 --- a/pkg/dwarf/godwarf/type.go +++ b/pkg/dwarf/godwarf/type.go @@ -27,6 +27,7 @@ const ( AttrGoEmbeddedField dwarf.Attr = 0x2903 AttrGoRuntimeType dwarf.Attr = 0x2904 AttrGoPackageName dwarf.Attr = 0x2905 + AttrGoDictIndex dwarf.Attr = 0x2906 ) // Basic type encodings -- the value for AttrEncoding in a TagBaseType Entry. @@ -513,6 +514,11 @@ func (t *ChanType) stringIntl(recCheck recCheck) string { return "chan " + t.ElemType.String() } +type ParametricType struct { + TypedefType + DictIndex int64 +} + // An UnsupportedType is a placeholder returned in situations where we // encounter a type that isn't supported. type UnsupportedType struct { @@ -1011,7 +1017,15 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ typeCache[off] = it t = &it.TypedefType default: - typ = t + if dictIndex, ok := e.Val(AttrGoDictIndex).(int64); ok { + pt := new(ParametricType) + pt.DictIndex = dictIndex + typ = pt + typeCache[off] = pt + t = &pt.TypedefType + } else { + typ = t + } } typeCache[off] = typ t.Name, _ = e.Val(dwarf.AttrName).(string) diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index edf81804..d67c1e68 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -20,10 +20,13 @@ import ( "github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/goversion" + "github.com/go-delve/delve/pkg/logflags" ) var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented") +const goDictionaryName = ".dict" + // EvalScope is the scope for variable evaluation. Contains the thread, // current location (PC), and canonical frame address. type EvalScope struct { @@ -49,6 +52,8 @@ type EvalScope struct { // The goroutine executing the expression evaluation shall signal that the // evaluation is complete by closing the continueRequest channel. callCtx *callContext + + dictAddr uint64 // dictionary address for instantiated generic functions } type localsFlags uint8 @@ -230,10 +235,35 @@ func (scope *EvalScope) Locals(flags localsFlags) ([]*Variable, error) { } varEntries := reader.Variables(dwarfTree, scope.PC, scope.Line, variablesFlags) + + // look for dictionary entry + if scope.dictAddr == 0 { + for _, entry := range varEntries { + name, _ := entry.Val(dwarf.AttrName).(string) + if name == goDictionaryName { + dictVar, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree, 0) + if err != nil { + logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, err) + } else if dictVar.Unreadable != nil { + logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, dictVar.Unreadable) + } else { + scope.dictAddr, err = readUintRaw(dictVar.mem, dictVar.Addr, int64(scope.BinInfo.Arch.PtrSize())) + if err != nil { + logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, err) + } + } + break + } + } + } + vars := make([]*Variable, 0, len(varEntries)) depths := make([]int, 0, len(varEntries)) for _, entry := range varEntries { - val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree) + if name, _ := entry.Val(dwarf.AttrName).(string); name == goDictionaryName { + continue + } + val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree, scope.dictAddr) if err != nil { // skip variables that we can't parse yet continue @@ -489,7 +519,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { } // Ignore errors trying to extract values - val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry)) + val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry), 0) if val != nil && val.Kind == reflect.Invalid { continue } @@ -526,7 +556,7 @@ func (scope *EvalScope) findGlobalInternal(name string) (*Variable, error) { if err != nil { return nil, err } - return extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry)) + return extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry), 0) } } for _, fn := range scope.BinInfo.Functions { diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 6226edcc..4ce9cc4d 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -566,7 +566,7 @@ func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg * var formalArgVar *Variable if formalArg.dwarfEntry != nil { var err error - formalArgVar, err = extractVarInfoFromEntry(scope.target, formalScope.BinInfo, formalScope.image(), formalScope.Regs, formalScope.Mem, formalArg.dwarfEntry) + formalArgVar, err = extractVarInfoFromEntry(scope.target, formalScope.BinInfo, formalScope.image(), formalScope.Regs, formalScope.Mem, formalArg.dwarfEntry, 0) if err != nil { return err } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 4626a2bf..b52914ac 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -174,6 +174,34 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind return typ, kind, nil } +// resolveParametricType returns the real type of t if t is a parametric +// type, by reading the correct dictionary entry. +func resolveParametricType(tgt *Target, bi *BinaryInfo, mem MemoryReadWriter, t godwarf.Type, dictAddr uint64) (godwarf.Type, error) { + ptyp, _ := t.(*godwarf.ParametricType) + if ptyp == nil { + return t, nil + } + if dictAddr == 0 { + return ptyp.TypedefType.Type, errors.New("parametric type without a dictionary") + } + rtypeAddr, err := readUintRaw(mem, dictAddr+uint64(ptyp.DictIndex*int64(bi.Arch.PtrSize())), int64(bi.Arch.PtrSize())) + if err != nil { + return ptyp.TypedefType.Type, err + } + runtimeType, err := bi.findType("runtime._type") + if err != nil { + return ptyp.TypedefType.Type, err + } + _type := newVariable("", rtypeAddr, runtimeType, bi, mem) + + typ, _, err := runtimeTypeToDIE(_type, 0) + if err != nil { + return ptyp.TypedefType.Type, err + } + + return typ, nil +} + type nameOfRuntimeTypeEntry struct { typename string kind int64 diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 30ad20ff..e8bc08d4 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -18,6 +18,7 @@ import ( "github.com/go-delve/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/goversion" + "github.com/go-delve/delve/pkg/logflags" ) const ( @@ -1135,7 +1136,7 @@ func readVarEntry(entry *godwarf.Tree, image *Image) (name string, typ godwarf.T // Extracts the name and type of a variable from a dwarf entry // then executes the instructions given in the DW_AT_location attribute to grab the variable's address -func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op.DwarfRegisters, mem MemoryReadWriter, entry *godwarf.Tree) (*Variable, error) { +func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op.DwarfRegisters, mem MemoryReadWriter, entry *godwarf.Tree, dictAddr uint64) (*Variable, error) { if entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagVariable { return nil, fmt.Errorf("invalid entry tag, only supports FormalParameter and Variable, got %s", entry.Tag.String()) } @@ -1145,6 +1146,12 @@ func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op. return nil, err } + t, err = resolveParametricType(tgt, bi, mem, t, dictAddr) + if err != nil { + // Log the error, keep going with t, which will be the shape type + logflags.DebuggerLogger().Errorf("could not resolve parametric type of %s", n) + } + addr, pieces, descr, err := bi.Location(entry, dwarf.AttrLocation, regs.PC(), regs, mem) if pieces != nil { var cmem *compositeMemory diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 44474084..74e80110 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -1602,7 +1602,48 @@ func TestCgoEval(t *testing.T) { t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) } } - + } + }) +} + +func TestEvalExpressionGenerics(t *testing.T) { + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) { + t.Skip("generics not supported") + } + + testcases := [][]varTest{ + // testfn[int, float32] + []varTest{ + {"arg1", true, "3", "", "int", nil}, + {"arg2", true, "2.1", "", "float32", nil}, + {"m", true, "map[float32]int [2.1: 3, ]", "", "map[float32]int", nil}, + }, + + // testfn[*astruct, astruct] + []varTest{ + {"arg1", true, "*main.astruct {x: 0, y: 1}", "", "*main.astruct", nil}, + {"arg2", true, "main.astruct {x: 2, y: 3}", "", "main.astruct", nil}, + {"m", true, "map[main.astruct]*main.astruct [{x: 2, y: 3}: *{x: 0, y: 1}, ]", "", "map[main.astruct]*main.astruct", nil}, + }, + } + + withTestProcess("testvariables_generic", t, func(p *proc.Target, fixture protest.Fixture) { + for i, tcs := range testcases { + assertNoError(p.Continue(), t, fmt.Sprintf("Continue() returned an error (%d)", i)) + for _, tc := range tcs { + variable, err := evalVariable(p, tc.name, pnormalLoadConfig) + if tc.err == nil { + assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name)) + assertVariable(t, variable, tc) + } else { + if err == nil { + t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name) + } + if tc.err.Error() != err.Error() { + t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) + } + } + } } }) }