PY-72345 Pycharm 2024.1 Broken debug on Python 3.12-3.13

Add a processing function for new breakpoints.


Merge-request: IJ-MR-152628
Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com>

(cherry picked from commit 63ebb4c7c620cf7cc3f56924619fc5adc09e25dd)

IJ-MR-152628

GitOrigin-RevId: 1f26240498360aff61ff27878118b0eb841ec082
This commit is contained in:
Egor Eliseev
2025-01-09 23:14:43 +00:00
committed by intellij-monorepo-bot
parent 55ce70bcba
commit b0995cd9a1
4 changed files with 141 additions and 1 deletions

View File

@@ -35,6 +35,10 @@ try:
except AttributeError: except AttributeError:
pass pass
_EVENT_ACTIONS = {
"ADD": lambda x, y: x | y,
"REMOVE": lambda x, y: x & ~y,
}
def _make_frame_cache_key(code): def _make_frame_cache_key(code):
return code.co_firstlineno, code.co_name, code.co_filename return code.co_firstlineno, code.co_name, code.co_filename
@@ -121,6 +125,10 @@ def _should_enable_line_events_for_code(frame, code, filename, info):
# will match either global or some function # will match either global or some function
if breakpoint.func_name in ('None', curr_func_name): if breakpoint.func_name in ('None', curr_func_name):
has_breakpoint_in_frame = True has_breakpoint_in_frame = True
# New breakpoint was processed -> stop tracing monitoring.events.INSTRUCTION
if getattr(breakpoint, '_not_processed', None):
breakpoint._not_processed = False
_modify_global_events(_EVENT_ACTIONS["REMOVE"], monitoring.events.INSTRUCTION)
break break
# Check is f_back has a breakpoint => need register return event # Check is f_back has a breakpoint => need register return event
@@ -200,6 +208,7 @@ def enable_pep669_monitoring():
(monitoring.events.LINE, py_line_callback), (monitoring.events.LINE, py_line_callback),
(monitoring.events.PY_RETURN, py_return_callback), (monitoring.events.PY_RETURN, py_return_callback),
(monitoring.events.RAISE, py_raise_callback), (monitoring.events.RAISE, py_raise_callback),
(monitoring.events.INSTRUCTION, instruction_callback),
): ):
monitoring.register_callback(DEBUGGER_ID, event_type, callback) monitoring.register_callback(DEBUGGER_ID, event_type, callback)
@@ -208,6 +217,20 @@ def enable_pep669_monitoring():
debugger.is_pep669_monitoring_enabled = True debugger.is_pep669_monitoring_enabled = True
def process_new_breakpoint(breakpoint):
breakpoint._not_processed = True
_modify_global_events(_EVENT_ACTIONS["ADD"], monitoring.events.INSTRUCTION)
def _modify_global_events(action, event):
DEBUGGER_ID = monitoring.DEBUGGER_ID
if not monitoring.get_tool(DEBUGGER_ID):
return
current_events = monitoring.get_events(DEBUGGER_ID)
monitoring.set_events(DEBUGGER_ID, action(current_events, event))
def _enable_return_tracing(code): def _enable_return_tracing(code):
local_events = monitoring.get_local_events(monitoring.DEBUGGER_ID, code) local_events = monitoring.get_local_events(monitoring.DEBUGGER_ID, code)
monitoring.set_local_events(monitoring.DEBUGGER_ID, code, monitoring.set_local_events(monitoring.DEBUGGER_ID, code,
@@ -220,6 +243,57 @@ def _enable_line_tracing(code):
local_events | monitoring.events.LINE) local_events | monitoring.events.LINE)
def instruction_callback(code, instruction_offset):
try:
py_db = GlobalDebuggerHolder.global_dbg
except AttributeError:
return
if py_db is None:
return monitoring.DISABLE
frame = _getframe(1)
# print('ENTER: INSTRUCTION ', code.co_filename, frame.f_lineno, code.co_name)
try:
if py_db._finish_debugging_session:
return monitoring.DISABLE
thread = get_current_thread()
if not is_thread_alive(thread):
return
frame_cache_key = _make_frame_cache_key(code)
info = _get_additional_info(thread)
pydev_step_cmd = info.pydev_step_cmd
is_stepping = pydev_step_cmd != -1
if not is_stepping and frame_cache_key in global_cache_skips:
return
abs_path_real_path_and_base = _get_abs_path_real_path_and_base_from_frame(frame)
filename = abs_path_real_path_and_base[1]
breakpoints_for_file = (py_db.breakpoints.get(filename)
or py_db.has_plugin_line_breaks)
if not breakpoints_for_file and not is_stepping:
return
if _should_enable_line_events_for_code(frame, code, filename, info):
_enable_line_tracing(code)
_enable_return_tracing(code)
except SystemExit:
return monitoring.DISABLE
except Exception:
try:
if traceback is not None:
traceback.print_exc()
except:
pass
return monitoring.DISABLE
def py_start_callback(code, instruction_offset): def py_start_callback(code, instruction_offset):
try: try:
py_db = GlobalDebuggerHolder.global_dbg py_db = GlobalDebuggerHolder.global_dbg

View File

@@ -8,6 +8,7 @@ import pydevd_tracing
from _pydev_bundle import pydev_log from _pydev_bundle import pydev_log
from _pydev_imps._pydev_saved_modules import threading from _pydev_imps._pydev_saved_modules import threading
from _pydevd_bundle import pydevd_traceproperty, pydevd_dont_trace, pydevd_utils from _pydevd_bundle import pydevd_traceproperty, pydevd_dont_trace, pydevd_utils
from _pydevd_bundle.pydevd_pep_669_tracing import process_new_breakpoint
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
from _pydevd_bundle.pydevd_breakpoints import LineBreakpoint, get_exception_class from _pydevd_bundle.pydevd_breakpoints import LineBreakpoint, get_exception_class
from _pydevd_bundle.pydevd_comm import (CMD_RUN, CMD_VERSION, CMD_LIST_THREADS, from _pydevd_bundle.pydevd_comm import (CMD_RUN, CMD_VERSION, CMD_LIST_THREADS,
@@ -452,6 +453,10 @@ def process_net_command(py_db, cmd_id, seq, text):
id_to_pybreakpoint[breakpoint_id] = breakpoint id_to_pybreakpoint[breakpoint_id] = breakpoint
py_db.consolidate_breakpoints(file, id_to_pybreakpoint, breakpoints) py_db.consolidate_breakpoints(file, id_to_pybreakpoint, breakpoints)
if py_db.is_pep669_monitoring_enabled:
process_new_breakpoint(breakpoint)
if py_db.plugin is not None: if py_db.plugin is not None:
py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks() py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks()
if py_db.has_plugin_line_breaks: if py_db.has_plugin_line_breaks:

View File

@@ -0,0 +1,27 @@
import time
def calculate_sum(a, b):
# Use a breakpoint here to inspect the program flow
result = a + b
return result
def greet(name):
# Use a breakpoint here to debug the call
print(f"Hello, {name}!")
if __name__ == "__main__":
counter = 0
while True:
print("\nIteration:", counter) # You can set a breakpoint here while the loop runs
num1 = counter
num2 = counter + 1
sum_result = calculate_sum(num1, num2) # Step into this function during debugging
print(f"The sum of {num1} and {num2} is: {sum_result}")
greet("Debugger") # Also step into this function if needed
counter += 1
# Add a sleep to slow down the loop and make debugging easier
time.sleep(2)

View File

@@ -1,16 +1,25 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.env.debug package com.jetbrains.env.debug
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.xdebugger.XDebuggerManager import com.intellij.xdebugger.XDebuggerManager
import com.jetbrains.env.PyEnvTestCase import com.jetbrains.env.PyEnvTestCase
import org.junit.Test import com.jetbrains.python.run.AbstractPythonRunConfiguration
import com.jetbrains.python.run.PythonRunConfiguration
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test
class PythonDebuggerAggregatorTest: PyEnvTestCase() { class PythonDebuggerAggregatorTest: PyEnvTestCase() {
@Test @Test
fun testSeveralReturnSignal() { fun testSeveralReturnSignal() {
runPythonTest(object: PyDebuggerTask("/debug", "test_several_return_signal.py") { runPythonTest(object: PyDebuggerTask("/debug", "test_several_return_signal.py") {
override fun createRunConfiguration(sdkHome: String, existingSdk: Sdk?): AbstractPythonRunConfiguration<*> {
val runConfiguration = super.createRunConfiguration(sdkHome, existingSdk) as PythonRunConfiguration
runConfiguration.envs["PYDEVD_USE_CYTHON"] = "NO"
return runConfiguration
}
override fun before() { override fun before() {
toggleBreakpoints(6) toggleBreakpoints(6)
} }
@@ -48,4 +57,29 @@ class PythonDebuggerAggregatorTest: PyEnvTestCase() {
} }
}) })
} }
@Test
fun testAddBreakpointAfterRun()
{
runPythonTest(object: PyDebuggerTask("/debug", "test_add_breakpoint_after_run.py") {
override fun createRunConfiguration(sdkHome: String, existingSdk: Sdk?): AbstractPythonRunConfiguration<*> {
val runConfiguration = super.createRunConfiguration(sdkHome, existingSdk) as PythonRunConfiguration
runConfiguration.envs["PYDEVD_USE_CYTHON"] = "NO"
return runConfiguration
}
override fun before() {
setWaitForTermination(false)
toggleBreakpoints(20)
}
override fun testing() {
waitForPause()
removeBreakpoint(scriptName, 20)
resume()
toggleBreakpoints(20)
waitForPause()
}
})
}
} }