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:
pass
_EVENT_ACTIONS = {
"ADD": lambda x, y: x | y,
"REMOVE": lambda x, y: x & ~y,
}
def _make_frame_cache_key(code):
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
if breakpoint.func_name in ('None', curr_func_name):
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
# 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.PY_RETURN, py_return_callback),
(monitoring.events.RAISE, py_raise_callback),
(monitoring.events.INSTRUCTION, instruction_callback),
):
monitoring.register_callback(DEBUGGER_ID, event_type, callback)
@@ -208,6 +217,20 @@ def enable_pep669_monitoring():
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):
local_events = monitoring.get_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)
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):
try:
py_db = GlobalDebuggerHolder.global_dbg

View File

@@ -8,6 +8,7 @@ import pydevd_tracing
from _pydev_bundle import pydev_log
from _pydev_imps._pydev_saved_modules import threading
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_breakpoints import LineBreakpoint, get_exception_class
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
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:
py_db.has_plugin_line_breaks = py_db.plugin.has_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.
package com.jetbrains.env.debug
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.xdebugger.XDebuggerManager
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.Test
class PythonDebuggerAggregatorTest: PyEnvTestCase() {
@Test
fun testSeveralReturnSignal() {
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() {
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()
}
})
}
}