Skip to content
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

Merged
merged 19 commits into from
Nov 12, 2020

Conversation

polinasok
Copy link
Collaborator

updates #1515

service/dap/server.go Show resolved Hide resolved
s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
return
}
retVars := s.debugger.ReturnValues(&prcCfg)
Copy link
Member

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?

Copy link
Collaborator Author

@polinasok polinasok Oct 23, 2020

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.

Copy link
Member

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.

Copy link
Collaborator Author

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".

return
}
retVars := s.debugger.ReturnValues(&prcCfg)
if retVars != nil && len(retVars) > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See DeepSource error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

service/dap/server_test.go Outdated Show resolved Hide resolved
@aarzilli
Copy link
Member

PS. see the failure of TestScopesAndVariables on windows/go1.15. I think you need to relax that test, it keeps going back and forth.

@polinasok
Copy link
Collaborator Author

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.

@aarzilli
Copy link
Member

aarzilli commented Nov 2, 2020

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.

@polinasok
Copy link
Collaborator Author

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.

@polinasok
Copy link
Collaborator Author

I think the proc test failures are unrelated.

s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
return
}
retVars = proct.Common().ReturnValues(prcCfg)
Copy link
Member

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer used?

Copy link
Collaborator Author

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.

if t.GoroutineID != stateBeforeCall.SelectedGoroutine.ID {
continue
}
if t.Line == stateBeforeCall.SelectedGoroutine.CurrentLoc.Line && t.ReturnValues != nil {
Copy link
Member

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 { ...```

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

@aarzilli aarzilli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@derekparker derekparker merged commit c94db60 into go-delve:master Nov 12, 2020
abner-chenc pushed a commit to loongson/delve that referenced this pull request Mar 1, 2024
…-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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants