Handle array- and string-like values in no-op pretty printers
authorTom Tromey <tromey@adacore.com>
Fri, 21 Jul 2023 15:34:21 +0000 (09:34 -0600)
committerTom Tromey <tromey@adacore.com>
Tue, 5 Sep 2023 17:10:24 +0000 (11:10 -0600)
This changes the no-op pretty printers -- used by DAP -- to handle
array- and string-like objects known by the gdb core.  Two new tests
are added, one for Ada and one for Rust.

gdb/python/lib/gdb/printing.py
gdb/testsuite/gdb.dap/ada-arrays.exp [new file with mode: 0644]
gdb/testsuite/gdb.dap/ada-arrays/cstuff.c [new file with mode: 0644]
gdb/testsuite/gdb.dap/ada-arrays/main.adb [new file with mode: 0644]
gdb/testsuite/gdb.dap/ada-arrays/pck.adb [new file with mode: 0644]
gdb/testsuite/gdb.dap/ada-arrays/pck.ads [new file with mode: 0644]
gdb/testsuite/gdb.dap/rust-slices.exp [new file with mode: 0644]
gdb/testsuite/gdb.dap/rust-slices.rs [new file with mode: 0644]

index a668bd0e3fc9e158ce117eb9d739f69ca39b8009..1a761a6aa57e11048888b9adcff41ab1d65ac954 100644 (file)
@@ -18,6 +18,7 @@
 
 import gdb
 import gdb.types
+import itertools
 import re
 
 
@@ -285,11 +286,24 @@ class NoOpArrayPrinter:
     def __init__(self, ty, value):
         self.value = value
         (low, high) = ty.range()
-        self.low = low
-        self.high = high
+        # In Ada, an array can have an index type that is a
+        # non-contiguous enum.  In this case the indexing must be done
+        # by using the indices into the enum type, not the underlying
+        # integer values.
+        range_type = ty.fields()[0].type
+        if range_type.target().code == gdb.TYPE_CODE_ENUM:
+            e_values = range_type.target().fields()
+            # Drop any values before LOW.
+            e_values = itertools.dropwhile(lambda x: x.enumval < low, e_values)
+            # Drop any values after HIGH.
+            e_values = itertools.takewhile(lambda x: x.enumval <= high, e_values)
+            low = 0
+            high = len(list(e_values)) - 1
         # This is a convenience to the DAP code and perhaps other
         # users.
         self.num_children = high - low + 1
+        self.low = low
+        self.high = high
 
     def to_string(self):
         return ""
@@ -330,7 +344,13 @@ def make_visualizer(value):
         pass
     else:
         ty = value.type.strip_typedefs()
-        if ty.code == gdb.TYPE_CODE_ARRAY:
+        if ty.is_string_like:
+            result = gdb.printing.NoOpScalarPrinter(value)
+        elif ty.code == gdb.TYPE_CODE_ARRAY:
+            result = gdb.printing.NoOpArrayPrinter(ty, value)
+        elif ty.is_array_like:
+            value = value.to_array()
+            ty = value.type.strip_typedefs()
             result = gdb.printing.NoOpArrayPrinter(ty, value)
         elif ty.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION):
             result = gdb.printing.NoOpStructPrinter(ty, value)
diff --git a/gdb/testsuite/gdb.dap/ada-arrays.exp b/gdb/testsuite/gdb.dap/ada-arrays.exp
new file mode 100644 (file)
index 0000000..13b5425
--- /dev/null
@@ -0,0 +1,123 @@
+# Copyright 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/>.
+
+load_lib "ada.exp"
+load_lib dap-support.exp
+
+require allow_dap_tests
+require allow_ada_tests
+
+standard_ada_testfile main
+set cfile "cstuff"
+set csrcfile ${srcdir}/${subdir}/${testdir}/${cfile}.c
+set cobject [standard_output_file ${cfile}.o]
+
+gdb_compile "${csrcfile}" "${cobject}" object [list debug]
+if {[gdb_compile_ada "${srcfile}" "${binfile}" executable debug] != ""} {
+  return -1
+}
+
+if {[dap_launch $testfile] == ""} {
+    return
+}
+
+# Stop in a C frame, but examine values in an Ada frame, to make sure
+# cross-language scenarios work correctly.
+set line [gdb_get_line_number "STOP" $testdir/cstuff.c]
+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 cstuff.c] $line]]
+set line_bpno [dap_get_breakpoint_number $obj]
+
+dap_check_request_and_response "start inferior" configurationDone
+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
+
+set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
+                   {o threadId [i 1]}] \
+           0]
+# The Ada frame is frame 1.
+set frame_id [dict get [lindex [dict get $bt body stackFrames] 1] id]
+
+set scopes [dap_check_request_and_response "get scopes" scopes \
+               [format {o frameId [i %d]} $frame_id]]
+set scopes [dict get [lindex $scopes 0] body scopes]
+
+gdb_assert {[llength $scopes] == 2} "two scopes"
+
+lassign $scopes scope ignore
+gdb_assert {[dict get $scope name] == "Arguments"} "scope is arguments"
+gdb_assert {[dict get $scope presentationHint] == "arguments"} \
+    "arguments presentation hint"
+gdb_assert {[dict get $scope namedVariables] == 3} "three vars in scope"
+
+set num [dict get $scope variablesReference]
+set refs [lindex [dap_check_request_and_response "fetch arguments" \
+                     "variables" \
+                     [format {o variablesReference [i %d]} $num]] \
+             0]
+
+# Helper to check the contents of a single array-like object.  VAR is
+# the variable entry.  NAME is the name of the variable, pulled out
+# for convenience.# ARGS are the expected child values.
+proc check_array_contents {var name args} {
+    set len [llength $args]
+    gdb_assert {[dict get $var indexedVariables] == $len} \
+       "check length of $name variable"
+
+    set num [dict get $var variablesReference]
+    set refs [lindex [dap_check_request_and_response \
+                         "fetch contents of $name" \
+                         "variables" \
+                         [format {o variablesReference [i %d]} $num]] \
+                 0]
+
+    foreach subvar [dict get $refs body variables] subvalue $args {
+       set subname [dict get $subvar name]
+       gdb_assert {[dict get $subvar value] == $subvalue} \
+           "check value of $name entry $subname"
+    }
+}
+
+foreach var [dict get $refs body variables] {
+    set name [dict get $var name]
+    switch $name {
+       "the_buffer" {
+           check_array_contents $var $name 1 2 3 4
+       }
+
+       "the_ar" {
+           check_array_contents $var $name 5 6 7 8 9
+       }
+
+       "hello" {
+           # Note that the expected value looks strange here -- there
+           # are too many backslashes.  This is a TON issue, as the
+           # JSON looks ok: "value": "\"hello\"".
+           gdb_assert {[dict get $var value] == "\\\"hello\\\""} \
+               "value of hello variable"
+       }
+
+       default {
+           fail "unknown variable $name"
+       }
+    }
+}
+
+dap_shutdown
diff --git a/gdb/testsuite/gdb.dap/ada-arrays/cstuff.c b/gdb/testsuite/gdb.dap/ada-arrays/cstuff.c
new file mode 100644 (file)
index 0000000..af87082
--- /dev/null
@@ -0,0 +1,22 @@
+/* Copyright 2023 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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/>.  */
+
+void
+c_procedure (int x)
+{
+  /* STOP */
+}
diff --git a/gdb/testsuite/gdb.dap/ada-arrays/main.adb b/gdb/testsuite/gdb.dap/ada-arrays/main.adb
new file mode 100644 (file)
index 0000000..c9e98c4
--- /dev/null
@@ -0,0 +1,24 @@
+--  Copyright 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/>.
+
+with Pck; use Pck;
+
+procedure Main is
+   Value : Buffer (1 .. 4) := (1, 2, 3, 4);
+   Another_Value : AR := (5, 6, 7, 8, 9);
+   Hello: String := "hello";
+begin
+   Do_Nothing (Value, Another_Value, Hello);
+end Main;
diff --git a/gdb/testsuite/gdb.dap/ada-arrays/pck.adb b/gdb/testsuite/gdb.dap/ada-arrays/pck.adb
new file mode 100644 (file)
index 0000000..7efa893
--- /dev/null
@@ -0,0 +1,21 @@
+--  Copyright 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/>.
+
+package body Pck is
+   procedure Do_Nothing (The_Buffer : in out Buffer; The_AR : in out AR; Hello: in out String) is
+   begin
+      C_Procedure (23);
+   end Do_Nothing;
+end Pck;
diff --git a/gdb/testsuite/gdb.dap/ada-arrays/pck.ads b/gdb/testsuite/gdb.dap/ada-arrays/pck.ads
new file mode 100644 (file)
index 0000000..475bb7b
--- /dev/null
@@ -0,0 +1,51 @@
+--  Copyright 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/>.
+
+package Pck is
+   pragma Linker_Options ("cstuff.o");
+
+   procedure C_Procedure (Value : Integer);
+   pragma Import(C, C_Procedure, "c_procedure");
+
+   type Small is new Integer range 0 .. 2 ** 6 - 1;
+
+   type Buffer is array (Integer range <>) of Small;
+   pragma Pack (Buffer);
+
+   type Enum_With_Gaps is
+     (
+      LIT0,
+      LIT1,
+      LIT2,
+      LIT3,
+      LIT4
+     );
+
+   for Enum_With_Gaps use
+     (
+      LIT0 => 3,
+      LIT1 => 5,
+      LIT2 => 8,
+      LIT3 => 13,
+      LIT4 => 21
+     );
+   for Enum_With_Gaps'size use 16;
+
+   type Enum_Subrange is new Enum_With_Gaps range Lit1 .. Lit3;
+
+   type AR is array (Enum_With_Gaps range <>) of Integer;
+
+   procedure Do_Nothing (The_Buffer : in out Buffer; The_AR : in out AR; Hello: in out String);
+end Pck;
diff --git a/gdb/testsuite/gdb.dap/rust-slices.exp b/gdb/testsuite/gdb.dap/rust-slices.exp
new file mode 100644 (file)
index 0000000..96ed5da
--- /dev/null
@@ -0,0 +1,119 @@
+# Copyright 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/>.
+
+# Test "scopes" and "variables".
+
+load_lib rust-support.exp
+load_lib dap-support.exp
+
+require allow_dap_tests
+require allow_rust_tests
+require {can_compile rust}
+
+standard_testfile .rs
+
+if {[build_executable ${testfile}.exp $testfile $srcfile {debug rust}] == -1} {
+    return
+}
+
+if {[dap_launch $testfile] == ""} {
+    return
+}
+
+set line [gdb_get_line_number "STOP"]
+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 "start inferior" configurationDone
+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
+
+set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
+                   {o threadId [i 1]}] \
+           0]
+set frame_id [dict get [lindex [dict get $bt body stackFrames] 0] id]
+
+set scopes [dap_check_request_and_response "get scopes" scopes \
+               [format {o frameId [i %d]} $frame_id]]
+set scopes [dict get [lindex $scopes 0] body scopes]
+
+gdb_assert {[llength $scopes] == 2} "two scopes"
+
+lassign $scopes scope ignore
+gdb_assert {[dict get $scope name] == "Locals"} "scope is locals"
+gdb_assert {[dict get $scope presentationHint] == "locals"} \
+    "locals presentation hint"
+gdb_assert {[dict get $scope namedVariables] == 3} "three vars in scope"
+
+set num [dict get $scope variablesReference]
+set refs [lindex [dap_check_request_and_response "fetch variables" \
+                     "variables" \
+                     [format {o variablesReference [i %d]} $num]] \
+             0]
+
+# Helper to check the contents of a single array-like object.  VAR is
+# the variable entry.  NAME is the name of the variable, pulled out
+# for convenience.  ARGS are the expected child values.
+proc check_array_contents {var name args} {
+    set len [llength $args]
+    gdb_assert {[dict get $var indexedVariables] == $len} \
+       "check length of $name variable"
+
+    set num [dict get $var variablesReference]
+    set refs [lindex [dap_check_request_and_response \
+                         "fetch contents of $name" \
+                         "variables" \
+                         [format {o variablesReference [i %d]} $num]] \
+                 0]
+
+    foreach subvar [dict get $refs body variables] subvalue $args {
+       set subname [dict get $subvar name]
+       gdb_assert {[dict get $subvar value] == $subvalue} \
+           "check value of $name entry $subname"
+    }
+}
+
+foreach var [dict get $refs body variables] {
+    set name [dict get $var name]
+    switch $name {
+       "array" {
+           check_array_contents $var $name 1 2 3 4
+       }
+
+       "slice" {
+           check_array_contents $var $name 2
+       }
+
+       "hello" {
+           # Note that the expected value looks strange here -- there
+           # are too many backslashes.  This is a TON issue, as the
+           # JSON looks ok: "value": "\"hello\"".
+           gdb_assert {[dict get $var value] == "\\\"hello\\\""} \
+               "value of hello variable"
+       }
+
+       default {
+           fail "unknown variable $name"
+       }
+    }
+}
+
+dap_shutdown
diff --git a/gdb/testsuite/gdb.dap/rust-slices.rs b/gdb/testsuite/gdb.dap/rust-slices.rs
new file mode 100644 (file)
index 0000000..0f06a75
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (C) 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/>.
+
+#![allow(dead_code)]
+#![allow(unused_variables)]
+#![allow(unused_assignments)]
+
+fn empty () {
+}
+
+fn main () {
+    let array = [1,2,3,4];
+    let slice = &array[1..2];
+    let hello = "hello";
+
+    empty();                   // STOP
+}