Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Make it possible to evaluate without a frameId. Fixes #1716 #1729

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 38 additions & 19 deletions src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,10 +897,14 @@ def _evaluate_response(py_db, request, result, error_message=''):
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response, is_json=True))


_global_frame = None


def internal_evaluate_expression_json(py_db, request, thread_id):
'''
:param EvaluateRequest request:
'''
global _global_frame
# : :type arguments: EvaluateArguments

arguments = request.arguments
Expand All @@ -918,28 +922,43 @@ def internal_evaluate_expression_json(py_db, request, thread_id):
_evaluate_response(py_db, request, '', error_message='Expression is not valid utf-8.')
raise

frame = py_db.find_frame(thread_id, frame_id)
result = pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=False)
is_error = isinstance(result, ExceptionOnEvaluate)
try_exec = False
if frame_id is None:
if _global_frame is None:
# Lazily create a frame to be used for evaluation with no frame id.

if is_error:
if context == 'hover':
_evaluate_response(py_db, request, result='')
return
def __create_frame():
yield sys._getframe()

elif context == 'repl':
try:
pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=True)
except Exception as ex:
err = ''.join(traceback.format_exception_only(type(ex), ex))
# Currently there is an issue in VSC where returning success=false for an
# eval request, in repl context, VSC does not show the error response in
# the debug console. So return the error message in result as well.
_evaluate_response(py_db, request, result=err, error_message=err)
_global_frame = next(__create_frame())

frame = _global_frame
try_exec = True # Always exec in this case
fabioz marked this conversation as resolved.
Show resolved Hide resolved
eval_result = None
else:
frame = py_db.find_frame(thread_id, frame_id)
eval_result = pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=False)
is_error = isinstance(eval_result, ExceptionOnEvaluate)
if is_error:
if context == 'hover': # In a hover it doesn't make sense to do an exec.
_evaluate_response(py_db, request, result='')
return
# No result on exec.
_evaluate_response(py_db, request, result='')
else:
try_exec = context == 'repl'

if try_exec:
try:
pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=True)
except Exception as ex:
err = ''.join(traceback.format_exception_only(type(ex), ex))
# Currently there is an issue in VSC where returning success=false for an
# eval request, in repl context, VSC does not show the error response in
# the debug console. So return the error message in result as well.
_evaluate_response(py_db, request, result=err, error_message=err)
return
# No result on exec.
_evaluate_response(py_db, request, result='')
return

# Ok, we have the result (could be an error), let's put it into the saved variables.
frame_tracker = py_db.suspended_frames_manager.get_frame_tracker(thread_id)
Expand All @@ -948,7 +967,7 @@ def internal_evaluate_expression_json(py_db, request, thread_id):
_evaluate_response(py_db, request, result='', error_message='Thread id: %s is not current thread id.' % (thread_id,))
return

variable = frame_tracker.obtain_as_variable(expression, result, frame=frame)
variable = frame_tracker.obtain_as_variable(expression, eval_result, frame=frame)
var_data = variable.get_var_data(fmt=fmt)

body = pydevd_schema.EvaluateResponseBody(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,22 +701,25 @@ def on_evaluate_request(self, py_db, request):
# : :type arguments: EvaluateArguments
arguments = request.arguments

thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
arguments.frameId)

if thread_id is not None:
self.api.request_exec_or_evaluate_json(
py_db, request, thread_id)
if arguments.frameId is None:
self.api.request_exec_or_evaluate_json(py_db, request, thread_id='*')
else:
body = EvaluateResponseBody('', 0)
response = pydevd_base_schema.build_response(
request,
kwargs={
'body': body,
'success': False,
'message': 'Unable to find thread for evaluation.'
})
return NetCommand(CMD_RETURN, 0, response, is_json=True)
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
arguments.frameId)

if thread_id is not None:
self.api.request_exec_or_evaluate_json(
py_db, request, thread_id)
else:
body = EvaluateResponseBody('', 0)
response = pydevd_base_schema.build_response(
request,
kwargs={
'body': body,
'success': False,
'message': 'Unable to find thread for evaluation.'
})
return NetCommand(CMD_RETURN, 0, response, is_json=True)

def on_setexpression_request(self, py_db, request):
# : :type arguments: SetExpressionArguments
Expand Down
43 changes: 31 additions & 12 deletions src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,14 @@ def write_initialize(self, access_token, success=True):
assert isinstance(process_id, int)
return response

def evaluate(self, expression, frameId=None, context=None, fmt=None, success=True):
eval_request = self.write_request(
pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments(
expression, frameId=frameId, context=context, format=fmt)))
eval_response = self.wait_for_response(eval_request)
assert eval_response.success == success
return eval_response


def test_case_json_logpoints(case_setup):
with case_setup.test_file('_debugger_case_change_breaks.py') as writer:
Expand Down Expand Up @@ -1803,25 +1811,36 @@ def test_evaluate(case_setup):
stack_frame_id = stack_frame['id']

# Test evaluate request that results in 'eval'
eval_request = json_facade.write_request(
pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments('var_1', frameId=stack_frame_id, context='repl')))
eval_response = json_facade.wait_for_response(eval_request)
eval_response = json_facade.evaluate('var_1', frameId=stack_frame_id, context='repl')
assert eval_response.body.result == '5'
assert eval_response.body.type == 'int'

# Test evaluate request that results in 'exec'
exec_request = json_facade.write_request(
pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments('var_1 = 6', frameId=stack_frame_id, context='repl')))
exec_response = json_facade.wait_for_response(exec_request)
exec_response = json_facade.evaluate('var_1 = 6', frameId=stack_frame_id, context='repl')
assert exec_response.body.result == ''

# Test evaluate request that results in 'exec' but fails
exec_request = json_facade.write_request(
pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments('var_1 = "abc"/6', frameId=stack_frame_id, context='repl')))
exec_response = json_facade.wait_for_response(exec_request)
assert exec_response.success == False
assert exec_response.body.result.find('TypeError') > -1
assert exec_response.message.find('TypeError') > -1
exec_response = json_facade.evaluate(
'var_1 = "abc"/6', frameId=stack_frame_id, context='repl', success=False)
assert 'TypeError' in exec_response.body.result
assert 'TypeError' in exec_response.message

# Evaluate without a frameId.

# Error because 'foo_value' is not set in 'sys'.
exec_response = json_facade.evaluate('import email;email.foo_value', success=False)
assert 'AttributeError' in exec_response.body.result
assert 'AttributeError' in exec_response.message

# Reading foo_value didn't work, but 'email' should be in the namespace now.
json_facade.evaluate('email.foo_value=True')

# Ok, 'foo_value' is now set in 'email' module.
exec_response = json_facade.evaluate('email.foo_value')

# We don't actually get variables without a frameId, we can just evaluate and observe side effects
# (so, the result is always empty -- or an error).
assert exec_response.body.result == ''

json_facade.write_continue(wait_for_response=False)

Expand Down