Skip to content

Commit

Permalink
Use code object instead of (co_firstlineno, co_name, co_filename) for…
Browse files Browse the repository at this point in the history
… caching frame info. Fixes #837 Fixes #844
  • Loading branch information
fabioz committed Feb 25, 2022
1 parent 1b8d5ab commit 6134532
Show file tree
Hide file tree
Showing 7 changed files with 423 additions and 432 deletions.
729 changes: 331 additions & 398 deletions src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython.c

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ cdef class PyDBFrame:
cdef str curr_func_name;
cdef bint exist_result;
cdef dict frame_skips_cache;
cdef tuple frame_cache_key;
cdef object frame_cache_key;
cdef tuple line_cache_key;
cdef int breakpoints_in_line_cache;
cdef int breakpoints_in_frame_cache;
Expand Down Expand Up @@ -1640,7 +1640,7 @@ cdef class ThreadTracer:
cdef str filename;
cdef str base;
cdef int pydev_step_cmd;
cdef tuple frame_cache_key;
cdef object frame_cache_key;
cdef dict cache_skips;
cdef bint is_stepping;
cdef tuple abs_path_canonical_path_and_base;
Expand All @@ -1667,7 +1667,7 @@ cdef class ThreadTracer:

# Note: it's important that the context name is also given because we may hit something once
# in the global context and another in the local context.
frame_cache_key = (frame.f_code.co_firstlineno, frame.f_code.co_name, frame.f_code.co_filename)
frame_cache_key = frame.f_code
if frame_cache_key in cache_skips:
if not is_stepping:
# if DEBUG: print('skipped: trace_dispatch (cache hit)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
Expand All @@ -1681,7 +1681,7 @@ cdef class ThreadTracer:

back_frame = frame.f_back
if back_frame is not None and pydev_step_cmd in (107, 144, 109, 160):
back_frame_cache_key = (back_frame.f_code.co_firstlineno, back_frame.f_code.co_name, back_frame.f_code.co_filename)
back_frame_cache_key = back_frame.f_code
if cache_skips.get(back_frame_cache_key) == 1:
# if DEBUG: print('skipped: trace_dispatch (cache hit: 1)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
return None if event == 'call' else NO_FTRACE
Expand Down Expand Up @@ -1721,7 +1721,7 @@ cdef class ThreadTracer:
back_frame = frame.f_back
if back_frame is not None and pydev_step_cmd in (107, 144, 109, 160):
if py_db.apply_files_filter(back_frame, back_frame.f_code.co_filename, False):
back_frame_cache_key = (back_frame.f_code.co_firstlineno, back_frame.f_code.co_name, back_frame.f_code.co_filename)
back_frame_cache_key = back_frame.f_code
cache_skips[back_frame_cache_key] = 1
# if DEBUG: print('skipped: trace_dispatch (filtered out: 1)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
return None if event == 'call' else NO_FTRACE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def _get_unfiltered_back_frame(self, main_debugger, frame):
# cdef str curr_func_name;
# cdef bint exist_result;
# cdef dict frame_skips_cache;
# cdef tuple frame_cache_key;
# cdef object frame_cache_key;
# cdef tuple line_cache_key;
# cdef int breakpoints_in_line_cache;
# cdef int breakpoints_in_frame_cache;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ def __call__(self, frame, event, arg):
# cdef str filename;
# cdef str base;
# cdef int pydev_step_cmd;
# cdef tuple frame_cache_key;
# cdef object frame_cache_key;
# cdef dict cache_skips;
# cdef bint is_stepping;
# cdef tuple abs_path_canonical_path_and_base;
Expand All @@ -367,7 +367,7 @@ def __call__(self, frame, event, arg):

# Note: it's important that the context name is also given because we may hit something once
# in the global context and another in the local context.
frame_cache_key = (frame.f_code.co_firstlineno, frame.f_code.co_name, frame.f_code.co_filename)
frame_cache_key = frame.f_code
if frame_cache_key in cache_skips:
if not is_stepping:
# if DEBUG: print('skipped: trace_dispatch (cache hit)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
Expand All @@ -381,7 +381,7 @@ def __call__(self, frame, event, arg):

back_frame = frame.f_back
if back_frame is not None and pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE):
back_frame_cache_key = (back_frame.f_code.co_firstlineno, back_frame.f_code.co_name, back_frame.f_code.co_filename)
back_frame_cache_key = back_frame.f_code
if cache_skips.get(back_frame_cache_key) == 1:
# if DEBUG: print('skipped: trace_dispatch (cache hit: 1)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
return None if event == 'call' else NO_FTRACE
Expand Down Expand Up @@ -421,7 +421,7 @@ def __call__(self, frame, event, arg):
back_frame = frame.f_back
if back_frame is not None and pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE):
if py_db.apply_files_filter(back_frame, back_frame.f_code.co_filename, False):
back_frame_cache_key = (back_frame.f_code.co_firstlineno, back_frame.f_code.co_name, back_frame.f_code.co_filename)
back_frame_cache_key = back_frame.f_code
cache_skips[back_frame_cache_key] = 1
# if DEBUG: print('skipped: trace_dispatch (filtered out: 1)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
return None if event == 'call' else NO_FTRACE
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 38 additions & 1 deletion src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from _pydevd_bundle.pydevd_comm_constants import file_system_encoding
from _pydevd_bundle.pydevd_constants import (int_types, IS_64BIT_PROCESS,
PY_VERSION_STR, PY_IMPL_VERSION_STR, PY_IMPL_NAME, IS_PY36_OR_GREATER,
IS_PYPY, GENERATED_LEN_ATTR_NAME, IS_WINDOWS, IS_LINUX, IS_MAC)
IS_PYPY, GENERATED_LEN_ATTR_NAME, IS_WINDOWS, IS_LINUX, IS_MAC, IS_PY38_OR_GREATER)
from tests_python import debugger_unittest
from tests_python.debug_constants import TEST_CHERRYPY, IS_PY2, TEST_DJANGO, TEST_FLASK, IS_PY26, \
IS_PY27, IS_CPYTHON, TEST_GEVENT, TEST_CYTHON
Expand Down Expand Up @@ -5988,6 +5988,43 @@ def pandas_mod():
writer.finished_ok = True


@pytest.mark.skipif(not IS_PY38_OR_GREATER, reason='Python 3.8 onwards required for test.')
def test_same_lineno_and_filename(case_setup, pyfile):

@pyfile
def target():

def some_code():
print('1') # Break here

code_obj = compile('''
func()
''', __file__, 'exec')

code_obj = code_obj.replace(co_name=some_code.__code__.co_name, co_firstlineno=some_code.__code__.co_firstlineno)
exec(code_obj, {'func': some_code})

print('TEST SUCEEDED')

with case_setup.test_file(target) as writer:
json_facade = JsonFacade(writer)

writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'))
json_facade.write_launch(justMyCode=False)
json_facade.write_make_initial_run()

json_hit = json_facade.wait_for_thread_stopped()
json_facade.write_continue()

if sys.version_info[:2] >= (3, 10):
# On Python 3.10 we'll stop twice in this specific case
# because the line actually matches in the caller (so
# this is correct based on what the debugger is seeing...)
json_hit = json_facade.wait_for_thread_stopped()
json_facade.write_continue()
writer.finished_ok = True


if __name__ == '__main__':
pytest.main(['-k', 'test_case_skipping_filters', '-s'])

35 changes: 25 additions & 10 deletions src/debugpy/_vendored/pydevd/tests_python/test_run.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import pytest
pytest_plugins = [
str('_pytest.pytester'),
]


def _run_and_check(testdir, path, check_for='Worked'):
result = testdir.runpython(path)
def _run_and_check(testdir_or_pytester, path, check_for='Worked'):
result = testdir_or_pytester.runpython(path)
result.stdout.fnmatch_lines([
check_for
])

def test_run(testdir):

if hasattr(pytest, 'version_tuple') and pytest.version_tuple[0] >= 7:

@pytest.fixture
def testdir_or_pytester(pytester):
return pytester

else:

@pytest.fixture
def testdir_or_pytester(testdir):
return testdir


def test_run(testdir_or_pytester):
from tests_python import debugger_unittest
import sys
import os
Expand All @@ -24,7 +39,7 @@ def test_run(testdir):
pydevd_dir = os.path.dirname(os.path.dirname(__file__))
assert os.path.exists(os.path.join(pydevd_dir, 'pydevd.py'))

_run_and_check(testdir, testdir.makepyfile('''
_run_and_check(testdir_or_pytester, testdir_or_pytester.makepyfile('''
import sys
sys.path.append(%(pydevd_dir)r)
import pydevd
Expand All @@ -33,7 +48,7 @@ def test_run(testdir):
py_db.run(%(foo_dir)r)
''' % locals()))

_run_and_check(testdir, testdir.makepyfile('''
_run_and_check(testdir_or_pytester, testdir_or_pytester.makepyfile('''
import sys
sys.path.append(%(pydevd_dir)r)
import pydevd
Expand All @@ -45,7 +60,7 @@ def test_run(testdir):
# Not valid for Python 2.6
return

_run_and_check(testdir, testdir.makepyfile('''
_run_and_check(testdir_or_pytester, testdir_or_pytester.makepyfile('''
import sys
sys.path.append(%(pydevd_dir)r)
sys.argv.append('--as-module')
Expand All @@ -55,7 +70,7 @@ def test_run(testdir):
py_db.run(%(foo_module)r, is_module=True)
''' % locals()))

_run_and_check(testdir, testdir.makepyfile('''
_run_and_check(testdir_or_pytester, testdir_or_pytester.makepyfile('''
import sys
sys.argv.append('--as-module')
sys.path.append(%(pydevd_dir)r)
Expand All @@ -65,7 +80,7 @@ def test_run(testdir):
''' % locals()))


def test_run_on_local_module_without_adding_to_pythonpath(testdir):
def test_run_on_local_module_without_adding_to_pythonpath(testdir_or_pytester):
import sys
import os

Expand All @@ -76,7 +91,7 @@ def test_run_on_local_module_without_adding_to_pythonpath(testdir):
with open(os.path.join(os.getcwd(), 'local_foo.py'), 'w') as stream:
stream.write('print("WorkedLocalFoo")')

_run_and_check(testdir, testdir.makepyfile('''
_run_and_check(testdir_or_pytester, testdir_or_pytester.makepyfile('''
import sys
import os
sys.path.append(%(pydevd_dir)r)
Expand All @@ -90,7 +105,7 @@ def test_run_on_local_module_without_adding_to_pythonpath(testdir):
py_db.run(%(foo_module)r, is_module=True)
''' % locals()), check_for='WorkedLocalFoo')

_run_and_check(testdir, testdir.makepyfile('''
_run_and_check(testdir_or_pytester, testdir_or_pytester.makepyfile('''
import sys
import os
sys.argv.append('--as-module')
Expand Down

0 comments on commit 6134532

Please sign in to comment.