[1/4] Introduce and use DAPException

Message ID 20231212-dap-no-test-exceptions-v1-1-af0e33f10093@adacore.com
State New
Headers
Series Check for rogue DAP exceptions |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Testing passed

Commit Message

Tom Tromey Dec. 12, 2023, 5:44 p.m. UTC
  This introduces a new DAPException class, and then changes various
spots in the DAP implementation to wrap "expected" exceptions in this.
This class will help detect rogue exceptions caused by bugs in the
implementation.
---
 gdb/python/lib/gdb/dap/breakpoint.py |  8 +++++---
 gdb/python/lib/gdb/dap/evaluate.py   | 12 ++++++------
 gdb/python/lib/gdb/dap/launch.py     |  4 ++--
 gdb/python/lib/gdb/dap/sources.py    |  6 +++---
 gdb/python/lib/gdb/dap/startup.py    | 18 ++++++++++++++++++
 gdb/python/lib/gdb/dap/varref.py     |  6 +++---
 6 files changed, 37 insertions(+), 17 deletions(-)
  

Patch

diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py
index 33aa18e65bc..c67bb471daf 100644
--- a/gdb/python/lib/gdb/dap/breakpoint.py
+++ b/gdb/python/lib/gdb/dap/breakpoint.py
@@ -24,7 +24,7 @@  from typing import Optional, Sequence
 
 from .server import request, capability, send_event
 from .sources import make_source
-from .startup import in_gdb_thread, log_stack
+from .startup import in_gdb_thread, log_stack, parse_and_eval, DAPException
 from .typecheck import type_check
 
 
@@ -168,7 +168,7 @@  def _set_breakpoints_callback(kind, specs, creator):
                 bp.ignore_count = 0
             else:
                 bp.ignore_count = int(
-                    gdb.parse_and_eval(hit_condition, global_context=True)
+                    parse_and_eval(hit_condition, global_context=True)
                 )
 
             # Reaching this spot means success.
@@ -212,6 +212,7 @@  class _PrintBreakpoint(gdb.Breakpoint):
                 # have already been stripped by the placement of the
                 # regex capture in the 'split' call.
                 try:
+                    # No real need to use the DAP parse_and_eval here.
                     val = gdb.parse_and_eval(item)
                     output += str(val)
                 except Exception as e:
@@ -360,12 +361,13 @@  def _catch_exception(filterId, **args):
     if filterId in ("assert", "exception", "throw", "rethrow", "catch"):
         cmd = "-catch-" + filterId
     else:
-        raise Exception("Invalid exception filterID: " + str(filterId))
+        raise DAPException("Invalid exception filterID: " + str(filterId))
     result = gdb.execute_mi(cmd)
     # A little lame that there's no more direct way.
     for bp in gdb.breakpoints():
         if bp.number == result["bkptno"]:
             return bp
+    # Not a DAPException because this is definitely unexpected.
     raise Exception("Could not find catchpoint after creating")
 
 
diff --git a/gdb/python/lib/gdb/dap/evaluate.py b/gdb/python/lib/gdb/dap/evaluate.py
index d39e7879205..eb38baf3973 100644
--- a/gdb/python/lib/gdb/dap/evaluate.py
+++ b/gdb/python/lib/gdb/dap/evaluate.py
@@ -20,7 +20,7 @@  from typing import Optional
 
 from .frames import select_frame
 from .server import capability, request, client_bool_capability
-from .startup import in_gdb_thread
+from .startup import in_gdb_thread, parse_and_eval, DAPException
 from .varref import find_variable, VariableReference, apply_format
 
 
@@ -37,7 +37,7 @@  def _evaluate(expr, frame_id, value_format):
         if frame_id is not None:
             select_frame(frame_id)
             global_context = False
-        val = gdb.parse_and_eval(expr, global_context=global_context)
+        val = parse_and_eval(expr, global_context=global_context)
         ref = EvaluateResult(val)
         return ref.to_object()
 
@@ -89,7 +89,7 @@  def eval_request(
         # Ignore the format for repl evaluation.
         return _repl(expression, frameId)
     else:
-        raise Exception('unknown evaluate context "' + context + '"')
+        raise DAPException('unknown evaluate context "' + context + '"')
 
 
 @request("variables")
@@ -119,8 +119,8 @@  def set_expression(
         if frameId is not None:
             select_frame(frameId)
             global_context = False
-        lhs = gdb.parse_and_eval(expression, global_context=global_context)
-        rhs = gdb.parse_and_eval(value, global_context=global_context)
+        lhs = parse_and_eval(expression, global_context=global_context)
+        rhs = parse_and_eval(value, global_context=global_context)
         lhs.assign(rhs)
         return _SetResult(lhs).to_object()
 
@@ -133,6 +133,6 @@  def set_variable(
     with apply_format(format):
         var = find_variable(variablesReference)
         lhs = var.find_child_by_name(name)
-        rhs = gdb.parse_and_eval(value)
+        rhs = parse_and_eval(value)
         lhs.assign(rhs)
         return lhs.to_object()
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
index 7014047ff51..a8adb125707 100644
--- a/gdb/python/lib/gdb/dap/launch.py
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -20,7 +20,7 @@  from typing import Mapping, Optional, Sequence
 
 from .events import exec_and_expect_stop, expect_process
 from .server import request, capability
-from .startup import exec_and_log
+from .startup import exec_and_log, DAPException
 
 
 # The program being launched, or None.  This should only be accessed
@@ -69,7 +69,7 @@  def attach(*, pid: Optional[int] = None, target: Optional[str] = None, **args):
     elif target is not None:
         cmd = "target remote " + target
     else:
-        raise Exception("attach requires either 'pid' or 'target'")
+        raise DAPException("attach requires either 'pid' or 'target'")
     expect_process("attach")
     exec_and_log(cmd)
 
diff --git a/gdb/python/lib/gdb/dap/sources.py b/gdb/python/lib/gdb/dap/sources.py
index 821205cedd1..d73a3f8c5b1 100644
--- a/gdb/python/lib/gdb/dap/sources.py
+++ b/gdb/python/lib/gdb/dap/sources.py
@@ -18,7 +18,7 @@  import os
 import gdb
 
 from .server import request, capability
-from .startup import in_gdb_thread
+from .startup import in_gdb_thread, DAPException
 
 
 # The next available source reference ID.  Must be greater than 0.
@@ -68,11 +68,11 @@  def decode_source(source):
     if "path" in source:
         return source["path"]
     if "sourceReference" not in source:
-        raise Exception("either 'path' or 'sourceReference' must appear in Source")
+        raise DAPException("either 'path' or 'sourceReference' must appear in Source")
     ref = source["sourceReference"]
     global _id_map
     if ref not in _id_map:
-        raise Exception("no sourceReference " + str(ref))
+        raise DAPException("no sourceReference " + str(ref))
     return _id_map[ref]["path"]
 
 
diff --git a/gdb/python/lib/gdb/dap/startup.py b/gdb/python/lib/gdb/dap/startup.py
index 1d3b94762a6..64a46597bf4 100644
--- a/gdb/python/lib/gdb/dap/startup.py
+++ b/gdb/python/lib/gdb/dap/startup.py
@@ -39,6 +39,24 @@  _gdb_thread = threading.current_thread()
 _dap_thread = None
 
 
+# "Known" exceptions are wrapped in a DAP exception, so that, by
+# default, only rogue exceptions are logged -- this is then used by
+# the test suite.
+class DAPException(Exception):
+    pass
+
+
+# Wrapper for gdb.parse_and_eval that turns exceptions into
+# DAPException.
+def parse_and_eval(expression, global_context=False):
+    try:
+        return gdb.parse_and_eval(expression, global_context=global_context)
+    except Exception as e:
+        # Be sure to preserve the summary, as this can propagate to
+        # the client.
+        raise DAPException(str(e)) from e
+
+
 def start_thread(name, target, args=()):
     """Start a new thread, invoking TARGET with *ARGS there.
     This is a helper function that ensures that any GDB signals are
diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py
index 8f0a070498c..c75afe3848e 100644
--- a/gdb/python/lib/gdb/dap/varref.py
+++ b/gdb/python/lib/gdb/dap/varref.py
@@ -14,7 +14,7 @@ 
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import gdb
-from .startup import in_gdb_thread
+from .startup import in_gdb_thread, DAPException
 from .server import client_bool_capability
 from abc import ABC, abstractmethod
 from collections import defaultdict
@@ -165,7 +165,7 @@  class BaseReference(ABC):
         # map here.
         if name in self.by_name:
             return self.by_name[name]
-        raise Exception("no variable named '" + name + "'")
+        raise DAPException("no variable named '" + name + "'")
 
 
 class VariableReference(BaseReference):
@@ -271,5 +271,5 @@  def find_variable(ref):
     # Variable references are offset by 1.
     ref = ref - 1
     if ref < 0 or ref > len(all_variables):
-        raise Exception("invalid variablesReference")
+        raise DAPException("invalid variablesReference")
     return all_variables[ref]