service/dap: warn users of debugging optimized functions (#2475)

* service/dap: warn users of debugging optimized functions

* Add test for optimized scopes

* service/dap: warn users of debugging optimized functions

* rename functionscope

* update warning message
This commit is contained in:
Suzy Mueller 2021-05-17 12:21:15 -04:00 committed by GitHub
parent 11cf6e689f
commit 1e9c5c3b07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 86 additions and 3 deletions

@ -1338,13 +1338,23 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) {
goid := sf.(stackFrame).goroutineID
frame := sf.(stackFrame).frameIndex
// Check if the function is optimized.
fn, err := s.debugger.Function(goid, frame, 0, DefaultLoadConfig)
if fn == nil || err != nil {
s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to find enclosing function", err.Error())
return
}
suffix := ""
if fn.Optimized() {
suffix = " (warning: optimized function)"
}
// Retrieve arguments
args, err := s.debugger.FunctionArguments(goid, frame, 0, DefaultLoadConfig)
if err != nil {
s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to list args", err.Error())
return
}
argScope := &fullyQualifiedVariable{&proc.Variable{Name: "Arguments", Children: slicePtrVarToSliceVar(args)}, "", true}
argScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Arguments%s", suffix), Children: slicePtrVarToSliceVar(args)}, "", true}
// Retrieve local variables
locals, err := s.debugger.LocalVariables(goid, frame, 0, DefaultLoadConfig)
@ -1352,7 +1362,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: "Locals", Children: slicePtrVarToSliceVar(locals)}, "", true}
locScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Locals%s", suffix), Children: slicePtrVarToSliceVar(locals)}, "", true}
scopeArgs := dap.Scope{Name: argScope.Name, VariablesReference: s.variableHandles.create(argScope)}
scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)}

@ -47,7 +47,11 @@ func TestMain(m *testing.M) {
// name is for _fixtures/<name>.go
func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture)) {
var buildFlags protest.BuildFlags = protest.AllNonOptimized
runTestBuildFlags(t, name, test, protest.AllNonOptimized)
}
// name is for _fixtures/<name>.go
func runTestBuildFlags(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture), buildFlags protest.BuildFlags) {
fixture := protest.BuildFixture(name, buildFlags)
// Start the DAP server.
@ -1383,6 +1387,63 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
})
}
// TestScopesRequestsOptimized executes to a breakpoint and tests different
// that the names of the "Locals" and "Arguments" scopes are correctly annotated with
// a warning about debugging an optimized function.
func TestScopesRequestsOptimized(t *testing.T) {
runTestBuildFlags(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
runDebugSessionWithBPs(t, client, "launch",
// Launch
func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
})
},
// Breakpoints are set within the program
fixture.Source, []int{},
[]onBreakpoint{{
// Stop at first breakpoint
execute: func() {
client.StackTraceRequest(1, 0, 20)
stack := client.ExpectStackTraceResponse(t)
startLineno := 66
if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
// Go1.15 on windows inserts a NOP after the call to
// runtime.Breakpoint and marks it same line as the
// runtime.Breakpoint call, making this flaky, so skip the line check.
startLineno = -1
}
expectStackFrames(t, stack, "main.foobar", startLineno, 1000, 4, 4)
client.ScopesRequest(1000)
scopes := client.ExpectScopesResponse(t)
expectScope(t, scopes, 0, "Arguments (warning: optimized function)", 1000)
expectScope(t, scopes, 1, "Locals (warning: optimized function)", 1001)
expectScope(t, scopes, 2, "Globals (package main)", 1002)
},
disconnect: false,
}, {
// Stop at second breakpoint
execute: func() {
// Frame ids get reset at each breakpoint.
client.StackTraceRequest(1, 0, 20)
stack := client.ExpectStackTraceResponse(t)
expectStackFrames(t, stack, "main.barfoo", 27, 1000, 5, 5)
client.ScopesRequest(1000)
scopes := client.ExpectScopesResponse(t)
expectScope(t, scopes, 0, "Arguments (warning: optimized function)", 1000)
expectScope(t, scopes, 1, "Locals (warning: optimized function)", 1001)
expectScope(t, scopes, 2, "Globals (package main)", 1002)
},
disconnect: false,
}})
},
protest.EnableOptimization)
}
// TestVariablesLoading exposes test cases where variables might be partiall or
// fully unloaded.
func TestVariablesLoading(t *testing.T) {

@ -1357,6 +1357,18 @@ func (d *Debugger) FunctionArguments(goid, frame, deferredCall int, cfg proc.Loa
return s.FunctionArguments(cfg)
}
// Function returns the current function.
func (d *Debugger) Function(goid, frame, deferredCall int, cfg proc.LoadConfig) (*proc.Function, error) {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
if err != nil {
return nil, err
}
return s.Fn, nil
}
// EvalVariableInScope will attempt to evaluate the variable represented by 'symbol'
// in the scope provided.
func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {