From patchwork Tue Aug 20 17:10:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 96182 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id AE2443870868 for ; Tue, 20 Aug 2024 17:13:56 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id A7847386D625 for ; Tue, 20 Aug 2024 17:11:00 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org A7847386D625 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org A7847386D625 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1724173865; cv=none; b=lZflL7tnAZ0UUWES98fWXWu37rbS2do6q/s1fQ7fjBN7gIxZ5jo47Gm1a4KqZ+zZdZlXHh35MvnxubDM+xV680CKGaVwvV44LEa8I2CisRDh/mfeBU5C8i4GqPXSbm5XE1astR+pehuPAoNvpmZcWtyOWhu5VTB82jm7FuMaB94= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1724173865; c=relaxed/simple; bh=IcIT9gmGOTwa1DZeKtUYNzoTmKxP9RUXAgKr9y0WBIM=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=M1gWEuESms/t+CBJHCqffBHi0BnnuA3XYypOg9SCgvylxqUY/XURxkdMLdIicbSRXnPd/0/0ONi+0XCKPRACuWew4zQ2nrTw1r7pG5Tg5KAAi2vWnrbXOd5s+flqsgAXxC2i/dcnWOZik17B2fBj6sTwC1djHhDlOzc9hVY7kFU= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1724173860; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=+1E2MKwOIfdJkRqWdav2uIeCLlCOTF8X/X5aHPFr0HY=; b=Fu2glApaxnhyKe4bNpYIvANEgC545rqLvl3lTnMCNoRZ0vHxzax9pARmfjEqatYDTkNw1q 5+/VfqOiFkwX7E00uhjjozBRPvh/VroJK6U/G+0bA74MFbjqWh1kX61hqrupH5/HvPQe+Y Ly2mVn+Uk67pGaw8Jz0H7eoH1SgHJkY= Received: from mail-wr1-f71.google.com (mail-wr1-f71.google.com [209.85.221.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-586-tCgoEI2iMQadu5-K44u3tA-1; Tue, 20 Aug 2024 13:10:59 -0400 X-MC-Unique: tCgoEI2iMQadu5-K44u3tA-1 Received: by mail-wr1-f71.google.com with SMTP id ffacd0b85a97d-3718d9d9267so2803514f8f.0 for ; Tue, 20 Aug 2024 10:10:59 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724173857; x=1724778657; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=+1E2MKwOIfdJkRqWdav2uIeCLlCOTF8X/X5aHPFr0HY=; b=AJKYxY0jBrcLKU8qaQ/gd8+VPKrLbfzBdy8pCo3WtOJ4op12N1Tp7m4g/kx7Amk/oD KqeU8YnxxJW+xO/9zvB7X7F+YvMaw6rvisJj9Z9n243YyraObQiNApX8ZVpUsPz6VTCH WCn7pIgsuGZv704omkC17ZiIZyNQeAs1z22vEPAkuAOAli5cdrEuGYuH+ahahMgSI3Rz sA9QwJlF64Wqdxr6UHl/DIoCwLNqTAVk0pXSQi4Z3lHdM9tsfL76WGF/t351Ze9KPNzL BgX88tj8wGw8AuvMa4nBltCCotXAbBlIyqb1Zcytgu59oxY4Rbh7DXrYY8kykkPZ1S0n RnKw== X-Gm-Message-State: AOJu0YynaN23oo50M3idi/ED9zO37LUfBVnWy4ejeTdgB52tPdDZo7dp kTUpbDoLlM+K3XizODRnvD6lBGRFY6+HfkBzkKsCUSbPxzVXv6iEmWRYND0JTH3GAmxoeaPzHNt qYrPrDdWBCzSlsXqRupE5gvDLtHT5UrASR2bObXEigB/LlV+JzjPqmCyYaw2SjDlfJNgaHMhGjS zWHapwkEMQJfFlDHrRq6H1EkIi1Yb+khU0VHzNDVz1z8c= X-Received: by 2002:adf:ec4b:0:b0:366:eade:bfbb with SMTP id ffacd0b85a97d-371946a48camr9016733f8f.46.1724173857126; Tue, 20 Aug 2024 10:10:57 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGzSLB5O2MI7eXs8GA8uU5VRhWlpAIdN/3SYNQrBhnzazhHKxAg8df/gR1AClPBY8opCLoeQw== X-Received: by 2002:adf:ec4b:0:b0:366:eade:bfbb with SMTP id ffacd0b85a97d-371946a48camr9016703f8f.46.1724173856197; Tue, 20 Aug 2024 10:10:56 -0700 (PDT) Received: from localhost (178.126.90.146.dyn.plus.net. [146.90.126.178]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-371898aa393sm13522551f8f.90.2024.08.20.10.10.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:10:55 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCHv5 07/14] gdb: apply escaping to filenames in 'complete' results Date: Tue, 20 Aug 2024 18:10:37 +0100 Message-Id: X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_BARRACUDACENTRAL, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org Building on the mechanism added in the previous commit(s), this commit applies escaping to filenames in the 'complete' command output. Consider a file: /tmp/xxx/aa"bb -- that is a filename that contains a double quote, currently the 'complete' command output looks like this: (gdb) complete file /tmp/xxx/a file /tmp/xxx/aa"bb Notice that the double quote in the output is not escaped. If we passed this same output back to GDB then the double quote will be treated as the start of a string. After this commit then the output looks like this: (gdb) complete file /tmp/xxx/a file /tmp/xxx/aa\"bb The double quote is now escaped. If we feed this output back to GDB then GDB will treat this as a single filename that contains a double quote, exactly what we want. To achieve this I've done a little refactoring, splitting out the core of gdb_completer_file_name_quote, and then added a new call from the filename_match_formatter function. There are updates to the tests to cover this new functionality. --- gdb/completer.c | 98 ++++++++++++++--- .../gdb.base/filename-completion.exp | 100 +++++++++++------- 2 files changed, 144 insertions(+), 54 deletions(-) diff --git a/gdb/completer.c b/gdb/completer.c index d9123e6e74a..7435532954f 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -320,25 +320,24 @@ gdb_completer_file_name_dequote (char *filename, int quote_char) return strdup (tmp.c_str ()); } -/* Apply character escaping to the file name in TEXT. QUOTE_PTR points to - the quote character surrounding TEXT, or points to the null-character if - there are no quotes around TEXT. MATCH_TYPE will be one of the readline - constants SINGLE_MATCH or MULTI_MATCH depending on if there is one or - many completions. */ +/* Apply character escaping to the filename in TEXT and return a newly + allocated buffer containing the possibly updated filename. + + QUOTE_CHAR is the quote character surrounding TEXT, or the + null-character if there are no quotes around TEXT. */ static char * -gdb_completer_file_name_quote (char *text, int match_type ATTRIBUTE_UNUSED, - char *quote_ptr) +gdb_completer_file_name_quote_1 (const char *text, char quote_char) { std::string str; - if (*quote_ptr == '\'') + if (quote_char == '\'') { /* There is no backslash escaping permitted within a single quoted string, so in this case we can just return the input sting. */ str = text; } - else if (*quote_ptr == '"') + else if (quote_char == '"') { /* Add escaping for a double quoted filename. */ for (const char *input = text; @@ -352,7 +351,7 @@ gdb_completer_file_name_quote (char *text, int match_type ATTRIBUTE_UNUSED, } else { - gdb_assert (*quote_ptr == '\0'); + gdb_assert (quote_char == '\0'); /* Add escaping for an unquoted filename. */ for (const char *input = text; @@ -369,6 +368,19 @@ gdb_completer_file_name_quote (char *text, int match_type ATTRIBUTE_UNUSED, return strdup (str.c_str ()); } +/* Apply character escaping to the filename in TEXT. QUOTE_PTR points to + the quote character surrounding TEXT, or points to the null-character if + there are no quotes around TEXT. MATCH_TYPE will be one of the readline + constants SINGLE_MATCH or MULTI_MATCH depending on if there is one or + many completions. */ + +static char * +gdb_completer_file_name_quote (char *text, int match_type ATTRIBUTE_UNUSED, + char *quote_ptr) +{ + return gdb_completer_file_name_quote_1 (text, *quote_ptr); +} + /* The function is used to update the completion word MATCH before displaying it to the user in the 'complete' command output. This function is only used for formatting filename or directory names. @@ -377,12 +389,28 @@ gdb_completer_file_name_quote (char *text, int match_type ATTRIBUTE_UNUSED, in which case a trailing "/" (forward-slash) is added, otherwise QUOTE_CHAR is added as a trailing quote. + When ADD_ESCAPES is true any special characters (e.g. whitespace, + quotes) will be escaped with a backslash. See + gdb_completer_file_name_quote_1 for full details on escaping. When + ADD_ESCAPES is false then no escaping will be added and MATCH (with the + correct trailing character) will be used unmodified. + Return the updated completion word as a string. */ static std::string -filename_match_formatter (const char *match, char quote_char) +filename_match_formatter_1 (const char *match, char quote_char, + bool add_escapes) { - std::string result (match); + std::string result; + if (add_escapes) + { + gdb::unique_xmalloc_ptr quoted_match + (gdb_completer_file_name_quote_1 (match, quote_char)); + result = quoted_match.get (); + } + else + result = match; + if (gdb_path_isdir (gdb_tilde_expand (match).c_str ())) result += "/"; else @@ -391,16 +419,52 @@ filename_match_formatter (const char *match, char quote_char) return result; } +/* The formatting function used to format the results of a 'complete' + command when the result is a filename, but the filename should not have + any escape characters added. Most commands that accept a filename don't + expect the filename to be quoted or to contain escape characters. + + See filename_match_formatter_1 for more argument details. */ + +static std::string +filename_unquoted_match_formatter (const char *match, char quote_char) +{ + return filename_match_formatter_1 (match, quote_char, false); +} + +/* The formatting function used to format the results of a 'complete' + command when the result is a filename, and the filename should have any + special character (e.g. whitespace, quotes) within it escaped with a + backslash. A limited number of commands accept this style of filename + argument. + + See filename_match_formatter_1 for more argument details. */ + +static std::string +filename_maybe_quoted_match_formatter (const char *match, char quote_char) +{ + return filename_match_formatter_1 (match, quote_char, true); +} + /* Generate filename completions of WORD, storing the completions into TRACKER. This is used for generating completions for commands that only accept unquoted filenames as well as for commands that accept - quoted and escaped filenames. */ + quoted and escaped filenames. + + When QUOTE_MATCHES is true TRACKER will be given a match formatter + function which will add escape characters (if needed) in the results. + When QUOTE_MATCHES is false the match formatter provided will not add + any escaping to the results. */ static void filename_completer_generate_completions (completion_tracker &tracker, - const char *word) + const char *word, + bool quote_matches) { - tracker.set_match_format_func (filename_match_formatter); + if (quote_matches) + tracker.set_match_format_func (filename_maybe_quoted_match_formatter); + else + tracker.set_match_format_func (filename_unquoted_match_formatter); int subsequent_name = 0; while (1) @@ -450,7 +514,7 @@ filename_maybe_quoted_completer (struct cmd_list_element *ignore, { filename_maybe_quoted_completer_handle_brkchars (ignore, tracker, text, word); - filename_completer_generate_completions (tracker, word); + filename_completer_generate_completions (tracker, word, true); } /* The brkchars callback used by commands that don't accept quoted @@ -481,7 +545,7 @@ deprecated_filename_completer { gdb_assert (tracker.use_custom_word_point ()); gdb_assert (word != nullptr); - filename_completer_generate_completions (tracker, word); + filename_completer_generate_completions (tracker, word, false); } /* Find the bounds of the current word for completion purposes, and diff --git a/gdb/testsuite/gdb.base/filename-completion.exp b/gdb/testsuite/gdb.base/filename-completion.exp index 39f616b45ba..c852b56f86a 100644 --- a/gdb/testsuite/gdb.base/filename-completion.exp +++ b/gdb/testsuite/gdb.base/filename-completion.exp @@ -82,10 +82,22 @@ proc test_gdb_complete_filename_multiple { $add_completed_line $completion_list $max_completions $testname } - if { $start_quote_char eq "" && $end_quote_char ne "" } { + if { $start_quote_char eq "" } { set updated_completion_list {} foreach entry $completion_list { + # If ENTRY is quoted with double quotes, then any double + # quotes within the entry need to be escaped. + if { $end_quote_char eq "\"" } { + regsub -all "\"" $entry "\\\"" entry + } + + if { $end_quote_char eq "" } { + regsub -all " " $entry "\\ " entry + regsub -all "\"" $entry "\\\"" entry + regsub -all "'" $entry "\\'" entry + } + if {[string range $entry end end] ne "/"} { set entry $entry$end_quote_char } @@ -146,47 +158,61 @@ proc run_quoting_and_escaping_tests { root } { } "" "${qc}" false \ "expand mixed directory and file names" - # GDB does not currently escape word break characters - # (e.g. white space) correctly in unquoted filenames. if { $qc ne "" } { set sp " " - - test_gdb_complete_filename_multiple "$cmd ${qc}${root}/aaa/" \ - "a" "a${sp}" { - "aa bb" - "aa cc" - } "" "${qc}" false \ - "expand filenames containing spaces" - - test_gdb_complete_filename_multiple "$cmd ${qc}${root}/bb1/" \ - "a" "a" { - "aa\"bb" - "aa'bb" - } "" "${qc}" false \ - "expand filenames containing quotes" } else { set sp "\\ " + } + + if { $qc eq "'" } { + set dq "\"" + set dq_re "\"" + } else { + set dq "\\\"" + set dq_re "\\\\\"" + } + + test_gdb_complete_filename_multiple "$cmd ${qc}${root}/bb2/" \ + "d" "ir${sp}" { + "dir 1/" + "dir 2/" + } "" "${qc}" false \ + "expand multiple directory names containing spaces" - test_gdb_complete_tab_multiple "$cmd ${qc}${root}/aaa/a" \ - "a${sp}" { - "aa bb" - "aa cc" - } false \ - "expand filenames containing spaces" - - test_gdb_complete_tab_multiple "$cmd ${qc}${root}/bb1/a" \ - "a" { - "aa\"bb" - "aa'bb" - } false \ - "expand filenames containing quotes" - - test_gdb_complete_tab_unique "$cmd ${qc}${root}/bb1/aa\\\"" \ - "$cmd ${qc}${root}/bb1/aa\\\\\"bb${qc}" " " \ - "expand unique filename containing double quotes" - - test_gdb_complete_tab_unique "$cmd ${qc}${root}/bb1/aa\\'" \ - "$cmd ${qc}${root}/bb1/aa\\\\'bb${qc}" " " \ + test_gdb_complete_filename_multiple "$cmd ${qc}${root}/aaa/" \ + "a" "a${sp}" { + "aa bb" + "aa cc" + } "" "${qc}" false \ + "expand filenames containing spaces" + + test_gdb_complete_filename_multiple "$cmd ${qc}${root}/bb1/" \ + "a" "a" { + "aa\"bb" + "aa'bb" + } "" "${qc}" false \ + "expand filenames containing quotes" + + test_gdb_complete_tab_unique "$cmd ${qc}${root}/bb1/aa${dq}" \ + "$cmd ${qc}${root}/bb1/aa${dq_re}bb${qc}" " " \ + "expand unique filename containing double quotes" + + # It is not possible to include a single quote character + # within a single quoted string. However, GDB does not do + # anything smart if a user tries to do this. Avoid testing + # this case. Maybe in the future we'll figure a way to avoid + # this situation. + if { $qc ne "'" } { + if { $qc eq "" } { + set sq "\\'" + set sq_re "\\\\'" + } else { + set sq "'" + set sq_re "'" + } + + test_gdb_complete_tab_unique "$cmd ${qc}${root}/bb1/aa${sq}" \ + "$cmd ${qc}${root}/bb1/aa${sq_re}bb${qc}" " " \ "expand unique filename containing single quote" } }