From 18d27d734a94323c37636d387a6f1d5b9285ff15 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Tue, 3 Dec 2024 08:35:06 -0700 Subject: [PATCH] Fixes for `gdb` stubs (#13169) * gdb: Clarify a comment * gdb: Fix gdb.unwinder.Unwinder.__call__ argument. It takes a gdb.PendingFrame, not a gdb.Frame. * gdb: Unwinders may implement a proto without subclassing gdb.unwinder.Unwinder * gdb: Fix Breakpoint.__init__ 1. `line` should be `int|str`, not just `int` (IDK what a string means, but that it can be a string is clear if you read py-breakpoint.c:bppy_init(). 2. `type` argument should be able to be passed to the "location" form, not just the "spec" form, even if https://sourceware.org/gdb/current/onlinedocs/gdb.html/Breakpoints-In-Python.html neglects to mention it (don't worry, I'll be submitting a patch to fix the doc soon). 3. Fix the positional argument order (based on GDB's sources, it isn't really documented) 4. Use more `@overloads` to enforce that at least 1 of `function`, `label`, or `line` are given in the location form. --- stubs/gdb/METADATA.toml | 2 +- stubs/gdb/gdb/__init__.pyi | 143 +++++++++++++++++++++++++++++++++---- stubs/gdb/gdb/unwinder.pyi | 5 +- 3 files changed, 133 insertions(+), 17 deletions(-) diff --git a/stubs/gdb/METADATA.toml b/stubs/gdb/METADATA.toml index 8aa64fa66..475788246 100644 --- a/stubs/gdb/METADATA.toml +++ b/stubs/gdb/METADATA.toml @@ -1,5 +1,5 @@ version = "15.0.*" -# This is the official web portal for GDB, +# This is the official web portal for the GDB Git repo, # see https://sourceware.org/gdb/current/ for other ways of obtaining the source code. upstream_repository = "https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=tree" extra_description = """\ diff --git a/stubs/gdb/gdb/__init__.pyi b/stubs/gdb/gdb/__init__.pyi index b7cb8209f..7bf762889 100644 --- a/stubs/gdb/gdb/__init__.pyi +++ b/stubs/gdb/gdb/__init__.pyi @@ -7,12 +7,11 @@ import threading from _typeshed import Incomplete from collections.abc import Callable, Iterator, Mapping, Sequence from contextlib import AbstractContextManager -from typing import Any, Final, Generic, Literal, Protocol, TypeVar, final, overload +from typing import Any, Final, Generic, Literal, Protocol, TypeVar, final, overload, type_check_only from typing_extensions import TypeAlias, deprecated import gdb.FrameDecorator import gdb.types -import gdb.unwinder import gdb.xmethod # The following submodules are automatically imported @@ -289,7 +288,15 @@ class PendingFrame: class UnwindInfo: def add_saved_register(self, reg: str | RegisterDescriptor | int, value: Value, /) -> None: ... -frame_unwinders: list[gdb.unwinder.Unwinder] +@type_check_only +class _Unwinder(Protocol): + @property + def name(self) -> str: ... + enabled: bool + + def __call__(self, pending_frame: PendingFrame) -> UnwindInfo | None: ... + +frame_unwinders: list[_Unwinder] # Inferiors @@ -468,7 +475,7 @@ class Progspace: pretty_printers: list[_PrettyPrinterLookupFunction] type_printers: list[gdb.types._TypePrinter] frame_filters: dict[str, _FrameFilter] - frame_unwinders: list[gdb.unwinder.Unwinder] + frame_unwinders: list[_Unwinder] missing_debug_handlers: Incomplete def block_for_pc(self, pc: int, /) -> Block | None: ... @@ -493,7 +500,7 @@ class Objfile: pretty_printers: list[_PrettyPrinterLookupFunction] type_printers: list[gdb.types._TypePrinter] frame_filters: dict[str, _FrameFilter] - frame_unwinders: list[gdb.unwinder.Unwinder] + frame_unwinders: list[_Unwinder] is_file: bool def is_valid(self) -> bool: ... @@ -664,21 +671,131 @@ class LineTable: # Breakpoints class Breakpoint: - @overload - def __init__( - self, spec: str, type: int = ..., wp_class: int = ..., internal: bool = ..., temporary: bool = ..., qualified: bool = ... - ) -> None: ... + + # The where="spec" form of __init__(). See py-breakpoints.c:bppy_init():keywords for the positional order. @overload def __init__( self, - source: str = ..., - function: str = ..., - label: str = ..., - line: int = ..., + # where + spec: str, + # options + type: int = ..., + wp_class: int = ..., internal: bool = ..., temporary: bool = ..., qualified: bool = ..., ) -> None: ... + + # The where="location" form of __init__(). A watchpoint (`type=BP_WATCHPOINT`) cannot be created with this form. + # + # We exclude the `wp_class` (watchpoint class) option here, even though py-breakpoints.c accepts it. It doesn't make sense + # unless type==BP_WATCHPOINT, and is silently ignored in those cases; allowing it in those cases is likely an oversight, not + # an intentional allowance. + # + # We repeat this 7 times because the type system doesn't have simple a way for us to say "at least one of `function`, `label`, + # or `line`", so we must repeat it for each combination of the 3. + # + # The len=3 combination. + @overload + def __init__( + self, + *, + # where + source: str = ..., + function: str, + label: str, + line: int | str, + # options + type: int = ..., + internal: bool = ..., + temporary: bool = ..., + qualified: bool = ..., + ) -> None: ... + # The 3 len=2 combinations. + @overload + def __init__( + self, + *, + source: str = ..., + # where + label: str, + line: int | str, + # options + type: int = ..., + internal: bool = ..., + temporary: bool = ..., + qualified: bool = ..., + ) -> None: ... + @overload + def __init__( + self, + *, + source: str = ..., + # where + function: str, + line: int | str, + # options + type: int = ..., + internal: bool = ..., + temporary: bool = ..., + qualified: bool = ..., + ) -> None: ... + @overload + def __init__( + self, + *, + source: str = ..., + # where + function: str, + label: str, + # options + type: int = ..., + internal: bool = ..., + temporary: bool = ..., + qualified: bool = ..., + ) -> None: ... + # The 3 len=1 combinations. + @overload + def __init__( + self, + *, + source: str = ..., + # where + function: str, + # options + type: int = ..., + internal: bool = ..., + temporary: bool = ..., + qualified: bool = ..., + ) -> None: ... + @overload + def __init__( + self, + *, + source: str = ..., + # where + label: str, + # options + type: int = ..., + internal: bool = ..., + temporary: bool = ..., + qualified: bool = ..., + ) -> None: ... + @overload + def __init__( + self, + *, + source: str = ..., + # where + line: int | str, + # options + type: int = ..., + internal: bool = ..., + temporary: bool = ..., + qualified: bool = ..., + ) -> None: ... + + # Methods. def stop(self) -> bool: ... def is_valid(self) -> bool: ... def delete(self) -> None: ... diff --git a/stubs/gdb/gdb/unwinder.pyi b/stubs/gdb/gdb/unwinder.pyi index 8d9fdd014..60a5f84d6 100644 --- a/stubs/gdb/gdb/unwinder.pyi +++ b/stubs/gdb/gdb/unwinder.pyi @@ -1,5 +1,4 @@ import gdb -from gdb import Frame, UnwindInfo class FrameId: def __init__(self, sp: gdb.Value | int, pc: gdb.Value | int, special: gdb.Value | int | None = None) -> None: ... @@ -16,6 +15,6 @@ class Unwinder: enabled: bool def __init__(self, name: str) -> None: ... - def __call__(self, pending_frame: Frame) -> UnwindInfo | None: ... + def __call__(self, pending_frame: gdb.PendingFrame) -> gdb.UnwindInfo | None: ... -def register_unwinder(locus: gdb.Objfile | gdb.Progspace | None, unwinder: Unwinder, replace: bool = ...) -> None: ... +def register_unwinder(locus: gdb.Objfile | gdb.Progspace | None, unwinder: gdb._Unwinder, replace: bool = ...) -> None: ...