From 9f1870077a492099fc329dc8a48e482392d7e017 Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Thu, 12 Oct 2017 19:45:33 +0300 Subject: [PATCH] Updates from PyDev: workaround for debugger on IronPython --- .../pydevd_additional_thread_info_regular.py | 18 +++--- .../pydev/_pydevd_bundle/pydevd_constants.py | 1 + .../pydev/_pydevd_bundle/pydevd_cython.pyx | 40 +++++++++---- .../pydevd_trace_dispatch_regular.py | 58 +++++++++++++++---- .../test_pydevd_reload/test_pydevd_reload.py | 4 +- .../tests_pydevd_python/test_save_locals.py | 8 +-- 6 files changed, 90 insertions(+), 39 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_additional_thread_info_regular.py b/python/helpers/pydev/_pydevd_bundle/pydevd_additional_thread_info_regular.py index 111982d69605..db827002f982 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_additional_thread_info_regular.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_additional_thread_info_regular.py @@ -1,5 +1,5 @@ import sys -from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON +from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON, IS_IRONPYTHON # IFDEF CYTHON # ELSE from _pydevd_bundle.pydevd_frame import PyDBFrame @@ -37,6 +37,14 @@ if not hasattr(sys, '_current_frames'): ret[thread.getId()] = frame return ret + + if IS_IRONPYTHON: + _tid_to_last_frame = {} + + # IronPython doesn't have it. Let's use our workaround... + def _current_frames(): + return _tid_to_last_frame + else: raise RuntimeError('Unable to proceed (sys._current_frames not available in this Python implementation).') else: @@ -110,14 +118,6 @@ class PyDBAdditionalThreadInfo(object): return [v] return [] - # IFDEF CYTHON - # def create_db_frame(self, *args, **kwargs): - # raise AssertionError('This method should not be called on cython (PyDbFrame should be used directly).') - # ELSE - # just create the db frame directly - create_db_frame = PyDBFrame - # ENDIF - def __str__(self): return 'State:%s Stop:%s Cmd: %s Kill:%s' % ( self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py index e62b3e9c6916..6f905cbed5e5 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py @@ -38,6 +38,7 @@ import os from _pydevd_bundle import pydevd_vm_type IS_JYTHON = pydevd_vm_type.get_vm_type() == pydevd_vm_type.PydevdVmType.JYTHON +IS_IRONPYTHON = sys.platform == 'cli' IS_JYTH_LESS25 = False if IS_JYTHON: diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_cython.pyx b/python/helpers/pydev/_pydevd_bundle/pydevd_cython.pyx index 5dcccbd2aeff..984cc882fa54 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_cython.pyx +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_cython.pyx @@ -3,7 +3,7 @@ # DO NOT edit manually! # DO NOT edit manually! import sys -from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON +from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON, IS_IRONPYTHON # IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated) # ELSE # from _pydevd_bundle.pydevd_frame import PyDBFrame @@ -41,6 +41,14 @@ if not hasattr(sys, '_current_frames'): ret[thread.getId()] = frame return ret + + if IS_IRONPYTHON: + _tid_to_last_frame = {} + + # IronPython doesn't have it. Let's use our workaround... + def _current_frames(): + return _tid_to_last_frame + else: raise RuntimeError('Unable to proceed (sys._current_frames not available in this Python implementation).') else: @@ -114,14 +122,6 @@ cdef class PyDBAdditionalThreadInfo: return [v] return [] - # IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated) - def create_db_frame(self, *args, **kwargs): - raise AssertionError('This method should not be called on cython (PyDbFrame should be used directly).') - # ELSE -# # just create the db frame directly -# create_db_frame = PyDBFrame - # ENDIF - def __str__(self): return 'State:%s Stop:%s Cmd: %s Kill:%s' % ( self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill) @@ -898,7 +898,7 @@ import traceback from _pydev_bundle.pydev_is_thread_alive import is_thread_alive from _pydev_imps._pydev_saved_modules import threading -from _pydevd_bundle.pydevd_constants import get_thread_id +from _pydevd_bundle.pydevd_constants import get_thread_id, IS_IRONPYTHON from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE from _pydevd_bundle.pydevd_kill_all_pydevd_threads import kill_all_pydev_threads from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER @@ -1093,3 +1093,23 @@ cdef class ThreadTracer: # (https://github.com/fabioz/PyDev.Debugger/issues/8) pass return None + + +if IS_IRONPYTHON: + # This is far from ideal, as we'll leak frames (we'll always have the last created frame, not really + # the last topmost frame saved -- this should be Ok for our usage, but it may leak frames and things + # may live longer... as IronPython is garbage-collected, things should live longer anyways, so, it + # shouldn't be an issue as big as it's in CPython -- it may still be annoying, but this should + # be a reasonable workaround until IronPython itself is able to provide that functionality). + # + # See: https://github.com/IronLanguages/main/issues/1630 + from _pydevd_bundle.pydevd_additional_thread_info_regular import _tid_to_last_frame + + _original_call = ThreadTracer.__call__ + + def __call__(self, frame, event, arg): + _tid_to_last_frame[self._args[1].ident] = frame + return _original_call(self, frame, event, arg) + + ThreadTracer.__call__ = __call__ + diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_trace_dispatch_regular.py b/python/helpers/pydev/_pydevd_bundle/pydevd_trace_dispatch_regular.py index ace760596f54..a333a24aab29 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_trace_dispatch_regular.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_trace_dispatch_regular.py @@ -2,7 +2,7 @@ import traceback from _pydev_bundle.pydev_is_thread_alive import is_thread_alive from _pydev_imps._pydev_saved_modules import threading -from _pydevd_bundle.pydevd_constants import get_thread_id +from _pydevd_bundle.pydevd_constants import get_thread_id, IS_IRONPYTHON from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE from _pydevd_bundle.pydevd_kill_all_pydevd_threads import kill_all_pydev_threads from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER @@ -14,8 +14,15 @@ from _pydevd_bundle.pydevd_tracing import SetTrace # ELSE from _pydevd_bundle.pydevd_additional_thread_info import PyDBAdditionalThreadInfo from _pydevd_bundle.pydevd_frame import PyDBFrame + # ENDIF +try: + from _pydevd_bundle.pydevd_signature import send_signature_call_trace +except ImportError: + def send_signature_call_trace(*args, **kwargs): + pass + threadingCurrentThread = threading.currentThread get_file_type = DONT_TRACE.get @@ -33,6 +40,7 @@ get_file_type = DONT_TRACE.get global_cache_skips = {} global_cache_frame_skips = {} + def trace_dispatch(py_db, frame, event, arg): t = threadingCurrentThread() @@ -47,13 +55,14 @@ def trace_dispatch(py_db, frame, event, arg): additional_info = t.additional_info = PyDBAdditionalThreadInfo() thread_tracer = ThreadTracer((py_db, t, additional_info, global_cache_skips, global_cache_frame_skips)) -# IFDEF CYTHON -# t._tracer = thread_tracer # Hack for cython to keep it alive while the thread is alive (just the method in the SetTrace is not enough). -# ELSE -# ENDIF + # IFDEF CYTHON + # t._tracer = thread_tracer # Hack for cython to keep it alive while the thread is alive (just the method in the SetTrace is not enough). + # ELSE + # ENDIF SetTrace(thread_tracer.__call__) return thread_tracer.__call__(frame, event, arg) + # IFDEF CYTHON # cdef class SafeCallWrapper: # cdef method_object @@ -75,8 +84,8 @@ def trace_dispatch(py_db, frame, event, arg): class ThreadTracer: def __init__(self, args): self._args = args - # ENDIF + # ENDIF def __call__(self, frame, event, arg): ''' This is the callback used when we enter some context in the debugger. @@ -109,7 +118,7 @@ class ThreadTracer: try: if py_db._finish_debugging_session: if not py_db._termination_event_set: - #that was not working very well because jython gave some socket errors + # that was not working very well because jython gave some socket errors try: if py_db.output_checker is None: kill_all_pydev_threads() @@ -143,10 +152,11 @@ class ThreadTracer: # print('skipped: trace_dispatch (cache hit)', cache_key, frame.f_lineno, event, frame.f_code.co_name) return None - file_type = get_file_type(abs_path_real_path_and_base[-1]) #we don't want to debug threading or anything related to pydevd + file_type = get_file_type( + abs_path_real_path_and_base[-1]) # we don't want to debug threading or anything related to pydevd if file_type is not None: - if file_type == 1: # inlining LIB_FILE = 1 + if file_type == 1: # inlining LIB_FILE = 1 if py_db.not_in_scope(filename): # print('skipped: trace_dispatch (not in scope)', abs_path_real_path_and_base[-1], frame.f_lineno, event, frame.f_code.co_name, file_type) cache_skips[cache_key] = 1 @@ -166,11 +176,13 @@ class ThreadTracer: # print('trace_dispatch', base, frame.f_lineno, event, frame.f_code.co_name, file_type) if additional_info.is_tracing: - return None #we don't wan't to trace code invoked from pydevd_frame.trace_dispatch + return None # we don't wan't to trace code invoked from pydevd_frame.trace_dispatch # Just create PyDBFrame directly (removed support for Python versions < 2.5, which required keeping a weak # reference to the frame). - ret = PyDBFrame((py_db, filename, additional_info, t, frame_skips_cache, (frame.f_code.co_name, frame.f_code.co_firstlineno, filename))).trace_dispatch(frame, event, arg) + ret = PyDBFrame((py_db, filename, additional_info, t, frame_skips_cache, + (frame.f_code.co_name, frame.f_code.co_firstlineno, filename))).trace_dispatch(frame, + event, arg) if ret is None: cache_skips[cache_key] = 1 return None @@ -186,7 +198,7 @@ class ThreadTracer: except Exception: if py_db._finish_debugging_session: - return None # Don't log errors when we're shutting down. + return None # Don't log errors when we're shutting down. # Log it try: if traceback is not None: @@ -197,3 +209,25 @@ class ThreadTracer: # (https://github.com/fabioz/PyDev.Debugger/issues/8) pass return None + + +if IS_IRONPYTHON: + # This is far from ideal, as we'll leak frames (we'll always have the last created frame, not really + # the last topmost frame saved -- this should be Ok for our usage, but it may leak frames and things + # may live longer... as IronPython is garbage-collected, things should live longer anyways, so, it + # shouldn't be an issue as big as it's in CPython -- it may still be annoying, but this should + # be a reasonable workaround until IronPython itself is able to provide that functionality). + # + # See: https://github.com/IronLanguages/main/issues/1630 + from _pydevd_bundle.pydevd_additional_thread_info_regular import _tid_to_last_frame + + _original_call = ThreadTracer.__call__ + + + def __call__(self, frame, event, arg): + _tid_to_last_frame[self._args[1].ident] = frame + return _original_call(self, frame, event, arg) + + + ThreadTracer.__call__ = __call__ + diff --git a/python/helpers/pydev/test_pydevd_reload/test_pydevd_reload.py b/python/helpers/pydev/test_pydevd_reload/test_pydevd_reload.py index 2e96995f2379..38a19196431c 100644 --- a/python/helpers/pydev/test_pydevd_reload/test_pydevd_reload.py +++ b/python/helpers/pydev/test_pydevd_reload/test_pydevd_reload.py @@ -24,9 +24,9 @@ class C: return 'unchanged' """ -IS_JYTHON = sys.platform.find('java') != -1 +from _pydevd_bundle.pydevd_constants import IS_JYTHON, IS_IRONPYTHON -@pytest.mark.skipif(IS_JYTHON, reason='CPython related test') +@pytest.mark.skipif(IS_JYTHON or IS_IRONPYTHON, reason='CPython related test') class Test(unittest.TestCase): diff --git a/python/helpers/pydev/tests_pydevd_python/test_save_locals.py b/python/helpers/pydev/tests_pydevd_python/test_save_locals.py index b5c6c5bc41dd..f3c476ad61e0 100644 --- a/python/helpers/pydev/tests_pydevd_python/test_save_locals.py +++ b/python/helpers/pydev/tests_pydevd_python/test_save_locals.py @@ -3,7 +3,7 @@ import sys import unittest from _pydevd_bundle.pydevd_save_locals import save_locals -from _pydevd_bundle.pydevd_constants import IS_JYTHON +from _pydevd_bundle.pydevd_constants import IS_JYTHON, IS_IRONPYTHON import pytest @@ -31,18 +31,17 @@ def check_method(fn): +@pytest.mark.skipif(IS_JYTHON or IS_IRONPYTHON, reason='CPython/pypy only') class TestSetLocals(unittest.TestCase): """ Test setting locals in one function from another function using several approaches. """ - @pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only') def test_set_locals_using_save_locals(self): x = check_method(use_save_locals) self.assertEqual(x, 2) # Expected to succeed - @pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only') def test_frame_simple_change(self): frame = sys._getframe() a = 20 @@ -51,7 +50,6 @@ class TestSetLocals(unittest.TestCase): self.assertEquals(50, a) - @pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only') def test_frame_co_freevars(self): outer_var = 20 @@ -64,7 +62,6 @@ class TestSetLocals(unittest.TestCase): func() - @pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only') def test_frame_co_cellvars(self): def check_co_vars(a): @@ -81,7 +78,6 @@ class TestSetLocals(unittest.TestCase): check_co_vars(1) - @pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only') def test_frame_change_in_inner_frame(self): def change(f): self.assert_(f is not sys._getframe())