From 1e9c5c3b07dc5f0f2b3b1fb17bde6444cbf7ca30 Mon Sep 17 00:00:00 2001 From: Suzy Mueller Date: Mon, 17 May 2021 12:21:15 -0400 Subject: [PATCH] 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 --- service/dap/server.go | 14 ++++++-- service/dap/server_test.go | 63 +++++++++++++++++++++++++++++++++++- service/debugger/debugger.go | 12 +++++++ 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/service/dap/server.go b/service/dap/server.go index 68c983ab..fa007fa3 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -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)} diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 5dcf11c9..2ec01c35 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -47,7 +47,11 @@ func TestMain(m *testing.M) { // name is for _fixtures/.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/.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) { diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 14ecd46d..7b9bf756 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -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) {