Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions stubs/gdb/@tests/stubtest_allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ gdb.TuiWindow
gdb.command
gdb.command.explore
gdb.command.frame_filters
gdb.command.missing_debug
gdb.command.missing_files
gdb.command.pretty_printers
gdb.command.prompt
gdb.command.type_printers
Expand All @@ -55,7 +55,6 @@ gdb.prompt.prompt_substitutions

# internal module to register a printer for mpx_bound128 type
gdb.printer
gdb.printer.bound_registers

gdb.printing.RegexpCollectionPrettyPrinter.RegexpSubprinter
gdb.printing.add_builtin_pretty_printer
Expand All @@ -74,10 +73,35 @@ gdb.Progspace.xmethods
# stubtest thinks this can't be sub-classed at runtime, but it is
gdb.disassembler.DisassemblerPart

gdb.TuiEnabledEvent
gdb.events.tui_enabled

gdb.Progspace.missing_debug_handlers
gdb.missing_debug_handlers
gdb.missing_files
gdb.missing_objfile
# internal sequence type returned by btrace recordings (e.g. Record.instruction_history)
gdb.BtraceObjectList

# internal compatibility wrappers around str.isalnum() and str.isascii()
gdb.missing_files.isalnum
gdb.missing_files.isascii

# Added in GDB 17, but the stubs are for GDB 16.3
gdb.COLORSPACE_AIXTERM_16COLOR
gdb.COLORSPACE_ANSI_8COLOR
gdb.COLORSPACE_MONOCHROME
gdb.COLORSPACE_RGB_24BIT
gdb.COLORSPACE_XTERM_256COLOR
gdb.Color
gdb.PARAM_COLOR
gdb.ParameterPrefix
gdb.Value.is_unavailable
gdb.dap.completions
gdb.dap.server.DeferredRequest.defer_events
gdb.dap.server.Server.emit_pending_events
gdb.dap.server.Server.set_defer_events
gdb.dap.server.export_column
gdb.dap.server.import_column

# Removed or changed in GDB 17, but the stubs are for GDB 16.3
gdb.dap.server.Server.send_event
gdb.dap.server.Server.send_event_later
gdb.dap.server.request

# TODO: add stubs for these modules
gdb.dap.globalvars
gdb.ptwrite
1 change: 0 additions & 1 deletion stubs/gdb/METADATA.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ extra-description = """\
"""

[tool.stubtest]
skip = true # https://github.com/python/typeshed/issues/15236
ci-platforms = ["linux"]
apt-dependencies = ["gdb"]
20 changes: 18 additions & 2 deletions stubs/gdb/gdb/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,17 @@ class Value:
def __mod__(self, other: _ValueOrNative, /) -> Value: ...
def __rmod__(self, other: _ValueOrNative, /) -> Value: ...
def __pow__(self, other: _ValueOrNative, mod: None = None, /) -> Value: ...
def __rpow__(self, other: _ValueOrNative, mod: None = None, /) -> Value: ...
def __and__(self, other: _ValueOrNative, /) -> Value: ...
def __rand__(self, other: _ValueOrNative, /) -> Value: ...
def __or__(self, other: _ValueOrNative, /) -> Value: ...
def __ror__(self, other: _ValueOrNative, /) -> Value: ...
def __xor__(self, other: _ValueOrNative, /) -> Value: ...
def __rxor__(self, other: _ValueOrNative, /) -> Value: ...
def __lshift__(self, other: _ValueOrNative, /) -> Value: ...
def __rlshift__(self, other: _ValueOrNative, /) -> Value: ...
def __rshift__(self, other: _ValueOrNative, /) -> Value: ...
def __rrshift__(self, other: _ValueOrNative, /) -> Value: ...
def __eq__(self, other: _ValueOrNative, /) -> bool: ... # type: ignore[override]
def __ne__(self, other: _ValueOrNative, /) -> bool: ... # type: ignore[override]
def __lt__(self, other: _ValueOrNative, /) -> bool: ...
Expand Down Expand Up @@ -403,8 +409,13 @@ class RecordInstruction(Instruction):

class RecordGap(Instruction):
number: int
error_code: int
error_string: str
reason_code: int
reason_string: str

@final
class RecordAuxiliary:
number: int
data: str

class RecordFunctionSegment:
number: int
Expand Down Expand Up @@ -493,6 +504,8 @@ class Function:
def current_progspace() -> Progspace | None: ...
def progspaces() -> Sequence[Progspace]: ...

missing_file_handlers: Sequence[tuple[Literal["debug"], MissingDebugHandler] | tuple[Literal["file"], MissingFileHandler]]

@final
class Progspace:
executable_filename: str | None
Expand Down Expand Up @@ -593,6 +606,7 @@ class Block:
static_block: Block | None
is_global: bool
is_static: bool
subblocks: list[Block]

def is_valid(self) -> bool: ...
def __iter__(self) -> BlockIterator: ...
Expand Down Expand Up @@ -625,6 +639,7 @@ class Symbol:
is_function: bool
is_variable: bool
is_artificial: bool
domain: int

def is_valid(self) -> bool: ...
def value(self, frame: Frame = ..., /) -> Value: ...
Expand Down Expand Up @@ -903,6 +918,7 @@ class Architecture:
def name(self) -> str: ...
def disassemble(self, start_pc: int, end_pc: int = ..., count: int = ...) -> list[_Instruction]: ...
def integer_type(self, size: int, signed: bool = ...) -> Type: ...
def void_type(self) -> Type: ...
def registers(self, reggroup: str = ...) -> RegisterDescriptorIterator: ...
def register_groups(self) -> RegisterGroupsIterator: ...

Expand Down
2 changes: 1 addition & 1 deletion stubs/gdb/gdb/dap/events.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def send_process_event_once() -> None: ...
def expect_process(reason: str) -> None: ...
def thread_event(event: gdb.ThreadEvent, reason: str) -> None: ...
def expect_stop(reason: str) -> None: ...
def exec_and_expect_stop(cmd: str, expected_pause: bool = False) -> None: ...
def exec_and_expect_stop(cmd: str, expected_pause: bool = False, propagate_exception: bool = False) -> None: ...

# Map from gdb stop reasons to DAP stop reasons.
stop_reason_map: dict[str, str]
2 changes: 2 additions & 0 deletions stubs/gdb/gdb/dap/frames.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ from collections.abc import Generator

import gdb

thread_ids: dict[int, int] = {}

def frame_for_id(id: int) -> gdb.Frame: ...
def select_frame(id: int) -> None: ...
def dap_frame_generator(frame_low: int, levels: int, include_all: bool) -> Generator[tuple[int, gdb.Frame]]: ...
1 change: 1 addition & 0 deletions stubs/gdb/gdb/dap/launch.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ def launch(
): ...
def attach(*, program: str | None = None, pid: int | None = None, target: str | None = None, **args: Unused) -> None: ...
def config_done(**args: Unused) -> None: ...
def file_command(program: str) -> None: ...
12 changes: 5 additions & 7 deletions stubs/gdb/gdb/dap/scopes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,19 @@ def symbol_value(sym: SymValueWrapper, frame: FrameDecorator) -> tuple[str, gdb.

class _ScopeReference(BaseReference):
hint: str
frame: FrameDecorator
inf_frame: gdb.Frame
function: str | None
line: int | None
frameId: int
var_list: tuple[SymValueWrapper, ...]
def __init__(self, name: str, hint: str, frame: FrameDecorator, var_list: Iterable[SymValueWrapper]) -> None: ...
def __init__(self, name: str, hint: str, frameId: int, var_list: Iterable[SymValueWrapper]) -> None: ...
def to_object(self) -> _ScopeReferenceDescriptor: ...
def has_children(self) -> bool: ...
def child_count(self) -> int: ...
# note: parameter named changed from 'index' to 'idx'
def fetch_one_child(self, idx: int) -> tuple[str, gdb.Value]: ...

class _FinishScopeReference(_ScopeReference): ...
class _FinishScopeReference(_ScopeReference):
def __init__(self, frameId: int) -> None: ...

class _RegisterReference(_ScopeReference):
def __init__(self, name: str, frame: FrameDecorator) -> None: ...
def __init__(self, name: str, frameId: int) -> None: ...

def scopes(*, frameId: int, **extra: Unused) -> _ScopesResult: ...
22 changes: 18 additions & 4 deletions stubs/gdb/gdb/dap/server.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,23 @@ _RequestID: TypeAlias = int
_EventBody: TypeAlias = Any # arbitrary object, implicitly constrained by the event being sent
_JSONValue: TypeAlias = Any # any object that can be handled by json.dumps/json.loads

class DeferredRequest:
def set_request(self, req: _RequestID, result: dict[str, Any]) -> None: ...
def invoke(self) -> _JSONValue | None: ...
def reschedule(self) -> None: ...

class NotStoppedException(Exception): ...

class CancellationHandler:
lock: threading.Lock
lock: threading.RLock
reqs: list[_RequestID]
in_flight_dap_thread: _RequestID | None
in_flight_gdb_thread: _RequestID | None
def starting(self, req: _RequestID) -> None: ...
def done(self, req: _RequestID) -> None: ... # req argument is not used
deferred_ids: set[_RequestID]
def current_request(self, req: _RequestID) -> AbstractContextManager[None]: ...
def defer_request(self, req: _RequestID) -> None: ...
def request_finished(self, req: _RequestID) -> None: ...
def check_cancel(self, req: _RequestID) -> None: ...
def cancel(self, req: _RequestID) -> None: ...
def interruptable_region(self, req: _RequestID | None) -> AbstractContextManager[None]: ...

Expand All @@ -42,20 +50,26 @@ class Server:
out_stream: _SupportsWriteAndFlushBytes,
child_stream: SupportsReadline[str],
) -> None: ...
def call_function_later(self, fn: Callable[[], DeferredRequest | _JSONValue | None]) -> None: ...
def invoke_request(self, req: _RequestID, result: dict[str, Any], fn: Callable[[], object]) -> None: ...
def main_loop(self) -> None: ...
def send_event(self, event: str, body: _EventBody | None = None) -> None: ...
def send_event_later(self, event: str, body: _EventBody | None = None) -> None: ...
def send_event_maybe_later(self, event: str, body: _EventBody | None = None) -> None: ...
def shutdown(self) -> None: ...

def send_event(event: str, body: _EventBody | None = None) -> None: ...
def call_function_later(fn: Callable[[], object]) -> None: ...
def export_line(line: int) -> int: ...
def import_line(line: int) -> int: ...

@type_check_only
class _Wrapper:
def __call__(self, func: _F) -> _F: ...

def request(name: str, *, response: bool = True, on_dap_thread: bool = False, expect_stopped: bool = True) -> _Wrapper: ...
def capability(name: str, value: bool = True) -> _Wrapper: ...
def client_bool_capability(name: str) -> bool: ...
def client_bool_capability(name: str, default: bool = False) -> bool: ...
def initialize(**args) -> dict[str, bool]: ... # args is arbitrary values for Server.config
def terminate(**args: Unused) -> None: ...
def disconnect(*, terminateDebuggee: bool = False, **args: Unused): ...
Expand Down
2 changes: 1 addition & 1 deletion stubs/gdb/gdb/dap/sources.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class _LoadSourcesResult(TypedDict):
class _SourceResult(TypedDict):
content: str

def make_source(fullname: str, filename: str | None) -> Source: ...
def make_source(fullname: str, filename: str | None = None) -> Source: ...
def decode_source(source: Source) -> _SourceReferenceID: ...
def loaded_sources(**extra: Unused) -> _LoadSourcesResult: ...
def source(*, source: Source | None = None, sourceReference: _SourceReferenceID, **extra: Unused) -> _SourceResult: ...
3 changes: 2 additions & 1 deletion stubs/gdb/gdb/dap/startup.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ dap_log: LoggingParam
def log(something: object, level: LogLevel = LogLevel.DEFAULT) -> None: ...
def thread_log(something: object, level: LogLevel = LogLevel.DEFAULT) -> None: ...
def log_stack(level: LogLevel = LogLevel.DEFAULT) -> None: ...
def exec_and_log(cmd: str) -> None: ...
def exec_and_log(cmd: str, propagate_exception: bool = False) -> None: ...
def exec_mi_and_log(*args: str) -> dict[str, object]: ...
5 changes: 5 additions & 0 deletions stubs/gdb/gdb/printing.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ class NoOpScalarPrinter(gdb.ValuePrinter):
def __init__(self, value) -> None: ...
def to_string(self) -> str: ...

class NoOpStringPrinter(gdb.ValuePrinter):
def __init__(self, ty, value) -> None: ...
def display_hint(self) -> str: ...
def to_string(self) -> gdb.Value | gdb.LazyString: ...

class NoOpStructPrinter(gdb.ValuePrinter):
def __init__(self, ty, value) -> None: ...
def children(self): ...
Expand Down
32 changes: 30 additions & 2 deletions tests/stubtest_third_party.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,15 @@ def run_stubtest(dist: Path, *, verbose: bool = False, ci_platforms_only: bool =
tmp = tempfile.mkdtemp(prefix="stubtest-") # TODO: Python 3.12: Use TemporaryDirectory
venv_dir = Path(tmp)
try:
extra_args: list[str] = []
if dist_name == "gdb":
version = get_gdb_python_version()
if version:
extra_args.extend(["--python", version])
else:
formatter.append_warning("Could not determine gdb's the Python version; using the default Python for venv.")
try:
subprocess.run(["uv", "venv", venv_dir, "--seed"], capture_output=True, check=True)
subprocess.run(["uv", "venv", venv_dir, "--seed", *extra_args], capture_output=True, check=True)
except subprocess.CalledProcessError as e:
formatter.command_error("fail (could not create a virtualenv)", e)
return False
Expand Down Expand Up @@ -284,7 +291,7 @@ def gdb_version_check(formatter: StatusLineFormatter) -> bool:
if "Python scripting is not supported in this copy of GDB" in gdb_version_output:
formatter.error("fail (GDB version does not support Python scripting)")
return False
m = re.search(r"GNU gdb\s+.*?(\d+\.\d+(\.[\da-z-]+)+)", gdb_version_output)
m = re.search(r"GNU gdb\s+.*?(\d+\.\d+(\.[\da-z-]+)*)", gdb_version_output)
if m is None:
formatter.error("failed to determine gdb version")
formatter.append_output(gdb_version_output)
Expand All @@ -294,6 +301,27 @@ def gdb_version_check(formatter: StatusLineFormatter) -> bool:
return True


def get_gdb_python_version() -> str | None:
"""Return the 'X.Y' Python version embedded in gdb, or None if it cannot be determined."""
gdb_cmd = [
"gdb",
"--batch",
"--ex",
"python import sys; print('GDB_PY_VERSION:%d.%d' % (sys.version_info[0], sys.version_info[1]))",
]
env = os.environ.copy()
# See setup_gdb_stubtest_command() for why LD_LIBRARY_PATH is removed.
env.pop("LD_LIBRARY_PATH", None)
try:
result = subprocess.run(gdb_cmd, env=env, capture_output=True, text=True, check=False)
except FileNotFoundError:
return None
m = re.search(r"GDB_PY_VERSION:(\d+\.\d+)", result.stdout)
if m is None:
return None
return m.group(1)


def setup_uwsgi_stubtest_command(dist: Path, venv_dir: Path, stubtest_cmd: list[str], formatter: StatusLineFormatter) -> bool:
"""Perform some black magic in order to run stubtest inside uWSGI.

Expand Down
Loading