kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/extractor/testlib.cc (about)

     1  /*
     2   * Copyright 2019 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  #include "kythe/cxx/extractor/testlib.h"
    18  
    19  #include <errno.h>
    20  #include <unistd.h>
    21  
    22  #include <cstddef>
    23  #include <cstring>
    24  #include <memory>
    25  #include <optional>
    26  #include <string>
    27  #include <utility>
    28  #include <vector>
    29  
    30  #include "absl/container/flat_hash_map.h"
    31  #include "absl/log/check.h"
    32  #include "absl/log/log.h"
    33  #include "absl/strings/str_cat.h"
    34  #include "absl/strings/string_view.h"
    35  #include "absl/utility/utility.h"
    36  #include "google/protobuf/any.pb.h"
    37  #include "google/protobuf/message.h"
    38  #include "google/protobuf/text_format.h"
    39  #include "gtest/gtest.h"
    40  #include "kythe/cxx/common/kzip_reader.h"
    41  #include "kythe/cxx/common/path_utils.h"
    42  #include "kythe/proto/analysis.pb.h"
    43  #include "kythe/proto/cxx.pb.h"
    44  #include "kythe/proto/filecontext.pb.h"
    45  #include "llvm/ADT/StringRef.h"
    46  #include "llvm/Support/Program.h"
    47  #include "tools/cpp/runfiles/runfiles.h"
    48  
    49  namespace kythe {
    50  namespace {
    51  namespace gpb = ::google::protobuf;
    52  namespace kpb = ::kythe::proto;
    53  using HashMap = ::absl::flat_hash_map<std::string, std::size_t>;
    54  using ::bazel::tools::cpp::runfiles::Runfiles;
    55  
    56  // Path prefix joined to runfiles to form the workspace-relative path.
    57  constexpr absl::string_view kWorkspaceRoot = "io_kythe";
    58  
    59  constexpr absl::string_view kExtractorPath =
    60      "kythe/cxx/extractor/cxx_extractor";
    61  
    62  void CanonicalizeHash(HashMap* hashes, std::string* hash) {
    63    auto inserted = hashes->insert({*hash, hashes->size()});
    64    *hash = absl::StrCat("hash", inserted.first->second);
    65  }
    66  
    67  /// \brief Range wrapper around ContextDependentVersion, if any.
    68  class MutableContextRows {
    69   public:
    70    using iterator = decltype(std::declval<kpb::ContextDependentVersion>()
    71                                  .mutable_row()
    72                                  ->begin());
    73    explicit MutableContextRows(kpb::CompilationUnit::FileInput* file_input) {
    74      for (gpb::Any& detail : *file_input->mutable_details()) {
    75        if (detail.UnpackTo(&context_)) {
    76          any_ = &detail;
    77        }
    78      }
    79    }
    80  
    81    ~MutableContextRows() {
    82      if (any_ != nullptr) {
    83        any_->PackFrom(context_);
    84      }
    85    }
    86  
    87    iterator begin() { return context_.mutable_row()->begin(); }
    88    iterator end() { return context_.mutable_row()->end(); }
    89  
    90   private:
    91    gpb::Any* any_ = nullptr;
    92    kpb::ContextDependentVersion context_;
    93  };
    94  
    95  // RAII class for changing working directory.
    96  // Ideally, this would be done after fork(), but before exec() except this isn't
    97  // supported by LLVM ExecuteAndWait.
    98  class ScopedWorkingDirectory {
    99   public:
   100    explicit ScopedWorkingDirectory(std::string working_directory)
   101        : saved_working_directory_(
   102              working_directory.empty() ? "" : GetCurrentDirectory().value()) {
   103      if (working_directory.empty()) return;
   104      CHECK(::chdir(working_directory.c_str()) == 0)
   105          << "unable to chdir(" << working_directory << "): " << strerror(errno);
   106    }
   107    ~ScopedWorkingDirectory() {
   108      if (saved_working_directory_.empty()) return;
   109  
   110      CHECK(::chdir(saved_working_directory_.c_str()) == 0)
   111          << "unable to chdir(" << saved_working_directory_
   112          << "): " << strerror(errno);
   113    }
   114  
   115   private:
   116    std::string saved_working_directory_;
   117  };
   118  
   119  // RAII class which removes the designated file name on destruction.
   120  class TemporaryKzipFile {
   121   public:
   122    explicit TemporaryKzipFile() : filename_(KzipFileName()) {}
   123  
   124    const std::string& filename() const { return filename_; };
   125    ~TemporaryKzipFile() {
   126      if (::unlink(filename_.c_str()) != 0) {
   127        LOG(ERROR) << "unable to remove " << filename_ << ": " << strerror(errno);
   128      }
   129      std::string::size_type slash = filename_.find_last_of('/');
   130      if (slash != std::string::npos) {
   131        std::string dirname = filename_.substr(0, slash);
   132        if (::rmdir(dirname.c_str()) != 0) {
   133          LOG(ERROR) << "unable to remove " << dirname << ": " << strerror(errno);
   134        }
   135      }
   136    }
   137  
   138   private:
   139    static std::string KzipFileName() {
   140      const testing::TestInfo* test_info =
   141          testing::UnitTest::GetInstance()->current_test_info();
   142      std::string testname =
   143          test_info
   144              ? absl::StrCat(test_info->test_suite_name(), "_", test_info->name())
   145              : "kzip_test";
   146  
   147      std::string base =
   148          absl::StrCat(JoinPath(testing::TempDir(), testname), "XXXXXX");
   149  
   150      CHECK(mkdtemp(&base.front()) != nullptr)
   151          << "unable to create temporary directory: " << strerror(errno);
   152  
   153      return JoinPath(base, "test.kzip");
   154    }
   155  
   156    std::string filename_;
   157  };
   158  
   159  // Transforms std::vector<std::string> into a null-terminated array of c-string
   160  // pointers suitable for passing to posix_spawn.
   161  std::vector<llvm::StringRef> VectorForExecute(
   162      const std::vector<std::string>& values) {
   163    std::vector<llvm::StringRef> result;
   164    result.reserve(values.size());
   165    for (const auto& arg : values) {
   166      result.push_back(arg.c_str());
   167    }
   168    return result;
   169  }
   170  
   171  // Transforms a map of {key, value} pairs into a flattened vector of "key=value"
   172  // strings.
   173  std::vector<std::string> FlattenEnvironment(
   174      const absl::flat_hash_map<std::string, std::string>& env) {
   175    std::vector<std::string> result;
   176    result.reserve(env.size());
   177    for (const auto& entry : env) {
   178      result.push_back(absl::StrCat(entry.first, "=", entry.second));
   179    }
   180    return result;
   181  }
   182  
   183  bool ExecuteAndWait(const std::string& program,
   184                      const std::vector<std::string>& argv,
   185                      const std::vector<std::string>& env,
   186                      const std::string& working_directory) {
   187    ScopedWorkingDirectory directory(working_directory);
   188  
   189    std::string error;
   190    bool execution_failed = false;
   191    int exit_code = llvm::sys::ExecuteAndWait(
   192        program, VectorForExecute(argv), VectorForExecute(env),
   193        /* Redirects */ std::nullopt,
   194        /* SecondsToWait */ 0, /* MemoryLimit */ 0, &error, &execution_failed);
   195    if (!error.empty() || execution_failed || exit_code != 0) {
   196      LOG(ERROR) << "unable to run extractor (" << exit_code << "): " << error;
   197      return false;
   198    }
   199    return true;
   200  }
   201  }  // namespace
   202  
   203  void CanonicalizeHashes(kpb::CompilationUnit* unit) {
   204    HashMap hashes;
   205    CanonicalizeHash(&hashes, unit->mutable_entry_context());
   206    for (auto& input : *unit->mutable_required_input()) {
   207      for (auto& row : MutableContextRows(&input)) {
   208        CanonicalizeHash(&hashes, row.mutable_source_context());
   209        for (auto& column : *row.mutable_column()) {
   210          CanonicalizeHash(&hashes, column.mutable_linked_context());
   211        }
   212      }
   213    }
   214  }
   215  
   216  std::optional<std::vector<kpb::CompilationUnit>> ExtractCompilations(
   217      ExtractorOptions options) {
   218    gpb::LinkMessageReflection<kpb::CxxCompilationUnitDetails>();
   219  
   220    options.environment.insert({"KYTHE_EXCLUDE_EMPTY_DIRS", "1"});
   221    options.environment.insert({"KYTHE_EXCLUDE_AUTOCONFIGURATION_FILES", "1"});
   222    if (std::optional<std::string> extractor = ResolveRunfiles(kExtractorPath)) {
   223      TemporaryKzipFile output_file;
   224      options.environment.insert({"KYTHE_OUTPUT_FILE", output_file.filename()});
   225  
   226      // Add the extractor path as argv[0].
   227      options.arguments.insert(options.arguments.begin(), *extractor);
   228      if (ExecuteAndWait(*extractor, options.arguments,
   229                         FlattenEnvironment(options.environment),
   230                         options.working_directory)) {
   231        if (auto reader = KzipReader::Open(output_file.filename()); reader.ok()) {
   232          std::optional<std::vector<kpb::CompilationUnit>> result(
   233              absl::in_place);  // Default construct a result vector.
   234  
   235          auto status = reader->Scan([&](absl::string_view digest) {
   236            if (auto unit = reader->ReadUnit(digest); unit.ok()) {
   237              result->push_back(std::move(*unit->mutable_unit()));
   238              return true;
   239            } else {
   240              LOG(ERROR) << "Unable to read CompilationUnit " << digest << ": "
   241                         << unit.status();
   242              result.reset();
   243              return false;
   244            }
   245          });
   246          if (!status.ok()) {
   247            LOG(ERROR) << "Unable to read compilations: " << status;
   248            return std::nullopt;
   249          }
   250          return result;
   251        } else {
   252          LOG(ERROR) << "Unable to open " << output_file.filename() << ": "
   253                     << reader.status();
   254        }
   255        return std::nullopt;
   256      }
   257  
   258    } else {
   259      LOG(ERROR) << "Unable to resolve extractor path";
   260    }
   261    return std::nullopt;
   262  }
   263  
   264  std::optional<std::string> ResolveRunfiles(absl::string_view path) {
   265    std::string error;
   266    std::unique_ptr<Runfiles> runfiles(Runfiles::CreateForTest(&error));
   267    if (runfiles == nullptr) {
   268      LOG(ERROR) << error;
   269      return std::nullopt;
   270    }
   271    std::string resolved = runfiles->Rlocation(JoinPath(kWorkspaceRoot, path));
   272    if (resolved.empty()) {
   273      return std::nullopt;
   274    }
   275    return resolved;
   276  }
   277  
   278  kpb::CompilationUnit ExtractSingleCompilationOrDie(ExtractorOptions options) {
   279    if (std::optional<std::vector<kpb::CompilationUnit>> result =
   280            ExtractCompilations(std::move(options))) {
   281      CHECK(result->size() == 1)
   282          << "unexpected number of extracted compilations: " << result->size();
   283      return std::move(result->front());
   284    } else {
   285      LOG(FATAL) << "Unable to extract compilation";
   286    }
   287  }
   288  
   289  kpb::CompilationUnit ParseTextCompilationUnitOrDie(absl::string_view text) {
   290    kpb::CompilationUnit result;
   291    CHECK(gpb::TextFormat::ParseFromString(std::string(text), &result));
   292    return result;
   293  }
   294  }  // namespace kythe