]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Handle linesStartAt1 in DAP
authorTom Tromey <tromey@adacore.com>
Mon, 16 Dec 2024 17:44:55 +0000 (10:44 -0700)
committerTom Tromey <tromey@adacore.com>
Mon, 6 Jan 2025 14:35:21 +0000 (07:35 -0700)
The DAP initialize request has a "linesStartAt1" option, where the
client can indicate that it prefers whether line numbers be 0-based or
1-based.

This patch implements this.  I audited all the line-related code in
the DAP implementation.

Note that while a similar option exists for column numbers, gdb
doesn't handle these yet, so nothing is done here.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32468
(cherry picked from commit 8ac42dbf5001416e6b741c5361be14186afde5b0)

gdb/python/lib/gdb/dap/breakpoint.py
gdb/python/lib/gdb/dap/bt.py
gdb/python/lib/gdb/dap/disassemble.py
gdb/python/lib/gdb/dap/locations.py
gdb/python/lib/gdb/dap/scopes.py
gdb/python/lib/gdb/dap/server.py
gdb/testsuite/gdb.dap/line-zero.exp [new file with mode: 0644]
gdb/testsuite/lib/dap-support.exp

index 6adb826d2237b2676437503ab95d7d6f959c9118..f0fe0734a03820174a531f6a205798cdd8852c0a 100644 (file)
@@ -21,7 +21,7 @@ from typing import Optional, Sequence
 
 import gdb
 
-from .server import capability, request, send_event
+from .server import capability, export_line, import_line, request, send_event
 from .sources import make_source
 from .startup import (
     DAPException,
@@ -128,7 +128,7 @@ def _breakpoint_descriptor(bp):
             result.update(
                 {
                     "source": make_source(filename),
-                    "line": line,
+                    "line": export_line(line),
                 }
             )
 
@@ -281,7 +281,7 @@ def _rewrite_src_breakpoint(
 ):
     return {
         "source": source["path"],
-        "line": line,
+        "line": import_line(line),
         "condition": condition,
         "hitCondition": hitCondition,
         "logMessage": logMessage,
index 668bcc7ce238d8c4e4a9e2d0c1323c74b12af227..a27c61a6cdaeb71ab75cb7a0efb6f871cf8af686 100644 (file)
@@ -21,7 +21,7 @@ import gdb
 from .frames import dap_frame_generator
 from .modules import module_id
 from .scopes import symbol_value
-from .server import capability, request
+from .server import capability, export_line, request
 from .sources import make_source
 from .startup import in_gdb_thread
 from .state import set_thread
@@ -84,10 +84,13 @@ def _backtrace(thread_id, levels, startFrame, stack_format):
                 "column": 0,
                 "instructionPointerReference": hex(pc),
             }
-            line = current_frame.line()
+            line = export_line(current_frame.line())
             if line is not None:
                 newframe["line"] = line
                 if stack_format["line"]:
+                    # Unclear whether export_line should be called
+                    # here, but since it's just for users we pick the
+                    # gdb representation.
                     name += ", line " + str(line)
             objfile = gdb.current_progspace().objfile_for_address(pc)
             if objfile is not None:
index a2e27e54a6408487f8c32b358389fd21b3eac179..5389803c7445f0d4a80daa060dbc30700fd2210f 100644 (file)
@@ -15,7 +15,7 @@
 
 import gdb
 
-from .server import capability, request
+from .server import capability, export_line, request
 from .sources import make_source
 
 
@@ -53,7 +53,7 @@ class _BlockTracker:
         sal = gdb.find_pc_line(pc)
         if sal.symtab is not None:
             if sal.line != 0:
-                result["line"] = sal.line
+                result["line"] = export_line(sal.line)
             if sal.symtab.filename is not None:
                 # The spec says this can be omitted in some
                 # situations, but it's a little simpler to just always
index befb7bf25751eb09ca21c76a910a556414682ba5..1ef5a34b584dfb1f263f617ae9101e4bb827f36f 100644 (file)
@@ -16,7 +16,7 @@
 # This is deprecated in 3.9, but required in older versions.
 from typing import Optional
 
-from .server import capability, request
+from .server import capability, export_line, import_line, request
 from .sources import decode_source
 from .startup import exec_mi_and_log
 
@@ -31,12 +31,15 @@ from .startup import exec_mi_and_log
 @request("breakpointLocations", expect_stopped=False)
 @capability("supportsBreakpointLocationsRequest")
 def breakpoint_locations(*, source, line: int, endLine: Optional[int] = None, **extra):
+    line = import_line(line)
     if endLine is None:
         endLine = line
+    else:
+        endLine = import_line(endLine)
     filename = decode_source(source)
     lines = set()
     for entry in exec_mi_and_log("-symbol-list-lines", filename)["lines"]:
         this_line = entry["line"]
         if this_line >= line and this_line <= endLine:
-            lines.add(this_line)
+            lines.add(export_line(this_line))
     return {"breakpoints": [{"line": x} for x in sorted(lines)]}
index 2fb3f77f1e3293fa144812a2200e915db8eb5d4a..221ae35a0023522ad3d7304403232789a24d0fcf 100644 (file)
@@ -17,7 +17,7 @@ import gdb
 
 from .frames import frame_for_id
 from .globalvars import get_global_scope
-from .server import request
+from .server import export_line, request
 from .sources import make_source
 from .startup import in_gdb_thread
 from .varref import BaseReference
@@ -92,7 +92,7 @@ class _ScopeReference(BaseReference):
         result["namedVariables"] = self.child_count()
         frame = frame_for_id(self.frameId)
         if frame.line() is not None:
-            result["line"] = frame.line()
+            result["line"] = export_line(frame.line())
         filename = frame.filename()
         if filename is not None:
             result["source"] = make_source(filename)
index 68801efb9e04205b76121b619cf3a0236c8077d7..6f3af732286b3260d90366882313265e35552fcc 100644 (file)
@@ -46,6 +46,10 @@ _commands = {}
 # The global server.
 _server = None
 
+# This is set by the initialize request and is used when rewriting
+# line numbers.
+_lines_start_at_1 = False
+
 
 class DeferredRequest:
     """If a DAP request function returns a deferred request, no
@@ -571,15 +575,15 @@ def capability(name, value=True):
     return wrap
 
 
-def client_bool_capability(name):
+def client_bool_capability(name, default=False):
     """Return the value of a boolean client capability.
 
     If the capability was not specified, or did not have boolean type,
-    False is returned."""
+    DEFAULT is returned.  DEFAULT defaults to False."""
     global _server
     if name in _server.config and isinstance(_server.config[name], bool):
         return _server.config[name]
-    return False
+    return default
 
 
 @request("initialize", on_dap_thread=True)
@@ -587,6 +591,8 @@ def initialize(**args):
     global _server, _capabilities
     _server.config = args
     _server.send_event_later("initialized")
+    global _lines_start_at_1
+    _lines_start_at_1 = client_bool_capability("linesStartAt1", True)
     return _capabilities.copy()
 
 
@@ -690,3 +696,27 @@ def send_gdb_with_response(fn):
     if isinstance(val, (Exception, KeyboardInterrupt)):
         raise val
     return val
+
+
+def export_line(line):
+    """Rewrite LINE according to client capability.
+    This applies the linesStartAt1 capability as needed,
+    when sending a line number from gdb to the client."""
+    global _lines_start_at_1
+    if not _lines_start_at_1:
+        # In gdb, lines start at 1, so we only need to change this if
+        # the client starts at 0.
+        line = line - 1
+    return line
+
+
+def import_line(line):
+    """Rewrite LINE according to client capability.
+    This applies the linesStartAt1 capability as needed,
+    when the client sends a line number to gdb."""
+    global _lines_start_at_1
+    if not _lines_start_at_1:
+        # In gdb, lines start at 1, so we only need to change this if
+        # the client starts at 0.
+        line = line + 1
+    return line
diff --git a/gdb/testsuite/gdb.dap/line-zero.exp b/gdb/testsuite/gdb.dap/line-zero.exp
new file mode 100644 (file)
index 0000000..e6309dc
--- /dev/null
@@ -0,0 +1,60 @@
+# Copyright 2024 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/>.
+
+# Test breakpoints when lines start at zero.
+
+require allow_dap_tests
+
+load_lib dap-support.exp
+
+standard_testfile basic-dap.c
+
+if {[build_executable ${testfile}.exp $testfile $srcfile] == -1} {
+    return
+}
+
+if {[dap_initialize {linesStartAt1 [l false]}] == ""} {
+    return
+}
+
+set launch_id [dap_launch $testfile]
+
+# We told gdb that lines start at 0, so subtract one.
+set line [expr {[gdb_get_line_number "BREAK"] - 1}]
+set obj [dap_check_request_and_response "set breakpoint by line number" \
+            setBreakpoints \
+            [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \
+                 [list s $srcfile] $line]]
+set line_bpno [dap_get_breakpoint_number $obj]
+
+dap_check_request_and_response "configurationDone" configurationDone
+
+dap_check_response "launch response" launch $launch_id
+
+dap_wait_for_event_and_check "inferior started" thread "body reason" started
+
+dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
+    "body reason" breakpoint \
+    "body hitBreakpointIds" $line_bpno \
+    "body allThreadsStopped" true
+
+set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
+                   {o threadId [i 1]}] \
+           0]
+set stop_line [dict get [lindex [dict get $bt body stackFrames] 0] line]
+
+gdb_assert {$stop_line == $line} "stop line is 0-based"
+
+dap_shutdown
index 0e481ac083d073a5f1491d2d59ce1f323b601e4e..5c192e583e19ff2a2f00e1be7da83cee068d105f 100644 (file)
@@ -272,16 +272,19 @@ proc dap_check_request_and_response {name command {obj {}}} {
 # Start gdb, send a DAP initialization request and return the
 # response.  This approach lets the caller check the feature list, if
 # desired.  Returns the empty string on failure.  NAME is used as the
-# test name.
-proc dap_initialize {{name "initialize"}} {
+# test name.  EXTRA are other settings to pass via the "initialize"
+# request.
+proc dap_initialize {{name "initialize"} {extra ""}} {
     if {[dap_gdb_start]} {
        return ""
     }
     return [dap_check_request_and_response $name initialize \
-               {o clientID [s "gdb testsuite"] \
-                    supportsVariableType [l true] \
-                    supportsVariablePaging [l true] \
-                    supportsMemoryReferences [l true]}]
+               [format {o clientID [s "gdb testsuite"] \
+                            supportsVariableType [l true] \
+                            supportsVariablePaging [l true] \
+                            supportsMemoryReferences [l true] \
+                            %s} \
+                    $extra]]
 }
 
 # Send a launch request specifying FILE as the program to use for the