From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id 6N1RJBqwhmZUPhsAWB0awg (envelope-from ) for ; Thu, 04 Jul 2024 10:22:18 -0400 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Y9/D/O6L; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 916A41E0D0; Thu, 4 Jul 2024 10:22:18 -0400 (EDT) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 7EEAB1E030 for ; Thu, 4 Jul 2024 10:22:16 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 22E183861022 for ; Thu, 4 Jul 2024 14:22:16 +0000 (GMT) Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id 3BA4B384A4B5 for ; Thu, 4 Jul 2024 14:21:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 3BA4B384A4B5 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 3BA4B384A4B5 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1720102884; cv=none; b=XyedIHCOSRJCTa/ujzzPKCdMlledhXsSqk2vqJkSWrWpGP1VwhMGa0DQkfoyjDUsRVac/mfMGs7MeVmL5sai3vkRZuFVcbAI3r7wMjV03Bxjt2emUXptsKOVxhwU1V8fE81E7QDb+NGUR9nMRH6ollIr7bltrpis2FKodwaK95E= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1720102884; c=relaxed/simple; bh=nBP3BqVxycwd99sTcr/YhC2/TCFUpkF1fXPdPCpsnxE=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=al1KnvrUYDKnDYDPmqBWi7r3dl3leBwFcC374iVU7vezicaOZgPNE8Pps3GI0BTGElPQxsjFyBKFhmI+lgea5AsYm5pqhJv3CzMY470oXk6DScjIRqwGjARVOr9gwcRHRnar0B0ivLmANsfIcYzXffi45Z5tY9jbckWjBrLrALs= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1720102881; 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=w1fvxID55ninqXnnaCFqFKTgVkCiaeCuMYoHm/SozbQ=; b=Y9/D/O6LiZc9TW6G+OPIXClqUlxParnMxR0ke88Lf6qEOQcmgROe1Ey5v3e22qrQSzMaNl mxrKz26ZRNkpV0aOP6DlxzjOT0aR+Xn5P3xM4e/XmprGycpO/wBzEaXDtYK6g5pPnKSJtR KOC+bYQ5ghkKTIlFTbof02ErrzaZ3Sw= Received: from mail-lj1-f197.google.com (mail-lj1-f197.google.com [209.85.208.197]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-91-Jtaymgp3OamphnKvoRnfFA-1; Thu, 04 Jul 2024 10:21:20 -0400 X-MC-Unique: Jtaymgp3OamphnKvoRnfFA-1 Received: by mail-lj1-f197.google.com with SMTP id 38308e7fff4ca-2ec4df4e2e8so8170811fa.0 for ; Thu, 04 Jul 2024 07:21:20 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720102878; x=1720707678; 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=w1fvxID55ninqXnnaCFqFKTgVkCiaeCuMYoHm/SozbQ=; b=P8kbgsfH8GqS8m8vE1tA9O4TslmNDJ0GVptMZYJ/poYXZoYWcIL6hPo5AZ0lLX6tZ1 uNNW8HYAVCXZwh+vpDPGCYPWleCg4dRcCxy9K/aV2DlZ28w1JrdiJz5kUDwR1cdqVcuH ++XeUdZn2oYoq7l3hlPgetsi+2MNKe0iGvyUYYxvGW/PONRAFgkbPYxWqDZgaxiVstfR Sfu6UX7n/fDV1A4xFNFiLU4sczX+/mp6tea29ysk+o3fh8ZZUCZxoCNRTv345iITXwXI 9yANreBEe3M7ORKahYZ/1ru1DsDGcBaRx+SrGsZfrvJPHBFHSB8vPoliQ9+gR53THoD1 kK1g== X-Gm-Message-State: AOJu0YwRq/VNrATMI5TL+fpUm9BktRo34CnRDwv041kniRax6RER27P1 deM20FmmbDVxLM4Ca1E7HYCezvzPvSe2TRYNDAGaMMv92vSZB3coC7Yj9URfNvs0V1VTUlnenje tfC6PQtFvOkoWrs8HOANmd9I+HZJtR/uDW6FM5opkP0CIdK6oFpZRwBxJtw46oLBFTwGLlj7Bna zDWR9woyVn6ghVlAtPSWQcTtJUSHWSTvgSiV1lBdV6Fn0= X-Received: by 2002:a2e:3506:0:b0:2ec:4df7:8cef with SMTP id 38308e7fff4ca-2ee8ed42586mr12896741fa.15.1720102878330; Thu, 04 Jul 2024 07:21:18 -0700 (PDT) X-Google-Smtp-Source: AGHT+IFIJY/GMcOIW+fnjFktgCFjcRG/acS7kY/R+5bqMvsj+G2mh+dcJcICjiCOHaSeCDJK1tKxmQ== X-Received: by 2002:a2e:3506:0:b0:2ec:4df7:8cef with SMTP id 38308e7fff4ca-2ee8ed42586mr12896501fa.15.1720102877775; Thu, 04 Jul 2024 07:21:17 -0700 (PDT) Received: from localhost ([31.111.84.186]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-36795d1fc9csm3962108f8f.83.2024.07.04.07.21.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jul 2024 07:21:17 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCHv4 03/14] gdb: improve escaping when completing filenames Date: Thu, 4 Jul 2024 15:20:58 +0100 Message-Id: <1a3a72ce5649840817b81270021055044566d3dc.1720101827.git.aburgess@redhat.com> 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 Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.7 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP 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~public-inbox=simark.ca@sourceware.org This improves quoting and escaping when completing filenames for commands that allow filenames to be quoted and escaped. I've struggled a bit trying to split this series into chunks. There's a lot of dependencies between different parts of the completion system, and trying to get this working correctly is pretty messy. This first step is really about implementing 3 readline hooks: rl_char_is_quoted_p - Is a particular character quoted within readline's input buffer? rl_filename_dequoting_function - Remove quoting characters from a filename. rl_filename_quoting_function - Add quoting characters to a filename. See 'info readline' for full details, but with these hooks connected up, readline (on behalf of GDB) should do a better job inserting backslash escapes when completing filenames. There's still a bunch of stuff that doesn't work after this commit, mostly around the 'complete' command which of course doesn't go through readline, so doesn't benefit from all of these new functions yet, I'll add some of this in a later commit. Tab completion is now slightly improved though, it is possible to tab-complete a filename that includes a double or single quote, either in an unquoted string or within a string surrounded by single or double quotes, backslash escaping is used when necessary. There are some additional tests to cover the new functionality. --- gdb/completer.c | 168 +++++++++++++++++- .../gdb.base/filename-completion.exp | 34 ++++ 2 files changed, 199 insertions(+), 3 deletions(-) diff --git a/gdb/completer.c b/gdb/completer.c index 0e49549a643..3ab342dab4f 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -214,6 +214,159 @@ noop_completer (struct cmd_list_element *ignore, { } +/* Return 1 if the character at EINDEX in STRING is quoted (there is an + unclosed quoted string), or if the character at EINDEX is quoted by a + backslash. */ + +static int +gdb_completer_file_name_char_is_quoted (char *string, int eindex) +{ + for (int i = 0; i <= eindex && string[i] != '\0'; ) + { + char c = string[i]; + + if (c == '\\') + { + /* The backslash itself is not quoted. */ + if (i >= eindex) + return 0; + ++i; + /* But the next character is. */ + if (i >= eindex) + return 1; + if (string[i] == '\0') + return 0; + ++i; + continue; + } + else if (strchr (rl_completer_quote_characters, c) != nullptr) + { + /* This assumes that extract_string_maybe_quoted can handle a + string quoted with character C. Currently this is true as the + only characters we put in rl_completer_quote_characters are + single and/or double quotes, both of which + extract_string_maybe_quoted can handle. */ + gdb_assert (c == '"' || c == '\''); + const char *tmp = &string[i]; + (void) extract_string_maybe_quoted (&tmp); + i = tmp - string; + + /* Consider any character within the string we just skipped over + as quoted, though this might not be completely correct; the + opening and closing quotes are not themselves quoted. But so + far this doesn't seem to have caused any issues. */ + if (i >= eindex) + return 1; + } + else + ++i; + } + + return 0; +} + +/* Removing character escaping from FILENAME. QUOTE_CHAR is the quote + character around FILENAME or the null-character if there is no quoting + around FILENAME. */ + +static char * +gdb_completer_file_name_dequote (char *filename, int quote_char) +{ + std::string tmp; + + if (quote_char == '\'') + { + /* There is no backslash escaping within a single quoted string. In + this case we can just return the input string. */ + tmp = filename; + } + else if (quote_char == '"') + { + /* Remove escaping from a double quoted string. */ + for (const char *input = filename; + *input != '\0'; + ++input) + { + if (input[0] == '\\' + && input[1] != '\0' + && strchr ("\"\\", input[1]) != nullptr) + ++input; + tmp += *input; + } + } + else + { + gdb_assert (quote_char == '\0'); + + /* Remove escaping from an unquoted string. */ + for (const char *input = filename; + *input != '\0'; + ++input) + { + /* We allow anything to be escaped in an unquoted string. */ + if (*input == '\\') + { + ++input; + if (*input == '\0') + break; + } + + tmp += *input; + } + } + + 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. */ + +static char * +gdb_completer_file_name_quote (char *text, int match_type ATTRIBUTE_UNUSED, + char *quote_ptr) +{ + std::string str; + + if (*quote_ptr == '\'') + { + /* 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 == '"') + { + /* Add escaping for a double quoted filename. */ + for (const char *input = text; + *input != '\0'; + ++input) + { + if (strchr ("\"\\", *input) != nullptr) + str += '\\'; + str += *input; + } + } + else + { + gdb_assert (*quote_ptr == '\0'); + + /* Add escaping for an unquoted filename. */ + for (const char *input = text; + *input != '\0'; + ++input) + { + if (strchr (" \t\n\\\"'", *input) + != nullptr) + str += '\\'; + str += *input; + } + } + + return strdup (str.c_str ()); +} + /* 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 @@ -272,6 +425,7 @@ filename_maybe_quoted_completer_handle_brkchars (gdb_completer_file_name_break_characters); rl_completer_quote_characters = gdb_completer_file_name_quote_characters; + rl_char_is_quoted_p = gdb_completer_file_name_char_is_quoted; } /* Complete on filenames. This is for commands that accepts possibly @@ -1314,6 +1468,7 @@ complete_line_internal_1 (completion_tracker &tracker, completing file names then we can switch to the file name quote character set (i.e., both single- and double-quotes). */ rl_completer_quote_characters = gdb_completer_expression_quote_characters; + rl_char_is_quoted_p = nullptr; /* Decide whether to complete on a list of gdb commands or on symbols. */ @@ -2209,9 +2364,11 @@ completion_tracker::build_completion_result (const char *text, /* Build replacement word, based on the LCD. */ recompute_lowest_common_denominator (); - match_list[0] - = expand_preserving_ws (text, end - start, - m_lowest_common_denominator); + if (rl_filename_completion_desired) + match_list[0] = xstrdup (m_lowest_common_denominator); + else + match_list[0] + = expand_preserving_ws (text, end - start, m_lowest_common_denominator); if (m_lowest_common_denominator_unique) { @@ -3074,6 +3231,11 @@ _initialize_completer () rl_attempted_completion_function = gdb_rl_attempted_completion_function; set_rl_completer_word_break_characters (default_word_break_characters ()); + /* Setup readline globals relating to filename completion. */ + rl_filename_quote_characters = " \t\n\\\"'"; + rl_filename_dequoting_function = gdb_completer_file_name_dequote; + rl_filename_quoting_function = gdb_completer_file_name_quote; + add_setshow_zuinteger_unlimited_cmd ("max-completions", no_class, &max_completions, _("\ Set maximum number of completion candidates."), _("\ diff --git a/gdb/testsuite/gdb.base/filename-completion.exp b/gdb/testsuite/gdb.base/filename-completion.exp index 37629bfbf77..c670637ad61 100644 --- a/gdb/testsuite/gdb.base/filename-completion.exp +++ b/gdb/testsuite/gdb.base/filename-completion.exp @@ -52,6 +52,9 @@ proc setup_directory_tree {} { remote_exec host "touch \"${root}/bb2/dir 2/file 1\"" remote_exec host "touch \"${root}/bb2/dir 2/file 2\"" + remote_exec host "touch \"${root}/bb1/aa\\\"bb\"" + remote_exec host "touch \"${root}/bb1/aa'bb\"" + return $root } @@ -107,6 +110,37 @@ proc run_quoting_and_escaping_tests { root } { "aa cc" } "" "${qc}" false \ "expand filenames containing spaces" + + test_gdb_complete_multiple "$cmd ${qc}${root}/bb1/" \ + "a" "a" { + "aa\"bb" + "aa'bb" + } "" "${qc}" false \ + "expand filenames containing quotes" + } else { + set sp "\\ " + + 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}" " " \ + "expand unique filename containing single quote" } } -- 2.25.4