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:
parent
fb4d6f1144
commit
30b3cc2c6f
@ -1950,6 +1950,31 @@ func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
|
|||||||
return nil, fmt.Errorf("key not found")
|
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) {
|
func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
|
||||||
wrong := false
|
wrong := false
|
||||||
cptrNeedsFakeSlice := false
|
cptrNeedsFakeSlice := false
|
||||||
|
@ -343,6 +343,16 @@ func (c *Client) VariablesRequest(variablesReference int) {
|
|||||||
c.send(request)
|
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.
|
// TeriminateRequest sends a 'terminate' request.
|
||||||
func (c *Client) TerminateRequest() {
|
func (c *Client) TerminateRequest() {
|
||||||
c.send(&dap.TerminateRequest{Request: *c.newRequest("terminate")})
|
c.send(&dap.TerminateRequest{Request: *c.newRequest("terminate")})
|
||||||
|
@ -24,6 +24,9 @@ type fullyQualifiedVariable struct {
|
|||||||
fullyQualifiedNameOrExpr string
|
fullyQualifiedNameOrExpr string
|
||||||
// True if this represents variable scope
|
// True if this represents variable scope
|
||||||
isScope bool
|
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 {
|
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())
|
s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to list args", err.Error())
|
||||||
return
|
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
|
// Retrieve local variables
|
||||||
locals, err := s.debugger.LocalVariables(goid, frame, 0, DefaultLoadConfig)
|
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())
|
s.sendErrorResponse(request.Request, UnableToListLocals, "Unable to list locals", err.Error())
|
||||||
return
|
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)}
|
scopeArgs := dap.Scope{Name: argScope.Name, VariablesReference: s.variableHandles.create(argScope)}
|
||||||
scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)}
|
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{
|
globScope := &fullyQualifiedVariable{&proc.Variable{
|
||||||
Name: fmt.Sprintf("Globals (package %s)", currPkg),
|
Name: fmt.Sprintf("Globals (package %s)", currPkg),
|
||||||
Children: slicePtrVarToSliceVar(globals),
|
Children: slicePtrVarToSliceVar(globals),
|
||||||
}, currPkg, true}
|
}, currPkg, true, 0}
|
||||||
scopeGlobals := dap.Scope{Name: globScope.Name, VariablesReference: s.variableHandles.create(globScope)}
|
scopeGlobals := dap.Scope{Name: globScope.Name, VariablesReference: s.variableHandles.create(globScope)}
|
||||||
scopes = append(scopes, scopeGlobals)
|
scopes = append(scopes, scopeGlobals)
|
||||||
}
|
}
|
||||||
@ -1661,6 +1661,18 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) {
|
|||||||
return
|
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)
|
children, err := s.childrenToDAPVariables(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error())
|
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)
|
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.
|
// childrenToDAPVariables returns the DAP presentation of the referenced variable's children.
|
||||||
func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variable, error) {
|
func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variable, error) {
|
||||||
// TODO(polina): consider convertVariableToString instead of convertVariable
|
// 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.
|
// Otherwise, we must return separate variables for both.
|
||||||
if keyref > 0 && valref > 0 { // Both are not scalars
|
if keyref > 0 && valref > 0 { // Both are not scalars
|
||||||
keyvar := dap.Variable{
|
keyvar := dap.Variable{
|
||||||
Name: fmt.Sprintf("[key %d]", kvIndex),
|
Name: fmt.Sprintf("[key %d]", v.startIndex+kvIndex),
|
||||||
EvaluateName: keyexpr,
|
EvaluateName: keyexpr,
|
||||||
Type: keyType,
|
Type: keyType,
|
||||||
Value: key,
|
Value: key,
|
||||||
VariablesReference: keyref,
|
VariablesReference: keyref,
|
||||||
|
IndexedVariables: getIndexedVariableCount(keyv),
|
||||||
}
|
}
|
||||||
valvar := dap.Variable{
|
valvar := dap.Variable{
|
||||||
Name: fmt.Sprintf("[val %d]", kvIndex),
|
Name: fmt.Sprintf("[val %d]", v.startIndex+kvIndex),
|
||||||
EvaluateName: valexpr,
|
EvaluateName: valexpr,
|
||||||
Type: valType,
|
Type: valType,
|
||||||
Value: val,
|
Value: val,
|
||||||
VariablesReference: valref,
|
VariablesReference: valref,
|
||||||
|
IndexedVariables: getIndexedVariableCount(valv),
|
||||||
}
|
}
|
||||||
children = append(children, keyvar, valvar)
|
children = append(children, keyvar, valvar)
|
||||||
} else { // At least one is a scalar
|
} 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.Name = fmt.Sprintf("%s... @ %#x", key[0:DefaultLoadConfig.MaxStringLen], keyv.Addr)
|
||||||
}
|
}
|
||||||
kvvar.VariablesReference = keyref
|
kvvar.VariablesReference = keyref
|
||||||
|
kvvar.IndexedVariables = getIndexedVariableCount(keyv)
|
||||||
} else if valref != 0 { // val is a type to be expanded
|
} else if valref != 0 { // val is a type to be expanded
|
||||||
kvvar.VariablesReference = valref
|
kvvar.VariablesReference = valref
|
||||||
|
kvvar.IndexedVariables = getIndexedVariableCount(valv)
|
||||||
}
|
}
|
||||||
children = append(children, kvvar)
|
children = append(children, kvvar)
|
||||||
}
|
}
|
||||||
@ -1749,14 +1789,16 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
|
|||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
children = make([]dap.Variable, len(v.Children))
|
children = make([]dap.Variable, len(v.Children))
|
||||||
for i := range 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)
|
cvalue, cvarref := s.convertVariable(&v.Children[i], cfqname)
|
||||||
children[i] = dap.Variable{
|
children[i] = dap.Variable{
|
||||||
Name: fmt.Sprintf("[%d]", i),
|
Name: fmt.Sprintf("[%d]", idx),
|
||||||
EvaluateName: cfqname,
|
EvaluateName: cfqname,
|
||||||
Type: s.getTypeIfSupported(&v.Children[i]),
|
Type: s.getTypeIfSupported(&v.Children[i]),
|
||||||
Value: cvalue,
|
Value: cvalue,
|
||||||
VariablesReference: cvarref,
|
VariablesReference: cvarref,
|
||||||
|
IndexedVariables: getIndexedVariableCount(&v.Children[i]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -1795,6 +1837,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
|
|||||||
Type: s.getTypeIfSupported(c),
|
Type: s.getTypeIfSupported(c),
|
||||||
Value: cvalue,
|
Value: cvalue,
|
||||||
VariablesReference: cvarref,
|
VariablesReference: cvarref,
|
||||||
|
IndexedVariables: getIndexedVariableCount(c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1849,7 +1892,7 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
|
|||||||
if opts&skipRef != 0 {
|
if opts&skipRef != 0 {
|
||||||
return 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()
|
value = api.ConvertVar(v).SinglelineString()
|
||||||
if v.Unreadable != nil {
|
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.
|
// 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 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) {
|
var reloadVariable = func(v *proc.Variable, qualifiedNameOrExpr string) (value string) {
|
||||||
// We might be loading variables from the frame that's not topmost, so use
|
// 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
|
// 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.
|
// 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
|
// TODO(polina): Get *proc.Variable object from debugger instead. Export a function to set v.loaded to false
|
||||||
// and call v.loadValue gain. It's more efficient, and it's guaranteed to keep working with generics.
|
// 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()
|
value = api.ConvertVar(v).SinglelineString()
|
||||||
typeName := api.PrettyTypeName(v.DwarfType)
|
typeName := api.PrettyTypeName(v.DwarfType)
|
||||||
loadExpr := fmt.Sprintf("*(*%q)(%#x)", typeName, v.Addr)
|
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.Len > int64(len(v.Children)) { // Not fully loaded
|
||||||
if v.Base != 0 && len(v.Children) == 0 { // Fully missing
|
if v.Base != 0 && len(v.Children) == 0 { // Fully missing
|
||||||
value = reloadVariable(v, qualifiedNameOrExpr)
|
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
|
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 v.Len > int64(len(v.Children)/2) { // Not fully loaded
|
||||||
if len(v.Children) == 0 { // Fully missing
|
if len(v.Children) == 0 { // Fully missing
|
||||||
value = reloadVariable(v, qualifiedNameOrExpr)
|
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
|
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{
|
response.Body = dap.EvaluateResponseBody{
|
||||||
Result: strings.TrimRight(retVarsAsStr, ", "),
|
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}
|
} else { // {expression}
|
||||||
@ -2064,7 +2105,7 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
|
|||||||
opts |= showFullValue
|
opts |= showFullValue
|
||||||
}
|
}
|
||||||
exprVal, exprRef := s.convertVariableWithOpts(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression), opts)
|
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)
|
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
|
// 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
|
// 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)
|
// 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()
|
t.Helper()
|
||||||
if len(got.Body.Variables) <= i {
|
if len(got.Body.Variables) <= i {
|
||||||
t.Errorf("\ngot len=%d (children=%#v)\nwant len>%d", len(got.Body.Variables), 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 {
|
if !matchedType {
|
||||||
t.Errorf("\ngot %s=%q\nwant %q", name, goti.Type, typ)
|
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
|
return goti.VariablesReference
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkVarExact is a helper like checkVar that matches value exactly.
|
// 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) {
|
func checkVarExact(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) {
|
||||||
t.Helper()
|
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.
|
// 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) {
|
func checkVarRegex(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) {
|
||||||
t.Helper()
|
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 {
|
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
|
// String partially missing based on LoadConfig.MaxStringLen
|
||||||
checkVarExact(t, locals, -1, "longstr", "longstr", "\"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more\"", "string", noChildren)
|
checkVarExact(t, locals, -1, "longstr", "longstr", "\"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more\"", "string", noChildren)
|
||||||
|
|
||||||
// Array partially missing based on LoadConfig.MaxArrayValues
|
checkArrayChildren := func(t *testing.T, longarr *dap.VariablesResponse, parentName string, start int) {
|
||||||
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)
|
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 {
|
if ref > 0 {
|
||||||
client.VariablesRequest(ref)
|
client.VariablesRequest(ref)
|
||||||
longarr := client.ExpectVariablesResponse(t)
|
longarr := client.ExpectVariablesResponse(t)
|
||||||
checkChildren(t, longarr, "longarr", 64)
|
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
|
// Slice not fully loaded 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)
|
// 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 {
|
if ref > 0 {
|
||||||
client.VariablesRequest(ref)
|
client.VariablesRequest(ref)
|
||||||
longarr := client.ExpectVariablesResponse(t)
|
longarr := client.ExpectVariablesResponse(t)
|
||||||
checkChildren(t, longarr, "longslice", 64)
|
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
|
// Map not fully loaded based on LoadConfig.MaxArrayValues
|
||||||
ref = checkVarRegex(t, locals, -1, "m1", "m1", `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
|
// 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 {
|
if ref > 0 {
|
||||||
client.VariablesRequest(ref)
|
client.VariablesRequest(ref)
|
||||||
m1 := client.ExpectVariablesResponse(t)
|
m1 := client.ExpectVariablesResponse(t)
|
||||||
checkChildren(t, m1, "m1", 64)
|
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
|
// 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
|
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) {
|
func checkEvalRegex(t *testing.T, got *dap.EvaluateResponse, valueRegex string, hasRef bool) (ref int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
matched, _ := regexp.MatchString(valueRegex, got.Body.Result)
|
matched, _ := regexp.MatchString(valueRegex, got.Body.Result)
|
||||||
@ -2612,7 +2705,7 @@ func TestEvaluateRequest(t *testing.T) {
|
|||||||
// Variable lookup that's not fully loaded
|
// Variable lookup that's not fully loaded
|
||||||
client.EvaluateRequest("ba", 1000, "this context will be ignored")
|
client.EvaluateRequest("ba", 1000, "this context will be ignored")
|
||||||
got = client.ExpectEvaluateResponse(t)
|
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 --
|
// All (binary and unary) on basic types except <-, ++ and --
|
||||||
client.EvaluateRequest("1+1", 1000, "this context will be ignored")
|
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
|
// Type casts between string, []byte and []rune
|
||||||
client.EvaluateRequest("[]byte(\"ABC€\")", 1000, "this context will be ignored")
|
client.EvaluateRequest("[]byte(\"ABC€\")", 1000, "this context will be ignored")
|
||||||
got = client.ExpectEvaluateResponse(t)
|
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)
|
// Struct member access (i.e. somevar.memberfield)
|
||||||
client.EvaluateRequest("ms.Nest.Level", 1000, "this context will be ignored")
|
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)
|
checkVarExact(t, locals, -1, "m6", "m6", `main.C {s: `+longstrTruncated+`}`, "main.C", hasChildren)
|
||||||
|
|
||||||
// large array
|
// large array
|
||||||
m1 := `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`
|
m1 := `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`
|
||||||
m1Truncated := `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.`
|
m1Truncated := `map\[string\]main\.astruct \[.+\.\.\.`
|
||||||
|
|
||||||
client.EvaluateRequest("m1", 0, evalContext)
|
client.EvaluateRequest("m1", 0, evalContext)
|
||||||
got3 := client.ExpectEvaluateResponse(t)
|
got3 := client.ExpectEvaluateResponse(t)
|
||||||
|
@ -1464,6 +1464,14 @@ func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, symbol str
|
|||||||
return s.EvalVariable(symbol, cfg)
|
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
|
// SetVariableInScope will set the value of the variable represented by
|
||||||
// 'symbol' to the value given, in the given scope.
|
// 'symbol' to the value given, in the given scope.
|
||||||
func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, value string) error {
|
func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, value string) error {
|
||||||
|
Loading…
Reference in New Issue
Block a user