-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
service/dap: support evaluate requests with expressions and calls #2185
Conversation
service/dap/server.go
Outdated
s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser) | ||
return | ||
} | ||
retVars := s.debugger.ReturnValues(&prcCfg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with this is that there's no guarantee that the return values for the call will be available the very next time the debugger stops. It could be that the debugger stopped because it encountered a breakpoint, either inside the function that was called or some other breakpoint in a different goroutine.
I'm not sure what the right way to handle this would be, I think it depends on what the UI of VSCode can do. For example what happens when using VSCode with gdb, doing a function call while a breakpoint is set in the function being called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a draft implementation. See TODO please - I am not fully sure what the cleanest way is to detect if the call completed or not.
Also, I added a unittest for a breakpoint inside of the call. I still need to add a test where the call restarts other goroutines that stop on a breakpoint. In the meantime, I tested this manually with https://github.com/aarzilli/delve_client_testing/blob/master/05nextbp/main.go. It works when one-off calls are issued from the debug console. But if we put a call into a watch panel, then everything gets totally screwed up. The call restarts everything, so before it finishes, another goroutine stops. That triggers watch re-eval, but relative to another goroutine, so it is allowed. This again restarts everything, jumping from goroutine to goroutine, and just keeps going like continue, never really stopping at any breakpoints for longer than a split second.
Should we be disallowing calls as watch expressions to avoid such circular behavior? That would be unfortunate because it could be such a neat way to pretty print most interesting variables in the watch panel and have them automatically update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See TODO please - I am not fully sure what the cleanest way is to detect if the call completed or not.
You should iterate over the thread list and check if one has the goroutine ID you want and ReturnValues != nil
. I don't think the way you are doing this works. First off there's no guarantee that the injected goroutine will make progress before we are interrupted by a simultaneous breakpoint, secondly consider what happens if the goroutine stopped on a breakpoint inside main.f
and the user injects a call to main.g
which calls main.f
again.
Should we be disallowing calls as watch expressions to avoid such circular behavior? That would be unfortunate because it could be such a neat way to pretty print most interesting variables in the watch panel and have them automatically update.
IMHO there's no way to do it cleanly. I'd like to know what VSCode's solution is for C-like languages, maybe there's something I haven't thought of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's no guarantee that the injected goroutine will make progress before we are interrupted by a simultaneous breakpoint
It that case won't the injected goroutine no longer be the selected goroutine, even if its position hasn't changed? Won't the selected goroutine match that of the simultaneous breakpoint, which would be different from the goroutine where the call got injected?
Let's say I do loop over all the threads and look for the one with the goroutine id I am interested in and it has non-nil values. In what case, would this not be the selected goroutine? After the injection, are we not guaranteed to go back to the original point on the original goroutine, making it selected again?
Can the goroutine with the injection end up in a different stopped state that does have return values? (Maybe if it stops right after some function call inside of the injection? Does that only happened if we step out?)
consider what happens if the goroutine stopped on a breakpoint inside main.f and the user injects a call to main.g which calls main.f again
Right, good point. Added test and handling for this. Except, I am calling "f" when stopped in "f".
service/dap/server.go
Outdated
return | ||
} | ||
retVars := s.debugger.ReturnValues(&prcCfg) | ||
if retVars != nil && len(retVars) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See DeepSource error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
PS. see the failure of TestScopesAndVariables on windows/go1.15. I think you need to relax that test, it keeps going back and forth. |
Don't take a look just yet. I am investigating the failure (everything did pass for me locally). And I still need to update the code to loop over threads like we agreed. |
I think travis is running the tests because of the merge conflict. If you don't know how to get the full log of cirrus-ci, it's here: https://cirrus-ci.com/task/6100324710612992. |
It took me a moment to figure out, but I did eventually get to the log by clicking some obscure link on the bottom. Let's see what things look like after the merge. |
I think the proc test failures are unrelated. |
service/dap/server.go
Outdated
s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser) | ||
return | ||
} | ||
retVars = proct.Common().ReturnValues(prcCfg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a s.debugger.LockTarget() / s.debugger.UnlockTarget() around this. Also at this point we can exit the loop early with break
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
service/debugger/debugger.go
Outdated
@@ -1518,6 +1518,16 @@ func (d *Debugger) Recorded() (recorded bool, tracedir string) { | |||
return d.target.Recorded() | |||
} | |||
|
|||
// ReturnValues returns the return values of the function we just stepped out of. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reused for ReturnValues of a given thread.
service/dap/server.go
Outdated
if t.GoroutineID != stateBeforeCall.SelectedGoroutine.ID { | ||
continue | ||
} | ||
if t.Line == stateBeforeCall.SelectedGoroutine.CurrentLoc.Line && t.ReturnValues != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if and the one above can be fused into a single one
if t.GoroutineID == stateBeforeCall.SelectedGoroutine.ID && t.Line == stateBeforeCall.SelectedGoroutine.CurrentLoc.Line && t.ReturnValues != nil { ...```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…-delve#2185) * Support evaluate request * Fix failing tests * Call support * Remove debugger.CurrentThread() that got accidentally reintroduced during merge * Address review comments * Function to stringify stop reason * Add resetHandlesForStop * Handle stop inside call * More tests * Address review comments * Check all threads to determine if call completed * Fix test * Fix test * Fix test * Address review comments Co-authored-by: Polina Sokolova <polinasok@users.noreply.github.com>
updates #1515