service/dap: add string value of byte/rune slice as child (#2541)
Load limit for metadata string is set to MaxStringLen
This commit is contained in:
parent
0b38b5d4ec
commit
7b98f5870d
@ -353,6 +353,14 @@ func (c *Client) IndexedVariablesRequest(variablesReference, start, count int) {
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// NamedVariablesRequest sends a 'variables' request.
|
||||
func (c *Client) NamedVariablesRequest(variablesReference int) {
|
||||
request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
|
||||
request.Arguments.VariablesReference = variablesReference
|
||||
request.Arguments.Filter = "named"
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// TeriminateRequest sends a 'terminate' request.
|
||||
func (c *Client) TerminateRequest() {
|
||||
c.send(&dap.TerminateRequest{Request: *c.newRequest("terminate")})
|
||||
|
||||
@ -1707,11 +1707,23 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
children, err := s.childrenToDAPVariables(v)
|
||||
var children []dap.Variable
|
||||
if request.Arguments.Filter == "named" || request.Arguments.Filter == "" {
|
||||
named, err := s.metadataToDAPVariables(v)
|
||||
if err != nil {
|
||||
s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error())
|
||||
return
|
||||
}
|
||||
children = append(children, named...)
|
||||
}
|
||||
if request.Arguments.Filter == "indexed" || request.Arguments.Filter == "" {
|
||||
indexed, err := s.childrenToDAPVariables(v)
|
||||
if err != nil {
|
||||
s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error())
|
||||
return
|
||||
}
|
||||
children = append(children, indexed...)
|
||||
}
|
||||
response := &dap.VariablesResponse{
|
||||
Response: *newResponse(request.Request),
|
||||
Body: dap.VariablesResponseBody{Variables: children},
|
||||
@ -1785,6 +1797,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
|
||||
Value: key,
|
||||
VariablesReference: keyref,
|
||||
IndexedVariables: getIndexedVariableCount(keyv),
|
||||
NamedVariables: getNamedVariableCount(keyv),
|
||||
}
|
||||
valvar := dap.Variable{
|
||||
Name: fmt.Sprintf("[val %d]", v.startIndex+kvIndex),
|
||||
@ -1793,6 +1806,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
|
||||
Value: val,
|
||||
VariablesReference: valref,
|
||||
IndexedVariables: getIndexedVariableCount(valv),
|
||||
NamedVariables: getNamedVariableCount(valv),
|
||||
}
|
||||
children = append(children, keyvar, valvar)
|
||||
} else { // At least one is a scalar
|
||||
@ -1813,9 +1827,11 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
|
||||
}
|
||||
kvvar.VariablesReference = keyref
|
||||
kvvar.IndexedVariables = getIndexedVariableCount(keyv)
|
||||
kvvar.NamedVariables = getNamedVariableCount(keyv)
|
||||
} else if valref != 0 { // val is a type to be expanded
|
||||
kvvar.VariablesReference = valref
|
||||
kvvar.IndexedVariables = getIndexedVariableCount(valv)
|
||||
kvvar.NamedVariables = getNamedVariableCount(valv)
|
||||
}
|
||||
children = append(children, kvvar)
|
||||
}
|
||||
@ -1833,6 +1849,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
|
||||
Value: cvalue,
|
||||
VariablesReference: cvarref,
|
||||
IndexedVariables: getIndexedVariableCount(&v.Children[i]),
|
||||
NamedVariables: getNamedVariableCount(&v.Children[i]),
|
||||
}
|
||||
}
|
||||
default:
|
||||
@ -1872,12 +1889,59 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
|
||||
Value: cvalue,
|
||||
VariablesReference: cvarref,
|
||||
IndexedVariables: getIndexedVariableCount(c),
|
||||
NamedVariables: getNamedVariableCount(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
|
||||
func getNamedVariableCount(v *proc.Variable) int {
|
||||
namedVars := 0
|
||||
if isListOfBytesOrRunes(v) {
|
||||
// string value of array/slice of bytes and runes.
|
||||
namedVars += 1
|
||||
}
|
||||
return namedVars
|
||||
}
|
||||
|
||||
// metadataToDAPVariables returns the DAP presentation of the referenced variable's metadata.
|
||||
// These are included as named variables
|
||||
func (s *Server) metadataToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variable, error) {
|
||||
var children []dap.Variable
|
||||
|
||||
if isListOfBytesOrRunes(v.Variable) {
|
||||
// Return the string value of []byte or []rune.
|
||||
typeName := api.PrettyTypeName(v.DwarfType)
|
||||
loadExpr := fmt.Sprintf("string(*(*%q)(%#x))", typeName, v.Addr)
|
||||
|
||||
s.log.Debugf("loading %s (type %s) with %s", v.fullyQualifiedNameOrExpr, typeName, loadExpr)
|
||||
// We know that this is an array/slice of Uint8 or Int32, so we will load up to MaxStringLen.
|
||||
config := DefaultLoadConfig
|
||||
config.MaxArrayValues = config.MaxStringLen
|
||||
vLoaded, err := s.debugger.EvalVariableInScope(-1, 0, 0, loadExpr, config)
|
||||
val := s.convertVariableToString(vLoaded)
|
||||
if err == nil {
|
||||
// TODO(suzmue): Add evaluate name. Using string(name) will not get the same result because the
|
||||
// MaxArrayValues is not auto adjusted in evaluate requests like MaxStringLen is adjusted.
|
||||
children = append(children, dap.Variable{
|
||||
Name: "string()",
|
||||
Value: val,
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
|
||||
func isListOfBytesOrRunes(v *proc.Variable) bool {
|
||||
if len(v.Children) > 0 && (v.Kind == reflect.Array || v.Kind == reflect.Slice) {
|
||||
childKind := v.Children[0].RealType.Common().ReflectKind
|
||||
return childKind == reflect.Uint8 || childKind == reflect.Int32
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Server) getTypeIfSupported(v *proc.Variable) string {
|
||||
if !s.clientCapabilities.supportsVariableType {
|
||||
return ""
|
||||
@ -2147,7 +2211,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, IndexedVariables: getIndexedVariableCount(exprVar)}
|
||||
response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef, IndexedVariables: getIndexedVariableCount(exprVar), NamedVariables: getNamedVariableCount(exprVar)}
|
||||
}
|
||||
s.send(response)
|
||||
}
|
||||
|
||||
@ -1524,7 +1524,6 @@ func TestVariablesLoading(t *testing.T) {
|
||||
longarr = client.ExpectVariablesResponse(t)
|
||||
checkChildren(t, longarr, "longarr", 50)
|
||||
checkArrayChildren(t, longarr, "longarr", 50)
|
||||
|
||||
}
|
||||
|
||||
// Slice not fully loaded based on LoadConfig.MaxArrayValues.
|
||||
@ -1789,6 +1788,89 @@ func TestVariablesLoading(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestVariablesMetadata exposes test cases where variables contain metadata that
|
||||
// can be accessed by requesting named variables.
|
||||
func TestVariablesMetadata(t *testing.T) {
|
||||
runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client, "launch",
|
||||
// Launch
|
||||
func() {
|
||||
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
||||
},
|
||||
// Breakpoints are set within the program
|
||||
fixture.Source, []int{},
|
||||
[]onBreakpoint{{
|
||||
execute: func() {},
|
||||
disconnect: false,
|
||||
}, {
|
||||
execute: func() {
|
||||
checkStop(t, client, 1, "main.main", 368)
|
||||
|
||||
client.VariablesRequest(1001) // Locals
|
||||
locals := client.ExpectVariablesResponse(t)
|
||||
|
||||
checkNamedChildren := func(ref int, name, typeStr string, vals []string, evaluate bool) {
|
||||
// byteslice, request named variables
|
||||
client.NamedVariablesRequest(ref)
|
||||
named := client.ExpectVariablesResponse(t)
|
||||
checkChildren(t, named, name, 1)
|
||||
checkVarExact(t, named, 0, "string()", "", "\"tèst\"", "string", false)
|
||||
|
||||
client.VariablesRequest(ref)
|
||||
all := client.ExpectVariablesResponse(t)
|
||||
checkChildren(t, all, name, len(vals)+1)
|
||||
checkVarExact(t, all, 0, "string()", "", "\"tèst\"", "string", false)
|
||||
for i, v := range vals {
|
||||
idx := fmt.Sprintf("[%d]", i)
|
||||
evalName := fmt.Sprintf("%s[%d]", name, i)
|
||||
if evaluate {
|
||||
evalName = fmt.Sprintf("(%s)[%d]", name, i)
|
||||
}
|
||||
checkVarExact(t, all, i+1, idx, evalName, v, typeStr, false)
|
||||
}
|
||||
}
|
||||
|
||||
// byteslice
|
||||
ref := checkVarExactIndexed(t, locals, -1, "byteslice", "byteslice", "[]uint8 len: 5, cap: 5, [116,195,168,115,116]", "[]uint8", true, 5, 1)
|
||||
checkNamedChildren(ref, "byteslice", "uint8", []string{"116", "195", "168", "115", "116"}, false)
|
||||
|
||||
client.EvaluateRequest("byteslice", 0, "")
|
||||
got := client.ExpectEvaluateResponse(t)
|
||||
ref = checkEvalIndexed(t, got, "[]uint8 len: 5, cap: 5, [116,195,168,115,116]", hasChildren, 5, 1)
|
||||
checkNamedChildren(ref, "byteslice", "uint8", []string{"116", "195", "168", "115", "116"}, true)
|
||||
|
||||
// runeslice
|
||||
ref = checkVarExactIndexed(t, locals, -1, "runeslice", "runeslice", "[]int32 len: 4, cap: 4, [116,232,115,116]", "[]int32", true, 4, 1)
|
||||
checkNamedChildren(ref, "runeslice", "int32", []string{"116", "232", "115", "116"}, false)
|
||||
|
||||
client.EvaluateRequest("runeslice", 0, "repl")
|
||||
got = client.ExpectEvaluateResponse(t)
|
||||
ref = checkEvalIndexed(t, got, "[]int32 len: 4, cap: 4, [116,232,115,116]", hasChildren, 4, 1)
|
||||
checkNamedChildren(ref, "runeslice", "int32", []string{"116", "232", "115", "116"}, true)
|
||||
|
||||
// bytearray
|
||||
ref = checkVarExactIndexed(t, locals, -1, "bytearray", "bytearray", "[5]uint8 [116,195,168,115,116]", "[5]uint8", true, 5, 1)
|
||||
checkNamedChildren(ref, "bytearray", "uint8", []string{"116", "195", "168", "115", "116"}, false)
|
||||
|
||||
client.EvaluateRequest("bytearray", 0, "hover")
|
||||
got = client.ExpectEvaluateResponse(t)
|
||||
ref = checkEvalIndexed(t, got, "[5]uint8 [116,195,168,115,116]", hasChildren, 5, 1)
|
||||
checkNamedChildren(ref, "bytearray", "uint8", []string{"116", "195", "168", "115", "116"}, true)
|
||||
|
||||
// runearray
|
||||
ref = checkVarExactIndexed(t, locals, -1, "runearray", "runearray", "[4]int32 [116,232,115,116]", "[4]int32", true, 4, 1)
|
||||
checkNamedChildren(ref, "runearray", "int32", []string{"116", "232", "115", "116"}, false)
|
||||
|
||||
client.EvaluateRequest("runearray", 0, "watch")
|
||||
got = client.ExpectEvaluateResponse(t)
|
||||
ref = checkEvalIndexed(t, got, "[4]int32 [116,232,115,116]", hasChildren, 4, 1)
|
||||
checkNamedChildren(ref, "runearray", "int32", []string{"116", "232", "115", "116"}, true)
|
||||
},
|
||||
disconnect: true,
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
// TestGlobalScopeAndVariables launches the program with showGlobalVariables
|
||||
// arg set, executes to a breakpoint in the main package and tests that global
|
||||
// package main variables got loaded. It then steps into a function
|
||||
|
||||
Loading…
Reference in New Issue
Block a user