mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Use trace function to detect line problems
This commit is contained in:
parent
b83cefd918
commit
498a0e70e4
3 changed files with 64 additions and 32 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue