Refactor Rcfile to use modern Google C++ practices

* Prefer `absl::flat_hash_map` to `std::unordered_map`. This is a much more efficient data structure.
* Prefer `absl::string_view` to `const char*`. This allows compile-time strlen.
* Prefer `absl::WrapUnique` to `std::unique_ptr<T>(T*)`. This better signals intent to wrap a raw pointer.
* Prefer `std::vector` to `std::deque` given we only need a stack, not a queue. This is much faster and more memory-efficient.
* Use if-init to more tightly scope variables.
* Prefer `absl::StrCat` to `blaze_util::StringPrintf` in simple cases. This is faster and more readable when merely concatenating a few strings together.
* Prefer `absl::StrFormat` to `blaze_util::StringPrintf` in cases where we need to do more complicated string construction. This is the standard printf-style library in Google.
* Prefer `absl::StrAppend` to `std::string::operator+`. This is more efficient and readable.
* Prefer `absl::StrSplit` to `blaze_util::Split`. This is the standard string splitting library in Google.
* Prefer `absl::StartsWith` to `std::string::compare`. This is a more expressive API.
* Prefer `absl::c_linear_search` to `std::find`. This avoids needing to name the container three times. This should really be `absl::c_contains`, which provides a better name to the operation, but this is blocked by https://github.com/bazelbuild/bazel/issues/22719.
* Use the early-return pattern where possible to decrease nesting.
* Use references to remove unnecessary copies.
* Don't store unnecessary variables in the class. This decreases object size.
* Remove using-declarations for names in `std::`, which is discouraged.

PiperOrigin-RevId: 661149102
Change-Id: I2ab8fc617e1cec9f6bb81c6a3d5bbb883164f299
diff --git a/src/main/cpp/BUILD b/src/main/cpp/BUILD
index efedce5..f2ae66d 100644
--- a/src/main/cpp/BUILD
+++ b/src/main/cpp/BUILD
@@ -242,6 +242,13 @@
         ":workspace_layout",
         "//src/main/cpp/util",
         "//src/main/cpp/util:logging",
+        "@abseil-cpp//absl/algorithm:container",
+        "@abseil-cpp//absl/container:flat_hash_map",
+        "@abseil-cpp//absl/memory",
+        "@abseil-cpp//absl/strings",
+        "@abseil-cpp//absl/strings:str_format",
+        "@abseil-cpp//absl/strings:string_view",
+        "@abseil-cpp//absl/types:span",
     ],
 )
 
diff --git a/src/main/cpp/rc_file.cc b/src/main/cpp/rc_file.cc
index 01016e0..d0d5f0a 100644
--- a/src/main/cpp/rc_file.cc
+++ b/src/main/cpp/rc_file.cc
@@ -14,8 +14,10 @@
 
 #include "src/main/cpp/rc_file.h"
 
-#include <algorithm>
+#include <memory>
+#include <string>
 #include <utility>
+#include <vector>
 
 #include "src/main/cpp/blaze_util.h"
 #include "src/main/cpp/blaze_util_platform.h"
@@ -23,47 +25,47 @@
 #include "src/main/cpp/util/logging.h"
 #include "src/main/cpp/util/strings.h"
 #include "src/main/cpp/workspace_layout.h"
+#include "absl/algorithm/container.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 
 namespace blaze {
 
-using std::deque;
-using std::string;
-using std::vector;
-
-static constexpr const char* kCommandImport = "import";
-static constexpr const char* kCommandTryImport = "try-import";
-
-RcFile::RcFile(string filename, const WorkspaceLayout* workspace_layout,
-               string workspace)
-    : filename_(std::move(filename)),
-      workspace_layout_(workspace_layout),
-      workspace_(std::move(workspace)) {}
+static constexpr absl::string_view kCommandImport = "import";
+static constexpr absl::string_view kCommandTryImport = "try-import";
 
 /*static*/ std::unique_ptr<RcFile> RcFile::Parse(
-    std::string filename, const WorkspaceLayout* workspace_layout,
-    std::string workspace, ParseError* error, std::string* error_text) {
-  std::unique_ptr<RcFile> rcfile(new RcFile(
-      std::move(filename), workspace_layout, std::move(workspace)));
-  deque<string> initial_import_stack = {rcfile->filename_};
-  *error = rcfile->ParseFile(
-      rcfile->filename_, &initial_import_stack, error_text);
+    const std::string& filename, const WorkspaceLayout* workspace_layout,
+    const std::string& workspace, ParseError* error, std::string* error_text) {
+  auto rcfile = absl::WrapUnique(new RcFile());
+  std::vector<std::string> initial_import_stack = {filename};
+  *error = rcfile->ParseFile(filename, workspace, *workspace_layout,
+                             initial_import_stack, error_text);
   return (*error == ParseError::NONE) ? std::move(rcfile) : nullptr;
 }
 
-RcFile::ParseError RcFile::ParseFile(const string& filename,
-                                     deque<string>* import_stack,
-                                     string* error_text) {
+RcFile::ParseError RcFile::ParseFile(const std::string& filename,
+                                     const std::string& workspace,
+                                     const WorkspaceLayout& workspace_layout,
+                                     std::vector<std::string>& import_stack,
+                                     std::string* error_text) {
   BAZEL_LOG(INFO) << "Parsing the RcFile " << filename;
-  string contents;
-  string error_message;
-  if (!blaze_util::ReadFile(filename, &contents, &error_message)) {
-    blaze_util::StringPrintf(error_text,
-                             "Unexpected error reading config file '%s': %s",
-                             filename.c_str(), error_message.c_str());
+  std::string contents;
+  if (std::string error_msg;
+      !blaze_util::ReadFile(filename, &contents, &error_msg)) {
+    *error_text = absl::StrFormat(
+        "Unexpected error reading config file '%s': %s", filename, error_msg);
     return ParseError::UNREADABLE_FILE;
   }
   const std::string canonical_filename =
       blaze_util::MakeCanonical(filename.c_str());
+  const absl::string_view workspace_prefix(
+      workspace_layout.WorkspacePrefix, workspace_layout.WorkspacePrefixLength);
 
   int rcfile_index = canonical_rcfile_paths_.size();
   canonical_rcfile_paths_.push_back(canonical_filename);
@@ -72,16 +74,14 @@
   blaze_util::Replace("\\\r\n", "", &contents);
   blaze_util::Replace("\\\n", "", &contents);
 
-  vector<string> lines = blaze_util::Split(contents, '\n');
-  for (string& line : lines) {
+  std::vector<std::string> lines = absl::StrSplit(contents, '\n');
+  for (std::string& line : lines) {
     blaze_util::StripWhitespace(&line);
 
     // Check for an empty line.
-    if (line.empty()) {
-      continue;
-    }
+    if (line.empty()) continue;
 
-    vector<string> words;
+    std::vector<std::string> words;
 
     // This will treat "#" as a comment, and properly
     // quote single and double quotes, and treat '\'
@@ -90,64 +90,68 @@
     // dangling backslash escapes and missing end-quotes.
     blaze_util::Tokenize(line, '#', &words);
 
-    if (words.empty()) {
-      // Could happen if line starts with "#"
+    // Could happen if line starts with "#"
+    if (words.empty()) continue;
+
+    const absl::string_view command = words[0];
+    if (command != kCommandImport && command != kCommandTryImport) {
+      for (absl::string_view word : absl::MakeConstSpan(words).subspan(1)) {
+        options_[command].push_back({std::string(word), rcfile_index});
+      }
       continue;
     }
 
-    string command = words[0];
+    if (words.size() != 2) {
+      *error_text = absl::StrFormat(
+          "Invalid import declaration in config file '%s': '%s'",
+          canonical_filename, line);
+      return ParseError::INVALID_FORMAT;
+    }
 
-    if (command == kCommandImport || command == kCommandTryImport) {
-      if (words.size() != 2 ||
-          (words[1].compare(0, workspace_layout_->WorkspacePrefixLength,
-                            workspace_layout_->WorkspacePrefix) == 0 &&
-           !workspace_layout_->WorkspaceRelativizeRcFilePath(workspace_,
-                                                             &words[1]) &&
-           command == kCommandImport)) {
-        blaze_util::StringPrintf(
-            error_text,
-            "Invalid import declaration in config file '%s': '%s'"
+    std::string& import_filename = words[1];
+    if (absl::StartsWith(import_filename, workspace_prefix)) {
+      const bool could_relativize =
+          workspace_layout.WorkspaceRelativizeRcFilePath(workspace,
+                                                         &import_filename);
+      if (!could_relativize && command == kCommandImport) {
+        *error_text = absl::StrFormat(
+            "Nonexistant path in import declaration in config file '%s': '%s'"
             " (are you in your source checkout/WORKSPACE?)",
-            canonical_filename.c_str(), line.c_str());
+            canonical_filename, line);
         return ParseError::INVALID_FORMAT;
       }
-      if (std::find(import_stack->begin(), import_stack->end(), words[1]) !=
-          import_stack->end()) {
-        string loop;
-        for (const string& imported_rc : *import_stack) {
-          loop += "  " + imported_rc + "\n";
-        }
-        loop += "  " + words[1] + "\n";  // Include the loop.
-        blaze_util::StringPrintf(error_text,
-            "Import loop detected:\n%s", loop.c_str());
-        return ParseError::IMPORT_LOOP;
-      }
+    }
 
-      import_stack->push_back(words[1]);
-      ParseError parse_error = ParseFile(words[1], import_stack, error_text);
-      if (parse_error != ParseError::NONE) {
-        if (parse_error == ParseError::UNREADABLE_FILE &&
-            command == kCommandTryImport) {
-          // For try-import, we ignore it if we couldn't find a file.
-          BAZEL_LOG(INFO) << "Skipped optional import of " << words[1]
-                          << ", the specified rc file either does not exist or "
-                             "is not readable.";
-          *error_text = "";
-        } else {
-          // Files that are there but are malformed or introduce a loop are
-          // still a problem, though, so perpetuate those errors as we would
-          // for a normal import statement.
-          return parse_error;
-        }
+    if (absl::c_linear_search(import_stack, import_filename)) {
+      std::string loop;
+      for (const std::string& imported_rc : import_stack) {
+        absl::StrAppend(&loop, "  ", imported_rc, "\n");
       }
-      import_stack->pop_back();
-    } else {
-      auto words_it = words.begin();
-      words_it++;  // Advance past command.
-      for (; words_it != words.end(); words_it++) {
-        options_[command].push_back({*words_it, rcfile_index});
+      absl::StrAppend(&loop, "  ", import_filename, "\n");  // Include the loop.
+      *error_text = absl::StrCat("Import loop detected:\n", loop);
+      return ParseError::IMPORT_LOOP;
+    }
+
+    import_stack.push_back(import_filename);
+    if (ParseError parse_error =
+            ParseFile(import_filename, workspace, workspace_layout,
+                      import_stack, error_text);
+        parse_error != ParseError::NONE) {
+      if (parse_error == ParseError::UNREADABLE_FILE &&
+          command == kCommandTryImport) {
+        // For try-import, we ignore it if we couldn't find a file.
+        BAZEL_LOG(INFO) << "Skipped optional import of " << import_filename
+                        << ", the specified rc file either does not exist or "
+                           "is not readable.";
+        *error_text = "";
+      } else {
+        // Files that are there but are malformed or introduce a loop are
+        // still a problem, though, so perpetuate those errors as we would
+        // for a normal import statement.
+        return parse_error;
       }
     }
+    import_stack.pop_back();
   }
 
   return ParseError::NONE;
diff --git a/src/main/cpp/rc_file.h b/src/main/cpp/rc_file.h
index 91bb119..88af39a 100644
--- a/src/main/cpp/rc_file.h
+++ b/src/main/cpp/rc_file.h
@@ -14,13 +14,12 @@
 #ifndef BAZEL_SRC_MAIN_CPP_RC_FILE_H_
 #define BAZEL_SRC_MAIN_CPP_RC_FILE_H_
 
-#include <deque>
 #include <memory>
 #include <string>
-#include <unordered_map>
 #include <vector>
 
 #include "src/main/cpp/workspace_layout.h"
+#include "absl/container/flat_hash_map.h"
 
 namespace blaze {
 
@@ -39,8 +38,8 @@
   // error and error text on failure.
   enum class ParseError { NONE, UNREADABLE_FILE, INVALID_FORMAT, IMPORT_LOOP };
   static std::unique_ptr<RcFile> Parse(
-      std::string filename, const WorkspaceLayout* workspace_layout,
-      std::string workspace, ParseError* error, std::string* error_text);
+      const std::string& filename, const WorkspaceLayout* workspace_layout,
+      const std::string& workspace, ParseError* error, std::string* error_text);
 
   // Movable and copyable.
   RcFile(const RcFile&) = default;
@@ -54,24 +53,19 @@
   }
 
   // Command -> all options for that command (in order of appearance).
-  using OptionMap = std::unordered_map<std::string, std::vector<RcOption>>;
+  using OptionMap = absl::flat_hash_map<std::string, std::vector<RcOption>>;
   const OptionMap& options() const { return options_; }
 
  private:
-  RcFile(std::string filename, const WorkspaceLayout* workspace_layout,
-         std::string workspace);
+  RcFile() = default;
 
   // Recursive call to parse a file and its imports.
   ParseError ParseFile(const std::string& filename,
-                       std::deque<std::string>* import_stack,
+                       const std::string& workspace,
+                       const WorkspaceLayout& workspace_layout,
+                       std::vector<std::string>& import_stack,
                        std::string* error_text);
 
-  std::string filename_;
-
-  // Workspace definition.
-  const WorkspaceLayout* workspace_layout_;
-  std::string workspace_;
-
   // Full closure of rcfile paths imported from this file (including itself).
   // These are all canonical paths, created with blaze_util::MakeCanonical.
   // This also means all of these paths should exist.
diff --git a/src/test/cpp/rc_options_test.cc b/src/test/cpp/rc_options_test.cc
index 0a1bf28..030fb50 100644
--- a/src/test/cpp/rc_options_test.cc
+++ b/src/test/cpp/rc_options_test.cc
@@ -12,23 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <cstddef>
+#include <memory>
+#include <string>
 #include <vector>
 
-#include "src/main/cpp/blaze_util.h"
 #include "src/main/cpp/blaze_util_platform.h"
 #include "src/main/cpp/option_processor.h"
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/file_platform.h"
 #include "src/main/cpp/util/path.h"
-#include "src/main/cpp/util/strings.h"
 #include "src/main/cpp/workspace_layout.h"
 #include "googlemock/include/gmock/gmock.h"
 #include "googletest/include/gtest/gtest.h"
+#include "absl/container/flat_hash_map.h"
+#include "src/main/cpp/rc_file.h"
 
 namespace blaze {
-using std::string;
-using std::unordered_map;
-using std::vector;
+namespace {
+
 using ::testing::MatchesRegex;
 
 #if _WIN32
@@ -46,10 +48,10 @@
         workspace_layout_() {
   }
 
-  const string test_file_dir_;
+  const std::string test_file_dir_;
   const WorkspaceLayout workspace_layout_;
 
-  void WriteRc(const string& filename, const string& contents) {
+  void WriteRc(const std::string& filename, const std::string& contents) {
     bool success_mkdir = blaze_util::MakeDirectories(test_file_dir_, 0755);
     ASSERT_TRUE(success_mkdir) << "Failed to mkdir -p " << test_file_dir_;
     bool success_write = blaze_util::WriteFile(
@@ -57,7 +59,7 @@
     ASSERT_TRUE(success_write) << "Failed to write " << filename;
   }
 
-  std::unique_ptr<RcFile> Parse(const string& filename,
+  std::unique_ptr<RcFile> Parse(const std::string& filename,
                                 RcFile::ParseError* error,
                                 std::string* error_text) {
     return RcFile::Parse(
@@ -70,10 +72,11 @@
   }
 
   void SuccessfullyParseRcWithExpectedArgs(
-      const string& filename,
-      const unordered_map<string, vector<string>>& expected_args_map) {
+      const std::string& filename,
+      const absl::flat_hash_map<std::string, std::vector<std::string>>&
+          expected_args_map) {
     RcFile::ParseError error;
-    string error_text;
+    std::string error_text;
     std::unique_ptr<RcFile> rc = Parse(filename, &error, &error_text);
     EXPECT_EQ(error_text, "");
     ASSERT_EQ(error, RcFile::ParseError::NONE);
@@ -83,11 +86,11 @@
     // correct order. Note that this is not just an exercise in rewriting map
     // equality - the results have type RcOption, and the expected values
     // are just strings. This is ignoring the source_path for convenience.
-    const RcFile::OptionMap& result = rc->options();
+    RcFile::OptionMap result = rc->options();
     ASSERT_EQ(expected_args_map.size(), result.size());
     for (const auto& command_args_pair : expected_args_map) {
-      const string& expected_command = command_args_pair.first;
-      const vector<string>& expected_args = command_args_pair.second;
+      const std::string& expected_command = command_args_pair.first;
+      const std::vector<std::string>& expected_args = command_args_pair.second;
       const auto result_args_iter = result.find(expected_command);
       ASSERT_NE(result_args_iter, rc->options().end());
       const std::vector<RcOption>& result_args = result_args_iter->second;
@@ -102,21 +105,21 @@
 TEST_F(RcOptionsTest, Empty) {
   WriteRc("empty.bazelrc",
           "");
-  unordered_map<string, vector<string>> no_expected_args;
+  absl::flat_hash_map<std::string, std::vector<std::string>> no_expected_args;
   SuccessfullyParseRcWithExpectedArgs("empty.bazelrc", no_expected_args);
 }
 
 TEST_F(RcOptionsTest, Whitespace) {
   WriteRc("whitespace.bazelrc",
           "      \n\t      ");
-  unordered_map<string, vector<string>> no_expected_args;
+  absl::flat_hash_map<std::string, std::vector<std::string>> no_expected_args;
   SuccessfullyParseRcWithExpectedArgs("whitespace.bazelrc", no_expected_args);
 }
 
 TEST_F(RcOptionsTest, CommentedStartup) {
   WriteRc("commented_startup.bazelrc",
           "# startup foo");
-  unordered_map<string, vector<string>> no_expected_args;
+  absl::flat_hash_map<std::string, std::vector<std::string>> no_expected_args;
   SuccessfullyParseRcWithExpectedArgs("commented_startup.bazelrc",
                                       no_expected_args);
 }
@@ -124,7 +127,7 @@
 TEST_F(RcOptionsTest, EmptyStartupLine) {
   WriteRc("empty_startup_line.bazelrc",
           "startup");
-  unordered_map<string, vector<string>> no_expected_args;
+  absl::flat_hash_map<std::string, std::vector<std::string>> no_expected_args;
   SuccessfullyParseRcWithExpectedArgs("empty_startup_line.bazelrc",
                                       no_expected_args);
 }
@@ -132,7 +135,7 @@
 TEST_F(RcOptionsTest, StartupWithOnlyCommentedArg) {
   WriteRc("startup_with_comment.bazelrc",
           "startup # bar");
-  unordered_map<string, vector<string>> no_expected_args;
+  absl::flat_hash_map<std::string, std::vector<std::string>> no_expected_args;
   SuccessfullyParseRcWithExpectedArgs("startup_with_comment.bazelrc",
                                       no_expected_args);
 }
@@ -356,7 +359,7 @@
           "import %workspace%/import_cycle_1.bazelrc");
 
   RcFile::ParseError error;
-  string error_text;
+  std::string error_text;
   std::unique_ptr<RcFile> rc =
       Parse("import_cycle_1.bazelrc", &error, &error_text);
   EXPECT_EQ(error, RcFile::ParseError::IMPORT_LOOP);
@@ -383,7 +386,7 @@
           "import %workspace%/import_cycle_1.bazelrc");
 
   RcFile::ParseError error;
-  string error_text;
+  std::string error_text;
   std::unique_ptr<RcFile> rc =
       Parse("chain_to_cycle_1.bazelrc", &error, &error_text);
   EXPECT_EQ(error, RcFile::ParseError::IMPORT_LOOP);
@@ -401,7 +404,7 @@
 
 TEST_F(RcOptionsTest, FileDoesNotExist) {
   RcFile::ParseError error;
-  string error_text;
+  std::string error_text;
   std::unique_ptr<RcFile> rc = Parse("not_a_file.bazelrc", &error, &error_text);
   EXPECT_EQ(error, RcFile::ParseError::UNREADABLE_FILE);
   ASSERT_THAT(
@@ -418,7 +421,7 @@
           "import somefile");
 
   RcFile::ParseError error;
-  string error_text;
+  std::string error_text;
   std::unique_ptr<RcFile> rc =
       Parse("import_fake_file.bazelrc", &error, &error_text);
   EXPECT_EQ(error, RcFile::ParseError::UNREADABLE_FILE);
@@ -437,7 +440,7 @@
 TEST_F(RcOptionsTest, TryImportedFileDoesNotExist) {
   WriteRc("try_import_fake_file.bazelrc", "try-import somefile");
 
-  unordered_map<string, vector<string>> no_expected_args;
+  absl::flat_hash_map<std::string, std::vector<std::string>> no_expected_args;
   SuccessfullyParseRcWithExpectedArgs("try_import_fake_file.bazelrc",
                                       no_expected_args);
 }
@@ -447,28 +450,25 @@
           "import somefile bar");
 
   RcFile::ParseError error;
-  string error_text;
+  std::string error_text;
   std::unique_ptr<RcFile> rc = Parse("bad_import.bazelrc", &error, &error_text);
   EXPECT_EQ(error, RcFile::ParseError::INVALID_FORMAT);
-  ASSERT_THAT(
-      error_text,
-      MatchesRegex("Invalid import declaration in config file "
-                   "'.*bad_import.bazelrc': 'import somefile bar' \\(are you "
-                   "in your source checkout/WORKSPACE\\?\\)"));
+  ASSERT_THAT(error_text,
+              MatchesRegex("Invalid import declaration in config file "
+                           "'.*bad_import.bazelrc': 'import somefile bar'"));
 }
 
 TEST_F(RcOptionsTest, TryImportHasTooManyArgs) {
   WriteRc("bad_import.bazelrc", "try-import somefile bar");
 
   RcFile::ParseError error;
-  string error_text;
+  std::string error_text;
   std::unique_ptr<RcFile> rc = Parse("bad_import.bazelrc", &error, &error_text);
   EXPECT_EQ(error, RcFile::ParseError::INVALID_FORMAT);
   ASSERT_THAT(
       error_text,
       MatchesRegex("Invalid import declaration in config file "
-                   "'.*bad_import.bazelrc': 'try-import somefile bar' \\(are "
-                   "you in your source checkout/WORKSPACE\\?\\)"));
+                   "'.*bad_import.bazelrc': 'try-import somefile bar'"));
 }
 
 // TODO(b/34811299) The tests below identify ways that '\' used as a line
@@ -522,5 +522,5 @@
       {{"startup", {"foo", " "}}, {"bar", {"baz"}}});
 }
 
+}  // namespace
 }  // namespace blaze
-