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:
Suzy Mueller 2021-06-23 19:51:57 -04:00 committed by GitHub
parent 0b38b5d4ec
commit 7b98f5870d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 160 additions and 6 deletions

@ -353,6 +353,14 @@ func (c *Client) IndexedVariablesRequest(variablesReference, start, count int) {
c.send(request) 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. // 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")})

@ -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 { 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())
return 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 := &dap.VariablesResponse{
Response: *newResponse(request.Request), Response: *newResponse(request.Request),
Body: dap.VariablesResponseBody{Variables: children}, Body: dap.VariablesResponseBody{Variables: children},
@ -1785,6 +1797,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
Value: key, Value: key,
VariablesReference: keyref, VariablesReference: keyref,
IndexedVariables: getIndexedVariableCount(keyv), IndexedVariables: getIndexedVariableCount(keyv),
NamedVariables: getNamedVariableCount(keyv),
} }
valvar := dap.Variable{ valvar := dap.Variable{
Name: fmt.Sprintf("[val %d]", v.startIndex+kvIndex), Name: fmt.Sprintf("[val %d]", v.startIndex+kvIndex),
@ -1793,6 +1806,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
Value: val, Value: val,
VariablesReference: valref, VariablesReference: valref,
IndexedVariables: getIndexedVariableCount(valv), IndexedVariables: getIndexedVariableCount(valv),
NamedVariables: getNamedVariableCount(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
@ -1813,9 +1827,11 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
} }
kvvar.VariablesReference = keyref kvvar.VariablesReference = keyref
kvvar.IndexedVariables = getIndexedVariableCount(keyv) kvvar.IndexedVariables = getIndexedVariableCount(keyv)
kvvar.NamedVariables = getNamedVariableCount(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) kvvar.IndexedVariables = getIndexedVariableCount(valv)
kvvar.NamedVariables = getNamedVariableCount(valv)
} }
children = append(children, kvvar) children = append(children, kvvar)
} }
@ -1833,6 +1849,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
Value: cvalue, Value: cvalue,
VariablesReference: cvarref, VariablesReference: cvarref,
IndexedVariables: getIndexedVariableCount(&v.Children[i]), IndexedVariables: getIndexedVariableCount(&v.Children[i]),
NamedVariables: getNamedVariableCount(&v.Children[i]),
} }
} }
default: default:
@ -1872,12 +1889,59 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
Value: cvalue, Value: cvalue,
VariablesReference: cvarref, VariablesReference: cvarref,
IndexedVariables: getIndexedVariableCount(c), IndexedVariables: getIndexedVariableCount(c),
NamedVariables: getNamedVariableCount(c),
} }
} }
} }
return children, nil 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 { func (s *Server) getTypeIfSupported(v *proc.Variable) string {
if !s.clientCapabilities.supportsVariableType { if !s.clientCapabilities.supportsVariableType {
return "" return ""
@ -2147,7 +2211,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, IndexedVariables: getIndexedVariableCount(exprVar)} response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef, IndexedVariables: getIndexedVariableCount(exprVar), NamedVariables: getNamedVariableCount(exprVar)}
} }
s.send(response) s.send(response)
} }

@ -1524,7 +1524,6 @@ func TestVariablesLoading(t *testing.T) {
longarr = client.ExpectVariablesResponse(t) longarr = client.ExpectVariablesResponse(t)
checkChildren(t, longarr, "longarr", 50) checkChildren(t, longarr, "longarr", 50)
checkArrayChildren(t, longarr, "longarr", 50) checkArrayChildren(t, longarr, "longarr", 50)
} }
// Slice not fully loaded based on LoadConfig.MaxArrayValues. // 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 // TestGlobalScopeAndVariables launches the program with showGlobalVariables
// arg set, executes to a breakpoint in the main package and tests that global // 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 // package main variables got loaded. It then steps into a function