diff --git a/stubs/gdb/@tests/stubtest_allowlist.txt b/stubs/gdb/@tests/stubtest_allowlist.txt index 0defd3d50977..48cf4c3c84b5 100644 --- a/stubs/gdb/@tests/stubtest_allowlist.txt +++ b/stubs/gdb/@tests/stubtest_allowlist.txt @@ -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 @@ -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 @@ -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 diff --git a/stubs/gdb/METADATA.toml b/stubs/gdb/METADATA.toml index 5fcf687ea80b..2b38acb6423b 100644 --- a/stubs/gdb/METADATA.toml +++ b/stubs/gdb/METADATA.toml @@ -11,6 +11,5 @@ extra-description = """\ """ [tool.stubtest] -skip = true # https://github.com/python/typeshed/issues/15236 ci-platforms = ["linux"] apt-dependencies = ["gdb"] diff --git a/stubs/gdb/gdb/__init__.pyi b/stubs/gdb/gdb/__init__.pyi index 5911d37ab914..7fa0a812c854 100644 --- a/stubs/gdb/gdb/__init__.pyi +++ b/stubs/gdb/gdb/__init__.pyi @@ -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: ... @@ -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 @@ -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 @@ -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: ... @@ -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: ... @@ -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: ... diff --git a/stubs/gdb/gdb/dap/events.pyi b/stubs/gdb/gdb/dap/events.pyi index 7cd269266eff..ac37ed7da0ca 100644 --- a/stubs/gdb/gdb/dap/events.pyi +++ b/stubs/gdb/gdb/dap/events.pyi @@ -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] diff --git a/stubs/gdb/gdb/dap/frames.pyi b/stubs/gdb/gdb/dap/frames.pyi index 3c7d516e36c6..7147b47031c0 100644 --- a/stubs/gdb/gdb/dap/frames.pyi +++ b/stubs/gdb/gdb/dap/frames.pyi @@ -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]]: ... diff --git a/stubs/gdb/gdb/dap/launch.pyi b/stubs/gdb/gdb/dap/launch.pyi index 5d573114df88..165b3cbb46d9 100644 --- a/stubs/gdb/gdb/dap/launch.pyi +++ b/stubs/gdb/gdb/dap/launch.pyi @@ -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: ... diff --git a/stubs/gdb/gdb/dap/scopes.pyi b/stubs/gdb/gdb/dap/scopes.pyi index c5421b6f747f..c9baf69cd217 100644 --- a/stubs/gdb/gdb/dap/scopes.pyi +++ b/stubs/gdb/gdb/dap/scopes.pyi @@ -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: ... diff --git a/stubs/gdb/gdb/dap/server.pyi b/stubs/gdb/gdb/dap/server.pyi index 8d9f49c16193..7e534c2fb383 100644 --- a/stubs/gdb/gdb/dap/server.pyi +++ b/stubs/gdb/gdb/dap/server.pyi @@ -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]: ... @@ -42,12 +50,18 @@ 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: @@ -55,7 +69,7 @@ class _Wrapper: 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): ... diff --git a/stubs/gdb/gdb/dap/sources.pyi b/stubs/gdb/gdb/dap/sources.pyi index 7adee35f2c9b..e29da5270a6c 100644 --- a/stubs/gdb/gdb/dap/sources.pyi +++ b/stubs/gdb/gdb/dap/sources.pyi @@ -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: ... diff --git a/stubs/gdb/gdb/dap/startup.pyi b/stubs/gdb/gdb/dap/startup.pyi index 107a408901d1..a6c2fa239a1d 100644 --- a/stubs/gdb/gdb/dap/startup.pyi +++ b/stubs/gdb/gdb/dap/startup.pyi @@ -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]: ... diff --git a/stubs/gdb/gdb/printing.pyi b/stubs/gdb/gdb/printing.pyi index f3348f1569ae..c9a8d75da639 100644 --- a/stubs/gdb/gdb/printing.pyi +++ b/stubs/gdb/gdb/printing.pyi @@ -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): ... diff --git a/tests/stubtest_third_party.py b/tests/stubtest_third_party.py index e74258d2b0c2..1a4a95c0eebd 100755 --- a/tests/stubtest_third_party.py +++ b/tests/stubtest_third_party.py @@ -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 @@ -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) @@ -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.