]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: add filename option support
authorAndrew Burgess <aburgess@redhat.com>
Wed, 11 Sep 2024 14:25:32 +0000 (15:25 +0100)
committerAndrew Burgess <aburgess@redhat.com>
Mon, 4 Nov 2024 15:56:01 +0000 (15:56 +0000)
This commit adds support for filename options to GDB's option
sub-system (see cli/cli-option.{c,h}).

The new filename options support quoted and escaped filenames, and tab
completion is fully supported.

This commit adds the new option, and adds these options to the
'maintenance test-options' command as '-filename', along with some
tests that exercise this new option.

I've split the -filename testing into two.  In gdb.base/options.exp we
use the -filename option with some arbitrary strings.  This tests that
GDB can correctly extract the value from a filename option, and that
GDB can complete other options after a filename option.  However,
these tests don't actually pass real filenames, nor do they test
filename completion.

In gdb.base/filename-completion.exp I have added some tests that test
the -filename option with real filenames, and exercise filename tab
completion.

This commit doesn't include any real uses of the new filename options,
that will come in the next commit.

gdb/cli/cli-option.c
gdb/cli/cli-option.h
gdb/maint-test-options.c
gdb/testsuite/gdb.base/filename-completion.exp
gdb/testsuite/gdb.base/options.exp

index 05539285c80dc40a078ec99244006a2f6222db38..9eb9ff81154569ce5cd52f598d07ea85fc9cb528 100644 (file)
@@ -43,7 +43,7 @@ union option_value
   /* For var_enum options.  */
   const char *enumeration;
 
-  /* For var_string options.  This is malloc-allocated.  */
+  /* For var_string and var_filename options.  This is allocated with new.  */
   std::string *string;
 };
 
@@ -85,7 +85,7 @@ struct option_def_and_value
   {
     if (value.has_value ())
       {
-       if (option.type == var_string)
+       if (option.type == var_string || option.type == var_filename)
          delete value->string;
       }
   }
@@ -102,7 +102,7 @@ private:
   {
     if (value.has_value ())
       {
-       if (option.type == var_string)
+       if (option.type == var_string || option.type == var_filename)
          value->string = nullptr;
       }
   }
@@ -452,6 +452,78 @@ parse_option (gdb::array_view<const option_def_group> options_group,
        return option_def_and_value {*match, match_ctx, val};
       }
 
+    case var_filename:
+      {
+       if (check_for_argument (args, "--"))
+         {
+           /* Treat e.g., "maint test-options -filename --" as if there
+              was no argument after "-filename".  */
+           error (_("-%s requires an argument"), match->name);
+         }
+
+       const char *arg_start = *args;
+       std::string str = extract_string_maybe_quoted (args);
+
+       /* If we are performing completion, and extracting STR moved ARGS
+          to the end of the line, then the user is trying to complete the
+          filename value.
+
+          If ARGS didn't make it to the end of the line then the filename
+          value is already complete and the user is trying to complete
+          something later on the line.  */
+       if (completion != nullptr && **args == '\0')
+         {
+           /* Preserve the current custom word point.  If the call to
+              advance_to_filename_maybe_quoted_complete_word_point below
+              skips to the end of the command line then the custom word
+              point will have been updated even though we generate no
+              completions.
+
+              However, *ARGS will also have been updated, and the general
+              option completion code (which we will return too) also
+              updates the custom word point based on the adjustment made
+              to *ARGS.
+
+              And so, if we don't find any completions, we should restore
+              the custom word point value, this leaves the generic option
+              completion code free to make its own adjustments.  */
+           int prev_word_pt = completion->tracker.custom_word_point ();
+
+           /* From ARG_START move forward to the start of the completion
+              word, this will skip over any opening quote if there is
+              one.
+
+              If the word to complete is fully quoted, i.e. has an
+              opening and closing quote, then this will skip over the
+              word entirely and leave WORD pointing to the end of the
+              input string.  */
+           const char *word
+             = advance_to_filename_maybe_quoted_complete_word_point
+             (completion->tracker, arg_start);
+
+           if (word == arg_start || *word != '\0')
+             {
+               filename_maybe_quoted_completer (nullptr, completion->tracker,
+                                                arg_start, word);
+
+               if (completion->tracker.have_completions ())
+                 return {};
+             }
+
+           /* No completions.  Restore the custom word point.  See the
+              comment above for why this is needed.  */
+           completion->tracker.set_custom_word_point (prev_word_pt);
+         }
+
+       /* Check we did manage to extract something.  */
+       if (*args == arg_start)
+         error (_("-%s requires an argument"), match->name);
+
+       option_value val;
+       val.string = new std::string (std::move (str));
+       return option_def_and_value {*match, match_ctx, val};
+      }
+
     default:
       /* Not yet.  */
       gdb_assert_not_reached ("option type not supported");
@@ -612,6 +684,7 @@ save_option_value_in_ctx (std::optional<option_def_and_value> &ov)
        = ov->value->enumeration;
       break;
     case var_string:
+    case var_filename:
       *ov->option.var_address.string (ov->option, ov->ctx)
        = std::move (*ov->value->string);
       break;
@@ -701,6 +774,8 @@ get_val_type_str (const option_def &opt, std::string &buffer)
       }
     case var_string:
       return "STRING";
+    case var_filename:
+      return "FILENAME";
     default:
       return nullptr;
     }
@@ -856,6 +931,15 @@ add_setshow_cmds_for_options (command_class cmd_class,
                                  nullptr, option.show_cmd_cb,
                                  set_list, show_list);
        }
+      else if (option.type == var_filename)
+       {
+         add_setshow_filename_cmd (option.name, cmd_class,
+                                   option.var_address.string (option, data),
+                                   option.set_doc, option.show_doc,
+                                   option.help_doc,
+                                   nullptr, option.show_cmd_cb,
+                                   set_list, show_list);
+       }
       else
        gdb_assert_not_reached ("option type not handled");
     }
index bbe281d9721e2928eb272e2233fa13e2714732c9..26307a5d1e9696e4e44b7608d4e25bf6fa4ce02a 100644 (file)
@@ -308,6 +308,26 @@ struct string_option_def : option_def
   }
 };
 
+/* A var_filename command line option.  */
+
+template<typename Context>
+struct filename_option_def : option_def
+{
+  filename_option_def (const char *long_option_,
+                      std::string *(*get_var_address_cb_) (Context *),
+                      show_value_ftype *show_cmd_cb_,
+                      const char *set_doc_,
+                      const char *show_doc_ = nullptr,
+                      const char *help_doc_ = nullptr)
+    : option_def (long_option_, var_filename, nullptr,
+                 (erased_get_var_address_ftype *) get_var_address_cb_,
+                 show_cmd_cb_,
+                 set_doc_, show_doc_, help_doc_)
+  {
+    var_address.string = detail::get_var_address<std::string, Context>;
+  }
+};
+
 /* A group of options that all share the same context pointer to pass
    to the options' get-current-value callbacks.  */
 struct option_def_group
index 48b68f910844035a424aa7c63954b6926a70db5b..9d7681777984e886589352b4a9da215c7193abda 100644 (file)
    readline, for proper testing of TAB completion.
 
    These maintenance commands support options of all the different
-   available kinds of commands (boolean, enum, flag, string, uinteger):
+   available kinds of commands (boolean, enum, flag, string, filename,
+   uinteger):
 
     (gdb) maint test-options require-delimiter -[TAB]
-    -bool                -pinteger-unlimited  -xx1
-    -enum                -string              -xx2
-    -flag                -uinteger-unlimited
+    -bool                -flag                      -uinteger-unlimited
+    -enum                -pinteger-unlimited        -xx1
+    -filename            -string                    -xx2
 
     (gdb) maint test-options require-delimiter -bool o[TAB]
     off  on
   Invoking the commands makes them print out the options parsed:
 
    (gdb) maint test-options unknown-is-error -flag -enum yyy cmdarg
-   -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint-unl 0 -pint-unl 0 -string '' -- cmdarg
+   -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint-unl 0 -pint-unl 0 -string '' -filename '' -- cmdarg
 
    (gdb) maint test-options require-delimiter -flag -enum yyy cmdarg
-   -flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0 -string '' -- -flag -enum yyy cmdarg
+   -flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0 -string '' -filename '' -- -flag -enum yyy cmdarg
    (gdb) maint test-options require-delimiter -flag -enum yyy cmdarg --
    Unrecognized option at: cmdarg --
    (gdb) maint test-options require-delimiter -flag -enum yyy -- cmdarg
-   -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint-unl 0 -pint-unl 0 -string '' -- cmdarg
+   -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint-unl 0 -pint-unl 0 -string '' -filename '' -- cmdarg
 
   The "maint show test-options-completion-result" command exists in
   order to do something similar for completion:
@@ -135,6 +136,7 @@ struct test_options_opts
   unsigned int uint_unl_opt = 0;
   int pint_unl_opt = 0;
   std::string string_opt;
+  std::string filename_opt;
 
   test_options_opts () = default;
 
@@ -146,7 +148,8 @@ struct test_options_opts
   {
     gdb_printf (file,
                _("-flag %d -xx1 %d -xx2 %d -bool %d "
-                 "-enum %s -uint-unl %s -pint-unl %s -string '%s' -- %s\n"),
+                 "-enum %s -uint-unl %s -pint-unl %s -string '%s' "
+                 "-filename '%s' -- %s\n"),
                flag_opt,
                xx1_opt,
                xx2_opt,
@@ -159,6 +162,7 @@ struct test_options_opts
                 ? "unlimited"
                 : plongest (pint_unl_opt)),
                string_opt.c_str (),
+               filename_opt.c_str (),
                args);
   }
 };
@@ -233,6 +237,14 @@ static const gdb::option::option_def test_options_option_defs[] = {
     nullptr, /* show_cmd_cb */
     N_("A string option."),
   },
+
+  /* A filename option.  */
+  gdb::option::filename_option_def<test_options_opts> {
+    "filename",
+    [] (test_options_opts *opts) { return &opts->filename_opt; },
+    nullptr, /* show_cmd_cb */
+    N_("A filename option."),
+  },
 };
 
 /* Create an option_def_group for the test_options_opts options, with
index 389e2d736c53cfa87a752b59e434668773a847a3..6de312bc6a347fba959e36e8e8325b44667e6303 100644 (file)
@@ -412,6 +412,13 @@ proc run_quoting_and_escaping_tests { root } {
 
        run_mid_line_completion_tests $root $cmd
     }
+
+    foreach sub_cmd { require-delimiter unknown-is-error unknown-is-operand } {
+       set cmd "maintenance test-options $sub_cmd -filename"
+       with_test_prefix "cmd=$cmd" {
+           run_quoting_and_escaping_tests_1 $root $cmd
+       }
+    }
 }
 
 # Helper for run_unquoted_tests.  ROOT is the root directory as setup
index 841e603764c8c8f2e2bcad3d67118ff9fbc21247..e1ad61e647028b81763311e8ac97dd79ae5e45be 100644 (file)
@@ -99,21 +99,21 @@ proc make_cmd {variant} {
 # operand.
 proc expect_none {operand} {
     return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0\
-           -string '' -- $operand"
+           -string '' -filename '' -- $operand"
 }
 
 # Return a string for the expected result of running "maint
 # test-options xxx", with -flag set.  OPERAND is the expected operand.
 proc expect_flag {operand} {
     return "-flag 1 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0\
-           -string '' -- $operand"
+           -string '' -filename '' -- $operand"
 }
 
 # Return a string for the expected result of running "maint
 # test-options xxx", with -bool set.  OPERAND is the expected operand.
 proc expect_bool {operand} {
     return "-flag 0 -xx1 0 -xx2 0 -bool 1 -enum xxx -uint-unl 0 -pint-unl 0\
-           -string '' -- $operand"
+           -string '' -filename '' -- $operand"
 }
 
 # Return a string for the expected result of running "maint
@@ -123,10 +123,10 @@ proc expect_bool {operand} {
 proc expect_integer {option val operand} {
     if {$option == "uinteger-unlimited"} {
        return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl $val\
-               -pint-unl 0 -string '' -- $operand"
+               -pint-unl 0 -string '' -filename '' -- $operand"
     } elseif {$option == "pinteger-unlimited"} {
        return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0\
-               -pint-unl $val -string '' -- $operand"
+               -pint-unl $val -string '' -filename '' -- $operand"
     } else {
        error "unsupported option: $option"
     }
@@ -144,12 +144,28 @@ proc expect_string {str operand} {
        set str [string range $str 1 end-1]
     }
     return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0\
-           -string '$str' -- $operand"
+           -string '$str' -filename '' -- $operand"
+}
+
+# Return a string for the expected result of running "maint
+# test-options xxx", with -filename set to $STR.  OPERAND is the
+# expected operand.
+proc expect_filename {str operand} {
+    # Dequote the string in the expected output.
+    if { ( [string range $str 0 0] == "\""
+          && [string range $str end end] == "\"")
+        || ([string range $str 0 0] == "'"
+            && [string range $str end end] == "'")} {
+       set str [string range $str 1 end-1]
+    }
+    return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0\
+           -string '' -filename '$str' -- $operand"
 }
 
 set all_options {
     "-bool"
     "-enum"
+    "-filename"
     "-flag"
     "-pinteger-unlimited"
     "-string"
@@ -612,7 +628,7 @@ proc_with_prefix test-flag {variant} {
     # Extract twice the same flag, separated by one space.
     gdb_test "$cmd -xx1     -xx2 -xx1  -xx2 -xx1    -- non flags args" \
        "-flag 0 -xx1 1 -xx2 1 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0\
-        -string '' -- non flags args"
+        -string '' -filename '' -- non flags args"
 
     # Extract 2 known flags in front of unknown flags.
     gdb_test "$cmd -xx1 -xx2 -a -b -c -xx1 --" \
@@ -1031,6 +1047,70 @@ proc_with_prefix test-string {variant} {
     }
 }
 
+# Filename option tests.  These tests only focus on how GDB parses the
+# filename option, and ensures that GDB can complete things after the
+# filename value.  The actual strings passed as filenames in this proc
+# are not actual files that exist on disk.
+#
+# Filename options do also support completion.  For testing of this
+# aspect see the gdb.base/filename-completion.exp script.
+proc_with_prefix test-filename {variant} {
+    global all_options
+
+    set cmd [make_cmd $variant]
+
+    # Check that "-" where a value is expected does not show the
+    # command's options.  I.e., a filename's value is not optional.
+    # Check both completion and running the command.
+    res_test_gdb_complete_none \
+       "1 [expect_none ""]" \
+       "$cmd -filename -"
+    gdb_test "$cmd -filename --" \
+       "-filename requires an argument"
+    if {$variant == "require-delimiter"} {
+       gdb_test "$cmd -filename" [expect_none "-filename"]
+    } else {
+       gdb_test "$cmd -filename" \
+           "-filename requires an argument"
+    }
+
+    foreach_with_prefix str {
+       "STR"
+       "\"STR\""
+       "\\\"STR"
+       "'STR'"
+       "\\'STR"
+       "\"STR AAA\""
+       "'STR BBB'"
+       "\"STR 'CCC' DDD\""
+       "'STR \"EEE\" FFF'"
+       "\"STR \\\"GGG\\\" HHH\""
+       "'STR \\\'III\\\' JJJ'"
+    } {
+       res_test_gdb_complete_none \
+           "1 [expect_none ""]" \
+           "$cmd -filename ${str}"
+       gdb_test "$cmd -filename ${str} --" [expect_filename "${str}" ""]
+
+       # Completing at "-" after parsing STR should list all options.
+       res_test_gdb_complete_multiple \
+           "1 [expect_filename "${str}" "-"]" \
+           "$cmd -filename ${str} " "-" "" $all_options
+
+       # Check that only $STR is considered part of the filename's value.
+       # I.e., that we stop parsing the filename at the first
+       # whitespace or after the closing quote of $STR.
+       if {$variant == "require-delimiter"} {
+           res_test_gdb_complete_none \
+               "1 [expect_filename "${str}" "BAR"]" \
+               "$cmd -filename ${str} BAR"
+       } else {
+           res_test_gdb_complete_none "0 BAR" "$cmd -filename ${str} BAR"
+       }
+       gdb_test "$cmd -filename ${str} BAR --" "Unrecognized option at: BAR --"
+    }
+}
+
 # Run the options framework tests first.
 foreach_with_prefix cmd {
     "require-delimiter"
@@ -1045,6 +1125,7 @@ foreach_with_prefix cmd {
     }
     test-enum $cmd
     test-string $cmd
+    test-filename $cmd
 }
 
 # Run the print integration tests, both as "standalone", and under