service/dap: implement array, slice, and map paging (#2512)

If the client supports paging, we allow them to fetch array and slice items in chunks that we assume will be of a reasonable size. For example, VS Code requests indexed variables in chunks of 100.

Fixes golang/vscode-go#1518
This commit is contained in:
Suzy Mueller 2021-06-10 12:34:20 -04:00 committed by GitHub
parent fb4d6f1144
commit 30b3cc2c6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 210 additions and 30 deletions

@ -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

@ -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")})

@ -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 {

@ -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)
}

@ -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)

@ -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 {