Use trace function to detect line problems

This commit is contained in:
Rich Chiodo false 2024-05-01 16:23:44 -07:00
parent b83cefd918
commit 498a0e70e4
3 changed files with 64 additions and 32 deletions

View file

@ -12,7 +12,7 @@ from debugpy import adapter
from debugpy.adapter import components
from debugpy.common import json, log, messaging, sockets
from debugpy.common.messaging import MessageDict, Request
from debugpy.common.util import IDMap, contains_line
from debugpy.common.util import IDMap
from debugpy.server import eval, new_dap_id
from debugpy.server.tracing import (
Breakpoint,
@ -290,7 +290,11 @@ class Adapter:
if thread is None:
raise request.isnt_valid(f'Unknown thread with "threadId":{thread_id}')
stop_frame = thread.stack_trace_len() if levels == () or levels == 0 else start_frame + levels
stop_frame = (
thread.stack_trace_len()
if levels == () or levels == 0
else start_frame + levels
)
log.info(f"stackTrace info {start_frame} {stop_frame}")
frames = None
try:
@ -351,7 +355,7 @@ class Adapter:
return {}
def gotoTargets_request(self, request: Request) -> dict:
source = request("source", json.object())
source = request("source", json.object())
path = source("path", str)
source = Source(path)
line = request("line", int)
@ -359,7 +363,6 @@ class Adapter:
target = {"id": target_id, "label": f"({path}:{line})", "line": line}
return {"targets": [target]}
def goto_request(self, request: Request) -> dict:
thread_id = request("threadId", int)
@ -370,20 +373,36 @@ class Adapter:
try:
source, line = self._goto_targets_map.obtain_value(target_id)
except KeyError:
return request.isnt_valid('Invalid targetId for goto')
return request.isnt_valid("Invalid targetId for goto")
# Make sure the thread is in the same source file
current_path = inspect.getsourcefile(thread.current_frame.f_code)
current_source = Source(current_path) if current_path is not None else None
if current_source != source:
return request.cant_handle(f'{source} is not in the same code block as the current frame', silent=True)
# Make sure line number is in the same code black
if not contains_line(thread.current_frame.f_code, line):
return request.cant_handle(f'Line {line} is not in the same code block as the current frame', silent=True)
return request.cant_handle(
f"{source} is not in the same code block as the current frame",
silent=True,
)
self._tracer.goto(thread, source, line)
return {}
# Create a callback for when the goto actually finishes. We don't
# want to send our response until then.
def goto_finished(e: Exception | None):
log.info(f"Inside goto finished handler for {line}")
if e is not None:
request.cant_handle(
f"Line {line} is not in the same code block as the current frame",
silent=True
)
else:
request.respond({})
self._tracer.goto(
thread, source, line, goto_finished
)
# Response will happen when the line_change_callback happens
return messaging.NO_RESPONSE
def exceptionInfo_request(self, request: Request):
thread_id = request("threadId", int)

View file

@ -14,7 +14,7 @@ from debugpy.server.eval import Scope, VariableContainer
from enum import Enum
from pathlib import Path
from sys import monitoring
from types import CodeType, FrameType
from types import CodeType, FrameType, FunctionType
from typing import Any, ClassVar, Generator, Literal, Union, override
# Shared for all global state pertaining to breakpoints and stepping.
@ -145,10 +145,10 @@ class Thread:
can exclude a specific thread from tracing.
"""
pending_ip: int | None
pending_callback: FunctionType | None
"""
As a result of a https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Goto
this is the line number for the current thread to switch to while it is stopped.
this is a callback to run when the thread is notified after a goto
"""
_all: ClassVar[dict[int, "Thread"]] = {}
@ -163,7 +163,7 @@ class Thread:
self.current_frame = None
self.is_known_to_adapter = False
self.is_traced = True
self.pending_ip = None
self.pending_callback = None
# Thread IDs are serialized as JSON numbers in DAP, which are handled as 64-bit
# floats by most DAP clients. However, OS thread IDs can be large 64-bit integers
@ -284,7 +284,6 @@ class Thread:
log.info("{0}", f"{self}: End stack trace.")
class StackFrame:
"""
Represents a DAP StackFrame object. Instances must never be created directly;

View file

@ -25,7 +25,7 @@ from debugpy.server.tracing import (
is_internal_python_frame,
)
from sys import monitoring
from types import CodeType, FrameType, TracebackType
from types import CodeType, FrameType, FunctionType, TracebackType
from typing import Literal
@ -236,20 +236,38 @@ class Tracer:
self._end_stop()
monitoring.restart_events()
def goto(self, thread: Thread, source: Source, line: int):
def goto(
self, thread: Thread, source: Source, line: int, finish_callback: FunctionType
):
log.info(f"Goto {source}:{line} on {thread}")
"""
Change the instruction pointer of the current thread to point to
the new line/source file.
"""
def goto_handler():
log.info(f"Inside goto handler for {thread}:{line}")
# Filter out runtime warnings that come from doing a goto
with warnings.catch_warnings():
warnings.simplefilter("ignore")
try:
thread.current_frame.f_lineno = line
except ValueError as e:
finish_callback(e)
else:
finish_callback(None)
# Send a stop once we finish changing the line number
self._begin_stop(thread, "goto")
with _cvar:
thread.pending_ip = line
# We do this with a callback because only the trace function
# can change the lineno
thread.pending_callback = goto_handler
# Notify just this thread
_cvar.notify(thread.id)
# Act like a new stop happened
self._begin_stop(thread, "goto")
def _begin_stop(
self,
thread: Thread,
@ -308,15 +326,11 @@ class Tracer:
while self._stopped_by is not None:
_cvar.wait()
# This thread may have had its IP changed. We
# want to change the IP before we resume but we
# need to change the IP during a trace callback.
if thread.pending_ip is not None:
# Filter out runtime warnings that come from doing a goto
with warnings.catch_warnings():
warnings.simplefilter("ignore")
thread.current_frame.f_lineno = thread.pending_ip
thread.pending_ip = None
# This thread may need to run some code while it's still
# stopped.
if thread.pending_callback is not None:
thread.pending_callback()
thread.pending_callback = None
thread.current_frame = None
log.info(f"{thread} resumed.")