Files
Tom Tromey 510586589e Add type-checking to DAP requests
It occurred to me recently that gdb's DAP implementation should
probably check the types of objects coming from the client.  This
patch implements this idea by reusing Python's existing type
annotations, and supplying a decorator that verifies these at runtime.

Python doesn't make it very easy to do runtime type-checking, so the
core of the checker is written by hand.  I haven't tried to make a
fully generic runtime type checker.  Instead, this only checks the
subset that is needed by DAP.  For example, only keyword-only
functions are handled.

Furthermore, in a few spots, it wasn't convenient to spell out the
type that is accepted.  I've added a couple of comments to this effect
in breakpoint.py.

I've tried to make this code compatible with older versions of Python,
but I've only been able to try it with 3.9 and 3.10.
2023-06-12 12:09:28 -06:00

92 lines
2.8 KiB
Python

# Copyright 2022, 2023 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gdb
import gdb.printing
# This is deprecated in 3.9, but required in older versions.
from typing import Optional
from .frames import frame_for_id
from .server import request
from .startup import send_gdb_with_response, in_gdb_thread
from .varref import find_variable, VariableReference
class EvaluateResult(VariableReference):
def __init__(self, value):
super().__init__(None, value, "result")
# Helper function to evaluate an expression in a certain frame.
@in_gdb_thread
def _evaluate(expr, frame_id):
global_context = True
if frame_id is not None:
frame = frame_for_id(frame_id)
frame.select()
global_context = False
val = gdb.parse_and_eval(expr, global_context=global_context)
ref = EvaluateResult(val)
return ref.to_object()
# Helper function to evaluate a gdb command in a certain frame.
@in_gdb_thread
def _repl(command, frame_id):
if frame_id is not None:
frame = frame_for_id(frame_id)
frame.select()
val = gdb.execute(command, from_tty=True, to_string=True)
return {
"result": val,
"variablesReference": 0,
}
# FIXME supportsVariableType handling
@request("evaluate")
def eval_request(
*,
expression: str,
frameId: Optional[int] = None,
context: str = "variables",
**args,
):
if context in ("watch", "variables"):
# These seem to be expression-like.
return send_gdb_with_response(lambda: _evaluate(expression, frameId))
elif context == "repl":
return send_gdb_with_response(lambda: _repl(expression, frameId))
else:
raise Exception(f'unknown evaluate context "{context}"')
@in_gdb_thread
def _variables(ref, start, count):
var = find_variable(ref)
children = var.fetch_children(start, count)
return [x.to_object() for x in children]
@request("variables")
# Note that we ignore the 'filter' field. That seems to be
# specific to javascript.
def variables(*, variablesReference: int, start: int = 0, count: int = 0, **args):
result = send_gdb_with_response(
lambda: _variables(variablesReference, start, count)
)
return {"variables": result}