fabioz / PyDev.Debugger

Sources for the debugger used in PyDev, PyCharm and VSCode Python
Eclipse Public License 1.0
419 stars 121 forks source link

Breakpoints not respected when invoked using debugpy #250

Open ReggieMarr opened 1 year ago

ReggieMarr commented 1 year ago

Environment data

~ [ python3.9 -m debugpy --version                                                                                                                 ] 10:11 AM
Unable to load extension: pydevd_plugins.extensions.types.pydevd_plugin_pandas_types
1.5.1
~ [ python3.9 --version                                                                                                                            ] 10:11 AM
Python 3.9.12
~ [ uname -a                                                                                                                                       ] 10:12 AM
Linux fedora 6.2.8-100.fc36.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Mar 22 19:14:19 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
~ [ emacs --version                                                                                                                                ] 10:12 AM
emacsclient 28.2

Using dap-mode with the following template:

(dap-register-debug-template
  "Python :: Run file (buffer)"
  (list :type "python"
        :args ""
        :cwd nil
        :module nil
        :program nil
        :request "launch"
        :name "Python :: Run file (buffer)"))

My python test script is defined as such (with breakpoint at line 10).

#!/usr/bin/env python3
from datetime import datetime

if __name__ == "__main__":
    now = datetime.now()
    now_str = now.strftime("%d-%m-%Y_%H-%M-%S")
    print("Starting test script")

    for i in range(10):
        print(f"Printing {i} {now_str}")

    print("Completed Start script")

Actual behavior

Debugger does not stop at the desired breakpoint, instead runs application until completion with the following error/output:

Unable to load extension: pydevd_plugins.extensions.types.pydevd_plugin_pandas_types
Starting test script
Traceback (most recent call last):
  File "/home/reggiemarr/anaconda3/lib/python3.9/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py", line 267, in _on_run
    self.process_net_command_json(self.py_db, json_contents)
  File "/home/reggiemarr/anaconda3/lib/python3.9/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py", line 193, in process_net_command_json
    cmd = on_request(py_db, request)
  File "/home/reggiemarr/anaconda3/lib/python3.9/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py", line 926, in on_stacktrace_request
    self.api.request_stack(py_db, request.seq, thread_id, fmt=fmt, start_frame=start_frame, levels=levels)
  File "/home/reggiemarr/anaconda3/lib/python3.9/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py", line 202, in request_stack
    if internal_get_thread_stack.can_be_executed_by(get_current_thread_id(threading.current_thread())):
  File "/home/reggiemarr/anaconda3/lib/python3.9/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py", line 672, in can_be_executed_by
    self._cmd = py_db.cmd_factory.make_get_thread_stack_message(
  File "/home/reggiemarr/anaconda3/lib/python3.9/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py", line 275, in make_get_thread_stack_message
    end = min(start + levels, total_frames)
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
Printing 0 06-04-2023_09-59-42
Printing 1 06-04-2023_09-59-42
Printing 2 06-04-2023_09-59-42
Printing 3 06-04-2023_09-59-42
Printing 4 06-04-2023_09-59-42
Printing 5 06-04-2023_09-59-42
Printing 6 06-04-2023_09-59-42
Printing 7 06-04-2023_09-59-42
Printing 8 06-04-2023_09-59-42
Printing 9 06-04-2023_09-59-42
Completed Start script

Expected behavior

Run without error/stop at breakpoints (for example this stops at each iteration and was run until 3 occurred:

Unable to load extension: pydevd_plugins.extensions.types.pydevd_plugin_pandas_types
Starting test script
Printing 0 06-04-2023_09-56-37
Printing 1 06-04-2023_09-56-37
Printing 2 06-04-2023_09-56-37
Printing 3 06-04-2023_09-56-37

Steps to reproduce:

Create breakpoint at line 10, run debugger using dap mode/similar application.

Solution:

I managed to figure out what the problem is. For some reason when running the test program the start_frame is None. I found I was able to fix this with the following change to pydevd::pydevd_net_command_factory_json.py

    @overrides(NetCommandFactory.make_get_thread_stack_message)
    def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False, start_frame=0, levels=0):
        frames = []
        module_events = []

        try:
            # : :type suspended_frames_manager: SuspendedFramesManager
            suspended_frames_manager = py_db.suspended_frames_manager
            frames_list = suspended_frames_manager.get_frames_list(thread_id)
            if frames_list is None:
                # Could not find stack of suspended frame...
                if must_be_suspended:
                    return None
                else:
                    frames_list = pydevd_frame_utils.create_frames_list_from_frame(topmost_frame)

            for frame_id, frame, method_name, original_filename, filename_in_utf8, lineno, applied_mapping, show_as_current_frame in self._iter_visible_frames_info(
                    py_db, frames_list
                ):

                try:
                    module_name = str(frame.f_globals.get('__name__', ''))
                except:
                    module_name = '<unknown>'

                module_events.extend(self.modules_manager.track_module(filename_in_utf8, module_name, frame))

                presentation_hint = None
                if not getattr(frame, 'IS_PLUGIN_FRAME', False):  # Never filter out plugin frames!
                    if py_db.is_files_filter_enabled and py_db.apply_files_filter(frame, original_filename, False):
                        continue

                    if not py_db.in_project_scope(frame):
                        presentation_hint = 'subtle'

                formatted_name = self._format_frame_name(fmt, method_name, module_name, lineno, filename_in_utf8)
                if show_as_current_frame:
                    formatted_name += ' (Current frame)'
                source_reference = pydevd_file_utils.get_client_filename_source_reference(filename_in_utf8)

                if not source_reference and not applied_mapping and not os.path.exists(original_filename):
                    if getattr(frame.f_code, 'co_lnotab', None):
                        # Create a source-reference to be used where we provide the source by decompiling the code.
                        # Note: When the time comes to retrieve the source reference in this case, we'll
                        # check the linecache first (see: get_decompiled_source_from_frame_id).
                        source_reference = pydevd_file_utils.create_source_reference_for_frame_id(frame_id, original_filename)
                    else:
                        # Check if someone added a source reference to the linecache (Python attrs does this).
                        if linecache.getline(original_filename, 1):
                            source_reference = pydevd_file_utils.create_source_reference_for_linecache(
                                original_filename)

                frames.append(pydevd_schema.StackFrame(
                    frame_id, formatted_name, lineno, column=1, source={
                        'path': filename_in_utf8,
                        'sourceReference': source_reference,
                    },
                    presentationHint=presentation_hint).to_dict())
        finally:
            topmost_frame = None

        for module_event in module_events:
            py_db.writer.add_command(module_event)

        total_frames = len(frames)
        stack_frames = frames
        if bool(levels):
            start = start_frame
            # This is my fix
            if start is None:
                start = 0
            print("start:", start, "total_frames:", total_frames, " + levels:", levels)
            end = min(start + levels, total_frames)
            stack_frames = frames[start:end]

        response = pydevd_schema.StackTraceResponse(
            request_seq=seq,
            success=True,
            command='stackTrace',
            body=pydevd_schema.StackTraceResponseBody(stackFrames=stack_frames, totalFrames=total_frames))
        return NetCommand(CMD_RETURN, 0, response, is_json=True)
ReggieMarr commented 1 year ago

Note I've also made an issue in the debugpy repo here