diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index fa35095d..6d33ec05 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -1950,6 +1950,31 @@ func (v *Variable) mapAccess(idx *Variable) (*Variable, error) { return nil, fmt.Errorf("key not found") } +// LoadResliced returns a new array, slice or map that starts at index start and contains +// up to cfg.MaxArrayValues children. +func (v *Variable) LoadResliced(start int, cfg LoadConfig) (newV *Variable, err error) { + switch v.Kind { + case reflect.Array, reflect.Slice: + low, high := int64(start), int64(start+cfg.MaxArrayValues) + if high > v.Len { + high = v.Len + } + newV, err = v.reslice(low, high) + if err != nil { + return nil, err + } + case reflect.Map: + newV = v.clone() + newV.Children = nil + newV.loaded = false + newV.mapSkip = start + default: + return nil, fmt.Errorf("variable to reslice is not an array, slice, or map") + } + newV.loadValue(cfg) + return newV, nil +} + func (v *Variable) reslice(low int64, high int64) (*Variable, error) { wrong := false cptrNeedsFakeSlice := false diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go index 8419f1c5..f62a7136 100644 --- a/service/dap/daptest/client.go +++ b/service/dap/daptest/client.go @@ -343,6 +343,16 @@ func (c *Client) VariablesRequest(variablesReference int) { c.send(request) } +// IndexedVariablesRequest sends a 'variables' request. +func (c *Client) IndexedVariablesRequest(variablesReference, start, count int) { + request := &dap.VariablesRequest{Request: *c.newRequest("variables")} + request.Arguments.VariablesReference = variablesReference + request.Arguments.Filter = "indexed" + request.Arguments.Start = start + request.Arguments.Count = count + c.send(request) +} + // TeriminateRequest sends a 'terminate' request. func (c *Client) TerminateRequest() { c.send(&dap.TerminateRequest{Request: *c.newRequest("terminate")}) diff --git a/service/dap/handles.go b/service/dap/handles.go index d01baf91..8c1f9497 100644 --- a/service/dap/handles.go +++ b/service/dap/handles.go @@ -24,6 +24,9 @@ type fullyQualifiedVariable struct { fullyQualifiedNameOrExpr string // True if this represents variable scope isScope bool + // startIndex is the index of the first child for an array or slice. + // This variable represents a chunk of the array, slice or map. + startIndex int } func newHandlesMap() *handlesMap { diff --git a/service/dap/server.go b/service/dap/server.go index 088b1e04..5b8cbc71 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -1588,7 +1588,7 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) { s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to list args", err.Error()) return } - argScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Arguments%s", suffix), Children: slicePtrVarToSliceVar(args)}, "", true} + argScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Arguments%s", suffix), Children: slicePtrVarToSliceVar(args)}, "", true, 0} // Retrieve local variables locals, err := s.debugger.LocalVariables(goid, frame, 0, DefaultLoadConfig) @@ -1596,7 +1596,7 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) { s.sendErrorResponse(request.Request, UnableToListLocals, "Unable to list locals", err.Error()) return } - locScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Locals%s", suffix), Children: slicePtrVarToSliceVar(locals)}, "", true} + locScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Locals%s", suffix), Children: slicePtrVarToSliceVar(locals)}, "", true, 0} scopeArgs := dap.Scope{Name: argScope.Name, VariablesReference: s.variableHandles.create(argScope)} scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)} @@ -1632,7 +1632,7 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) { globScope := &fullyQualifiedVariable{&proc.Variable{ Name: fmt.Sprintf("Globals (package %s)", currPkg), Children: slicePtrVarToSliceVar(globals), - }, currPkg, true} + }, currPkg, true, 0} scopeGlobals := dap.Scope{Name: globScope.Name, VariablesReference: s.variableHandles.create(globScope)} scopes = append(scopes, scopeGlobals) } @@ -1661,6 +1661,18 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) { return } + // If there is a filter applied, we will need to create a new variable that includes + // the values actually needed to load. This cannot be done when loading the parent + // node, since it is unknown at that point which children will need to be loaded. + if request.Arguments.Filter == "indexed" { + var err error + v, err = s.maybeLoadResliced(v, request.Arguments.Start, request.Arguments.Count) + if err != nil { + s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error()) + return + } + } + children, err := s.childrenToDAPVariables(v) if err != nil { s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error()) @@ -1673,6 +1685,30 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) { s.send(response) } +func (s *Server) maybeLoadResliced(v *fullyQualifiedVariable, start, count int) (*fullyQualifiedVariable, error) { + if start == 0 && count == len(v.Children) { + // If we have already loaded the correct children, + // just return the variable. + return v, nil + } + indexedLoadConfig := DefaultLoadConfig + indexedLoadConfig.MaxArrayValues = count + newV, err := s.debugger.LoadResliced(v.Variable, start, indexedLoadConfig) + if err != nil { + return nil, err + } + return &fullyQualifiedVariable{newV, v.fullyQualifiedNameOrExpr, false, start}, nil +} + +func getIndexedVariableCount(c *proc.Variable) int { + indexedVars := 0 + switch c.Kind { + case reflect.Array, reflect.Slice, reflect.Map: + indexedVars = int(c.Len) + } + return indexedVars +} + // childrenToDAPVariables returns the DAP presentation of the referenced variable's children. func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variable, error) { // TODO(polina): consider convertVariableToString instead of convertVariable @@ -1709,18 +1745,20 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab // Otherwise, we must return separate variables for both. if keyref > 0 && valref > 0 { // Both are not scalars keyvar := dap.Variable{ - Name: fmt.Sprintf("[key %d]", kvIndex), + Name: fmt.Sprintf("[key %d]", v.startIndex+kvIndex), EvaluateName: keyexpr, Type: keyType, Value: key, VariablesReference: keyref, + IndexedVariables: getIndexedVariableCount(keyv), } valvar := dap.Variable{ - Name: fmt.Sprintf("[val %d]", kvIndex), + Name: fmt.Sprintf("[val %d]", v.startIndex+kvIndex), EvaluateName: valexpr, Type: valType, Value: val, VariablesReference: valref, + IndexedVariables: getIndexedVariableCount(valv), } children = append(children, keyvar, valvar) } else { // At least one is a scalar @@ -1740,8 +1778,10 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab kvvar.Name = fmt.Sprintf("%s... @ %#x", key[0:DefaultLoadConfig.MaxStringLen], keyv.Addr) } kvvar.VariablesReference = keyref + kvvar.IndexedVariables = getIndexedVariableCount(keyv) } else if valref != 0 { // val is a type to be expanded kvvar.VariablesReference = valref + kvvar.IndexedVariables = getIndexedVariableCount(valv) } children = append(children, kvvar) } @@ -1749,14 +1789,16 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab case reflect.Slice, reflect.Array: children = make([]dap.Variable, len(v.Children)) for i := range v.Children { - cfqname := fmt.Sprintf("%s[%d]", v.fullyQualifiedNameOrExpr, i) + idx := v.startIndex + i + cfqname := fmt.Sprintf("%s[%d]", v.fullyQualifiedNameOrExpr, idx) cvalue, cvarref := s.convertVariable(&v.Children[i], cfqname) children[i] = dap.Variable{ - Name: fmt.Sprintf("[%d]", i), + Name: fmt.Sprintf("[%d]", idx), EvaluateName: cfqname, Type: s.getTypeIfSupported(&v.Children[i]), Value: cvalue, VariablesReference: cvarref, + IndexedVariables: getIndexedVariableCount(&v.Children[i]), } } default: @@ -1795,6 +1837,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab Type: s.getTypeIfSupported(c), Value: cvalue, VariablesReference: cvarref, + IndexedVariables: getIndexedVariableCount(c), } } } @@ -1849,7 +1892,7 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s if opts&skipRef != 0 { return 0 } - return s.variableHandles.create(&fullyQualifiedVariable{v, qualifiedNameOrExpr, false /*not a scope*/}) + return s.variableHandles.create(&fullyQualifiedVariable{v, qualifiedNameOrExpr, false /*not a scope*/, 0}) } value = api.ConvertVar(v).SinglelineString() if v.Unreadable != nil { @@ -1858,15 +1901,13 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s // Some of the types might be fully or partially not loaded based on LoadConfig. // Those that are fully missing (e.g. due to hitting MaxVariableRecurse), can be reloaded in place. - // Those that are partially missing (e.g. MaxArrayValues from a large array), need a more creative solution - // that is still to be determined. For now, clearly communicate when that happens with additional value labels. - // TODO(polina): look into layered/paged loading for truncated strings, arrays, maps and structs. var reloadVariable = func(v *proc.Variable, qualifiedNameOrExpr string) (value string) { // We might be loading variables from the frame that's not topmost, so use // frame-independent address-based expression, not fully-qualified name as per // https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables. - // TODO(polina): Get *proc.Variable object from debugger instead. Export and set v.loaded to false - // and call v.loadValue gain. It's more efficient, and it's guaranteed to keep working with generics. + // TODO(polina): Get *proc.Variable object from debugger instead. Export a function to set v.loaded to false + // and call v.loadValue gain with a different load config. It's more efficient, and it's guaranteed to keep + // working with generics. value = api.ConvertVar(v).SinglelineString() typeName := api.PrettyTypeName(v.DwarfType) loadExpr := fmt.Sprintf("*(*%q)(%#x)", typeName, v.Addr) @@ -1923,7 +1964,7 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s if v.Len > int64(len(v.Children)) { // Not fully loaded if v.Base != 0 && len(v.Children) == 0 { // Fully missing value = reloadVariable(v, qualifiedNameOrExpr) - } else { // Partially missing (TODO) + } else if !s.clientCapabilities.supportsVariablePaging { value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children), v.Len) + value } } @@ -1934,7 +1975,7 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s if v.Len > int64(len(v.Children)/2) { // Not fully loaded if len(v.Children) == 0 { // Fully missing value = reloadVariable(v, qualifiedNameOrExpr) - } else { // Partially missing (TODO) + } else if !s.clientCapabilities.supportsVariablePaging { value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children)/2, v.Len) + value } } @@ -2033,7 +2074,7 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) { } response.Body = dap.EvaluateResponseBody{ Result: strings.TrimRight(retVarsAsStr, ", "), - VariablesReference: s.variableHandles.create(&fullyQualifiedVariable{retVarsAsVar, "", false /*not a scope*/}), + VariablesReference: s.variableHandles.create(&fullyQualifiedVariable{retVarsAsVar, "", false /*not a scope*/, 0}), } } } else { // {expression} @@ -2064,7 +2105,7 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) { opts |= showFullValue } exprVal, exprRef := s.convertVariableWithOpts(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression), opts) - response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef} + response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef, IndexedVariables: getIndexedVariableCount(exprVar)} } s.send(response) } diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 90d1b076..6a82d11e 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -667,7 +667,7 @@ func checkChildren(t *testing.T, got *dap.VariablesResponse, parentName string, // useExactMatch - true if name, evalName and value are to be compared to exactly, false if to be used as regex // hasRef - true if the variable should have children and therefore a non-0 variable reference // ref - reference to retrieve children of this variable (0 if none) -func checkVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, useExactMatch, hasRef bool) (ref int) { +func checkVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, useExactMatch, hasRef bool, indexed, named int) (ref int) { t.Helper() if len(got.Body.Variables) <= i { t.Errorf("\ngot len=%d (children=%#v)\nwant len>%d", len(got.Body.Variables), got.Body.Variables, i) @@ -723,19 +723,37 @@ func checkVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, v if !matchedType { t.Errorf("\ngot %s=%q\nwant %q", name, goti.Type, typ) } + if indexed >= 0 && goti.IndexedVariables != indexed { + t.Errorf("\ngot %s=%d indexed\nwant %d indexed", name, goti.IndexedVariables, indexed) + } + if named >= 0 && goti.NamedVariables != named { + t.Errorf("\ngot %s=%d named\nwant %d named", name, goti.NamedVariables, named) + } return goti.VariablesReference } // checkVarExact is a helper like checkVar that matches value exactly. func checkVarExact(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) { t.Helper() - return checkVar(t, got, i, name, evalName, value, typ, true, hasRef) + return checkVarExactIndexed(t, got, i, name, evalName, value, typ, hasRef, -1, -1) +} + +// checkVarExact is a helper like checkVar that matches value exactly. +func checkVarExactIndexed(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool, indexed, named int) (ref int) { + t.Helper() + return checkVar(t, got, i, name, evalName, value, typ, true, hasRef, indexed, named) } // checkVarRegex is a helper like checkVar that treats value, evalName or name as a regex. func checkVarRegex(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) { t.Helper() - return checkVar(t, got, i, name, evalName, value, typ, false, hasRef) + return checkVarRegexIndexed(t, got, i, name, evalName, value, typ, hasRef, -1, -1) +} + +// checkVarRegex is a helper like checkVar that treats value, evalName or name as a regex. +func checkVarRegexIndexed(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool, indexed, named int) (ref int) { + t.Helper() + return checkVar(t, got, i, name, evalName, value, typ, false, hasRef, indexed, named) } func expectMessageFilterStopped(t *testing.T, client *daptest.Client) dap.Message { @@ -1481,28 +1499,91 @@ func TestVariablesLoading(t *testing.T) { // String partially missing based on LoadConfig.MaxStringLen checkVarExact(t, locals, -1, "longstr", "longstr", "\"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more\"", "string", noChildren) - // Array partially missing based on LoadConfig.MaxArrayValues - ref := checkVarExact(t, locals, -1, "longarr", "longarr", "(loaded 64/100) [100]int [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+36 more]", "[100]int", hasChildren) + checkArrayChildren := func(t *testing.T, longarr *dap.VariablesResponse, parentName string, start int) { + t.Helper() + for i, child := range longarr.Body.Variables { + idx := start + i + if child.Name != fmt.Sprintf("[%d]", idx) || child.EvaluateName != fmt.Sprintf("%s[%d]", parentName, idx) { + t.Errorf("Expected %s[%d] to have Name=\"[%d]\" EvaluateName=\"%s[%d]\", got %#v", parentName, idx, idx, parentName, idx, child) + } + } + } + + // Array not fully loaded based on LoadConfig.MaxArrayValues. + // Expect to be able to load array by paging. + ref := checkVarExactIndexed(t, locals, -1, "longarr", "longarr", "[100]int [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+36 more]", "[100]int", hasChildren, 100, 0) if ref > 0 { client.VariablesRequest(ref) longarr := client.ExpectVariablesResponse(t) checkChildren(t, longarr, "longarr", 64) + checkArrayChildren(t, longarr, "longarr", 0) + + client.IndexedVariablesRequest(ref, 0, 100) + longarr = client.ExpectVariablesResponse(t) + checkChildren(t, longarr, "longarr", 100) + checkArrayChildren(t, longarr, "longarr", 0) + + client.IndexedVariablesRequest(ref, 50, 50) + longarr = client.ExpectVariablesResponse(t) + checkChildren(t, longarr, "longarr", 50) + checkArrayChildren(t, longarr, "longarr", 50) + } - // Slice partially missing based on LoadConfig.MaxArrayValues - ref = checkVarExact(t, locals, -1, "longslice", "longslice", "(loaded 64/100) []int len: 100, cap: 100, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+36 more]", "[]int", hasChildren) + // Slice not fully loaded based on LoadConfig.MaxArrayValues. + // Expect to be able to load slice by paging. + ref = checkVarExactIndexed(t, locals, -1, "longslice", "longslice", "[]int len: 100, cap: 100, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+36 more]", "[]int", hasChildren, 100, 0) if ref > 0 { client.VariablesRequest(ref) longarr := client.ExpectVariablesResponse(t) checkChildren(t, longarr, "longslice", 64) + checkArrayChildren(t, longarr, "longslice", 0) + + client.IndexedVariablesRequest(ref, 0, 100) + longarr = client.ExpectVariablesResponse(t) + checkChildren(t, longarr, "longslice", 100) + checkArrayChildren(t, longarr, "longslice", 0) + + client.IndexedVariablesRequest(ref, 50, 50) + longarr = client.ExpectVariablesResponse(t) + checkChildren(t, longarr, "longslice", 50) + checkArrayChildren(t, longarr, "longslice", 50) } - // Map partially missing based on LoadConfig.MaxArrayValues - ref = checkVarRegex(t, locals, -1, "m1", "m1", `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren) + // Map not fully loaded based on LoadConfig.MaxArrayValues + // Expect to be able to load map by paging. + ref = checkVarRegexIndexed(t, locals, -1, "m1", "m1", `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren, 66, 0) if ref > 0 { client.VariablesRequest(ref) m1 := client.ExpectVariablesResponse(t) checkChildren(t, m1, "m1", 64) + + client.IndexedVariablesRequest(ref, 0, 66) + m1 = client.ExpectVariablesResponse(t) + checkChildren(t, m1, "m1", 66) + + client.IndexedVariablesRequest(ref, 0, 33) + m1part1 := client.ExpectVariablesResponse(t) + checkChildren(t, m1part1, "m1", 33) + + client.IndexedVariablesRequest(ref, 33, 33) + m1part2 := client.ExpectVariablesResponse(t) + checkChildren(t, m1part2, "m1", 33) + + if len(m1part1.Body.Variables)+len(m1part2.Body.Variables) == len(m1.Body.Variables) { + for i, got := range m1part1.Body.Variables { + want := m1.Body.Variables[i] + if got.Name != want.Name || got.Value != want.Value { + t.Errorf("got %#v, want Name=%q Value=%q", got, want.Name, want.Value) + } + } + for i, got := range m1part2.Body.Variables { + want := m1.Body.Variables[i+len(m1part1.Body.Variables)] + if got.Name != want.Name || got.Value != want.Value { + t.Errorf("got %#v, want Name=%q Value=%q", got, want.Name, want.Value) + } + } + } } // Struct partially missing based on LoadConfig.MaxStructFields @@ -2570,6 +2651,18 @@ func checkEval(t *testing.T, got *dap.EvaluateResponse, value string, hasRef boo return got.Body.VariablesReference } +// checkEvalIndexed is a helper for verifying the values within an EvaluateResponse. +// value - the value of the evaluated expression +// hasRef - true if the evaluated expression should have children and therefore a non-0 variable reference +// ref - reference to retrieve children of this evaluated expression (0 if none) +func checkEvalIndexed(t *testing.T, got *dap.EvaluateResponse, value string, hasRef bool, indexed, named int) (ref int) { + t.Helper() + if got.Body.Result != value || (got.Body.VariablesReference > 0) != hasRef || got.Body.IndexedVariables != indexed || got.Body.NamedVariables != named { + t.Errorf("\ngot %#v\nwant Result=%q hasRef=%t IndexedVariables=%d NamedVariables=%d", got, value, hasRef, indexed, named) + } + return got.Body.VariablesReference +} + func checkEvalRegex(t *testing.T, got *dap.EvaluateResponse, valueRegex string, hasRef bool) (ref int) { t.Helper() matched, _ := regexp.MatchString(valueRegex, got.Body.Result) @@ -2612,7 +2705,7 @@ func TestEvaluateRequest(t *testing.T) { // Variable lookup that's not fully loaded client.EvaluateRequest("ba", 1000, "this context will be ignored") got = client.ExpectEvaluateResponse(t) - checkEval(t, got, "(loaded 64/200) []int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", hasChildren) + checkEvalIndexed(t, got, "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", hasChildren, 200, 0) // All (binary and unary) on basic types except <-, ++ and -- client.EvaluateRequest("1+1", 1000, "this context will be ignored") @@ -2644,7 +2737,7 @@ func TestEvaluateRequest(t *testing.T) { // Type casts between string, []byte and []rune client.EvaluateRequest("[]byte(\"ABC€\")", 1000, "this context will be ignored") got = client.ExpectEvaluateResponse(t) - checkEval(t, got, "[]uint8 len: 6, cap: 6, [65,66,67,226,130,172]", noChildren) + checkEvalIndexed(t, got, "[]uint8 len: 6, cap: 6, [65,66,67,226,130,172]", noChildren, 6, 0) // Struct member access (i.e. somevar.memberfield) client.EvaluateRequest("ms.Nest.Level", 1000, "this context will be ignored") @@ -2783,8 +2876,8 @@ func TestEvaluateRequestLongStrLargeValue(t *testing.T) { checkVarExact(t, locals, -1, "m6", "m6", `main.C {s: `+longstrTruncated+`}`, "main.C", hasChildren) // large array - m1 := `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.\+2 more\]` - m1Truncated := `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.` + m1 := `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]` + m1Truncated := `map\[string\]main\.astruct \[.+\.\.\.` client.EvaluateRequest("m1", 0, evalContext) got3 := client.ExpectEvaluateResponse(t) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index e4505b31..64e73645 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -1464,6 +1464,14 @@ func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, symbol str return s.EvalVariable(symbol, cfg) } +// LoadResliced will attempt to 'reslice' a map, array or slice so that the values +// up to cfg.MaxArrayValues children are loaded starting from index start. +func (d *Debugger) LoadResliced(v *proc.Variable, start int, cfg proc.LoadConfig) (*proc.Variable, error) { + d.targetMutex.Lock() + defer d.targetMutex.Unlock() + return v.LoadResliced(start, cfg) +} + // SetVariableInScope will set the value of the variable represented by // 'symbol' to the value given, in the given scope. func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, value string) error {