Return evaluate result in DAP response body instead of writing to stdout#2027
Conversation
When a DAP client sends an `evaluate` request with `context: "repl"` and no `frameId`, debugpy forces the expression through the exec code path. Previously, if the expression could be compiled as an eval (e.g. `2 + 2`), `evaluate_expression` would compute the result but write it to `sys.stdout` and return `None`. The caller in `internal_evaluate_expression_json` would then send back `result=""` in the response body. Clients that read the response body would get nothing, while the actual value was emitted as a DAP output event. This changes `evaluate_expression` to return the computed result from the eval-within-exec path instead of printing it. The caller now captures that return value and includes it in the response body. Pure exec statements (e.g. `x = 42`) continue to return `None` and produce `result=""` as before. VS Code is unaffected because it always provides a `frameId`, which routes through the normal eval path where results already go into the response body.
rchiodo
left a comment
There was a problem hiding this comment.
It seems correct but I'm wondering why you wanted to change this? Is there a reason this is needed elsewhere?
Also what does VS code send if you're not stopped on a breakpoint and you try to eval something in the debug console?
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
| @@ -1239,12 +1239,16 @@ def __create_frame(): | |||
|
|
|||
There was a problem hiding this comment.
Copilot generated:
-1249
[unverified] The "%s" % (exec_result,) formatting at line 1249 now happens outside the try/except that catches exceptions from evaluate_expression. Previously, the equivalent "%s\n" % (result,) was inside evaluate_expression (at pydevd_vars.py:577), meaning any __str__ exception was caught by the caller's except (Exception, KeyboardInterrupt). Now an object with a broken __str__ will raise an unhandled exception at the response-formatting stage.
Suggested fix — move the formatting inside the try/except:
try:
exec_result = pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=True)
result = "%s" % (exec_result,) if exec_result is not None else ""
except (Exception, KeyboardInterrupt):
_evaluate_response_return_exception(py_db, request, *sys.exc_info())
return
_evaluate_response(py_db, request, result=result)The Advocate notes this uses the same %s as before, but the error-handling boundary has shifted — this is a new failure mode, not pre-existing.
[verified]
We have non-VS Code clients that are affected by this.
Evaluation without being stopped shows the behavior in VS Code as well: Before the change: after the change: My understanding is that the latter is the correct per the DAP spec, and matches how other debuggers respond. |
rchiodo
left a comment
There was a problem hiding this comment.
Approved via Review Center.
|
Thanks Philip. |
When a DAP client sends an
evaluaterequest withcontext: "repl"and noframeId, debugpy forces the expression through the exec code path. Previously, if the expression could be compiled as an eval (e.g.2 + 2),evaluate_expressionwould compute the result but write it tosys.stdoutand returnNone. The caller ininternal_evaluate_expression_jsonwould then send backresult=""in the response body. Clients that read the response body would get nothing, while the actual value was emitted as a DAP output event.This changes
evaluate_expressionto return the computed result from the eval-within-exec path instead of printing it. The caller now captures that return value and includes it in the response body. Pure exec statements (e.g.x = 42) continue to returnNoneand produceresult=""as before.VS Code is unaffected because it always provides a
frameId, which routes through the normal eval path where results already go into the response body.