mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Don't evaluate twice if an exception is raised. Fixes #742
This commit is contained in:
parent
f14ba78c5c
commit
765b64c0a4
4 changed files with 109 additions and 54 deletions
|
|
@ -1187,6 +1187,7 @@ def internal_evaluate_expression_json(py_db, request, thread_id):
|
|||
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_checked(eval_result, ExceptionOnEvaluate)
|
||||
if is_error:
|
||||
|
|
@ -1201,41 +1202,24 @@ def internal_evaluate_expression_json(py_db, request, thread_id):
|
|||
_evaluate_response(py_db, request, result=msg, error_message=msg)
|
||||
return
|
||||
else:
|
||||
try_exec = context == 'repl'
|
||||
# We only try the exec if the failure we had was due to not being able
|
||||
# to evaluate the expression.
|
||||
try:
|
||||
pydevd_vars.compile_as_eval(expression)
|
||||
except Exception:
|
||||
try_exec = context == 'repl'
|
||||
else:
|
||||
try_exec = False
|
||||
if context == 'repl':
|
||||
# In the repl we should show the exception to the user.
|
||||
_evaluate_response_return_exception(py_db, request, eval_result.etype, eval_result.result, eval_result.tb)
|
||||
return
|
||||
|
||||
if try_exec:
|
||||
try:
|
||||
pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=True)
|
||||
except (Exception, KeyboardInterrupt):
|
||||
try:
|
||||
exc, exc_type, initial_tb = sys.exc_info()
|
||||
tb = initial_tb
|
||||
|
||||
# Show the traceback without pydevd frames.
|
||||
temp_tb = tb
|
||||
while temp_tb:
|
||||
if py_db.get_file_type(temp_tb.tb_frame) == PYDEV_FILE:
|
||||
tb = temp_tb.tb_next
|
||||
temp_tb = temp_tb.tb_next
|
||||
|
||||
if tb is None:
|
||||
tb = initial_tb
|
||||
err = ''.join(traceback.format_exception(exc, exc_type, tb))
|
||||
|
||||
# Make sure we don't keep references to them.
|
||||
exc = None
|
||||
exc_type = None
|
||||
tb = None
|
||||
temp_tb = None
|
||||
initial_tb = None
|
||||
except:
|
||||
err = '<Internal error - unable to get traceback when evaluating expression>'
|
||||
pydev_log.exception(err)
|
||||
|
||||
# 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)
|
||||
_evaluate_response_return_exception(py_db, request, *sys.exc_info())
|
||||
return
|
||||
# No result on exec.
|
||||
_evaluate_response(py_db, request, result='')
|
||||
|
|
@ -1272,6 +1256,36 @@ def internal_evaluate_expression_json(py_db, request, thread_id):
|
|||
variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response, is_json=True))
|
||||
|
||||
def _evaluate_response_return_exception(py_db, request, exc_type, exc, initial_tb):
|
||||
try:
|
||||
tb = initial_tb
|
||||
|
||||
# Show the traceback without pydevd frames.
|
||||
temp_tb = tb
|
||||
while temp_tb:
|
||||
if py_db.get_file_type(temp_tb.tb_frame) == PYDEV_FILE:
|
||||
tb = temp_tb.tb_next
|
||||
temp_tb = temp_tb.tb_next
|
||||
|
||||
if tb is None:
|
||||
tb = initial_tb
|
||||
err = ''.join(traceback.format_exception(exc_type, exc, tb))
|
||||
|
||||
# Make sure we don't keep references to them.
|
||||
exc = None
|
||||
exc_type = None
|
||||
tb = None
|
||||
temp_tb = None
|
||||
initial_tb = None
|
||||
except:
|
||||
err = '<Internal error - unable to get traceback when evaluating expression>'
|
||||
pydev_log.exception(err)
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
@silence_warnings_decorator
|
||||
def internal_evaluate_expression(dbg, seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result):
|
||||
|
|
|
|||
|
|
@ -259,20 +259,8 @@ def eval_in_context(expression, globals, locals):
|
|||
try:
|
||||
result = eval(_expression_to_evaluate(expression), globals, locals)
|
||||
except (Exception, KeyboardInterrupt):
|
||||
s = StringIO()
|
||||
traceback.print_exc(file=s)
|
||||
result = s.getvalue()
|
||||
|
||||
try:
|
||||
try:
|
||||
etype, value, tb = sys.exc_info()
|
||||
result = value
|
||||
finally:
|
||||
etype = value = tb = None
|
||||
except:
|
||||
pass
|
||||
|
||||
result = ExceptionOnEvaluate(result)
|
||||
etype, result, tb = sys.exc_info()
|
||||
result = ExceptionOnEvaluate(result, etype, tb)
|
||||
|
||||
# Ok, we have the initial error message, but let's see if we're dealing with a name mangling error...
|
||||
try:
|
||||
|
|
@ -373,6 +361,19 @@ def _evaluate_with_timeouts(original_func):
|
|||
return new_func
|
||||
|
||||
|
||||
def compile_as_eval(expression):
|
||||
'''
|
||||
|
||||
:param expression:
|
||||
The expression to be compiled.
|
||||
|
||||
:return: code object
|
||||
|
||||
:raises Exception if the expression cannot be evaluated.
|
||||
'''
|
||||
return compile(_expression_to_evaluate(expression), '<string>', 'eval')
|
||||
|
||||
|
||||
@_evaluate_with_timeouts
|
||||
def evaluate_expression(py_db, frame, expression, is_exec):
|
||||
'''
|
||||
|
|
@ -416,7 +417,7 @@ def evaluate_expression(py_db, frame, expression, is_exec):
|
|||
try:
|
||||
# try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and
|
||||
# it will have whatever the user actually did)
|
||||
compiled = compile(_expression_to_evaluate(expression), '<string>', 'eval')
|
||||
compiled = compile_as_eval(expression)
|
||||
except Exception:
|
||||
Exec(_expression_to_evaluate(expression), updated_globals, frame.f_locals)
|
||||
pydevd_save_locals.save_locals(frame)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ def make_valid_xml_value(s):
|
|||
|
||||
class ExceptionOnEvaluate:
|
||||
|
||||
def __init__(self, result):
|
||||
def __init__(self, result, etype, tb):
|
||||
self.result = result
|
||||
self.etype = etype
|
||||
self.tb = tb
|
||||
|
||||
|
||||
_IS_JYTHON = sys.platform.startswith("java")
|
||||
|
|
|
|||
|
|
@ -2226,14 +2226,16 @@ def test_evaluate_unicode(case_setup):
|
|||
assert reference > 0
|
||||
variables_response = json_facade.get_variables_response(reference)
|
||||
child_variables = variables_response.to_dict()['body']['variables']
|
||||
assert len(child_variables) == 1
|
||||
assert json_facade.pop_variables_reference(child_variables)[0] > 0
|
||||
assert child_variables == [{
|
||||
u'type': u'SyntaxError',
|
||||
u'evaluateName': u'\u16a0.result',
|
||||
u'name': u'result',
|
||||
u'value': u"SyntaxError('invalid syntax', ('<string>', 1, 1, '\\xe1\\x9a\\xa0'))"
|
||||
}]
|
||||
assert len(child_variables) == 2
|
||||
for c in child_variables:
|
||||
if c[u'type'] == u'SyntaxError':
|
||||
assert c.pop('variablesReference') > 0
|
||||
assert c == {
|
||||
u'type': u'SyntaxError',
|
||||
u'evaluateName': u'\u16a0.result',
|
||||
u'name': u'result',
|
||||
u'value': u"SyntaxError('invalid syntax', ('<string>', 1, 1, '\\xe1\\x9a\\xa0'))"
|
||||
}
|
||||
|
||||
else:
|
||||
assert evaluate_response_body == {
|
||||
|
|
@ -2329,6 +2331,42 @@ def test_evaluate_repl_redirect(case_setup):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_evaluate_no_double_exec(case_setup, pyfile):
|
||||
|
||||
@pyfile
|
||||
def exec_code():
|
||||
|
||||
def print_and_raise():
|
||||
print('Something')
|
||||
raise RuntimeError()
|
||||
|
||||
print('Break here')
|
||||
print('TEST SUCEEDED!')
|
||||
|
||||
with case_setup.test_file(exec_code) as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
json_facade.write_launch(justMyCode=False)
|
||||
json_facade.write_set_breakpoints(writer.get_line_index_with_content('Break here'))
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
json_hit = json_facade.wait_for_thread_stopped()
|
||||
json_hit = json_facade.get_stack_as_json_hit(json_hit.thread_id)
|
||||
|
||||
json_facade.evaluate(
|
||||
"print_and_raise()",
|
||||
frameId=json_hit.frame_id,
|
||||
context="repl",
|
||||
success=False,
|
||||
)
|
||||
|
||||
messages = json_facade.mark_messages(
|
||||
OutputEvent, lambda output_event: u'Something' in output_event.body.output)
|
||||
assert len(messages) == 1
|
||||
|
||||
json_facade.write_continue()
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_evaluate_variable_references(case_setup):
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import EvaluateRequest
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import EvaluateArguments
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue