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

     1  /*
     2   * Copyright 2015 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/tools/fyi/fyi.h"
    18  
    19  #include <memory>
    20  
    21  #include "absl/strings/str_cat.h"
    22  #include "absl/strings/str_format.h"
    23  #include "clang/Frontend/ASTUnit.h"
    24  #include "clang/Frontend/CompilerInstance.h"
    25  #include "clang/Frontend/TextDiagnosticPrinter.h"
    26  #include "clang/Lex/Preprocessor.h"
    27  #include "clang/Parse/ParseAST.h"
    28  #include "clang/Rewrite/Core/Rewriter.h"
    29  #include "clang/Sema/ExternalSemaSource.h"
    30  #include "clang/Sema/Sema.h"
    31  #include "kythe/cxx/common/kythe_uri.h"
    32  #include "kythe/cxx/common/schema/edges.h"
    33  #include "kythe/cxx/common/schema/facts.h"
    34  #include "llvm/Support/Path.h"
    35  #include "llvm/Support/Timer.h"
    36  #include "llvm/Support/raw_ostream.h"
    37  #include "third_party/llvm/src/clang_builtin_headers.h"
    38  
    39  namespace kythe {
    40  namespace fyi {
    41  
    42  /// \brief Tracks changes and edits to a single file identified by its full
    43  /// path.
    44  ///
    45  /// Holds a `llvm::MemoryBuffer` with the results of the most recent edits
    46  /// if edits have been made.
    47  ///
    48  /// This object is first created when the compiler enters a new main source
    49  /// file. Before each new compile or reparse pass, the outer loop should call
    50  /// ::BeginPass(). FileTracker is then notified of various events involving the
    51  /// file being processed. If the FileTracker enters a state that is not kBusy,
    52  /// it can make no further progress. Otherwise, when the compile completes, it
    53  /// is expected that the outer loop will attempt a call to ::Rewrite() with a
    54  /// fresh Rewriter instance. If that succeeds, then ::CommitRewrite will update
    55  /// internal buffers for subsequent passes.
    56  class FileTracker {
    57   public:
    58    explicit FileTracker(llvm::StringRef filename) : filename_(filename) {}
    59  
    60    /// \brief Returns the current rewritten file (or null, if rewriting hasn't
    61    /// happend).
    62    ///
    63    /// The object shares its lifetime with this FileTracker.
    64    llvm::MemoryBuffer* memory_buffer() { return memory_buffer_.get(); }
    65  
    66    llvm::StringRef filename() { return filename_; }
    67  
    68    const llvm::StringRef backing_store() {
    69      assert(active_buffer_ < 2);
    70      if (memory_buffer_backing_store_[active_buffer_].empty()) {
    71        return "";
    72      }
    73      auto* store = &memory_buffer_backing_store_[active_buffer_];
    74      const char* start = store->data();
    75      // Why the - 1?: MemoryBufferBackingStore ends with a NUL terminator.
    76      return llvm::StringRef(start, store->size() - 1);
    77    }
    78  
    79    /// \param Start a new pass involving this `FileTracker`
    80    void BeginPass() {
    81      file_begin_ = clang::SourceLocation();
    82      pass_had_errors_ = false;
    83    }
    84  
    85    /// Called for each include file we discover is in the file during a major
    86    /// pass.
    87    /// Will catch includes that we've added in earlier major passes as well.
    88    /// \param source_manager the active SourceManager
    89    /// \param canonical_path the canonical path to the include file
    90    /// \param uttered_path the path as it appeared in the program
    91    /// \param is_angled whether angle brackets were used
    92    /// \param hash_location the source location of the include's \#
    93    /// \param end_location the source location following the include
    94    void NextInclude(clang::SourceManager* source_manager,
    95                     llvm::StringRef canonical_path, llvm::StringRef uttered_path,
    96                     bool IsAngled, clang::SourceLocation hash_location,
    97                     clang::SourceLocation end_location) {
    98      unsigned offset = source_manager->getFileOffset(end_location);
    99      if (offset > last_include_offset_) {
   100        last_include_offset_ = offset;
   101      }
   102    }
   103  
   104    /// \brief Rewrite the associated source file with our tentative suggestions.
   105    /// \param rewriter a valid Rewriter.
   106    /// \return true if changes will be made, false otherwise.
   107    bool Rewrite(clang::Rewriter* rewriter) {
   108      if (state_ != State::kBusy) {
   109        return false;
   110      }
   111      if (!pass_had_errors_) {
   112        state_ = State::kSuccess;
   113        return false;
   114      }
   115      if (!untried_.empty()) {
   116        auto to_try = *untried_.begin();
   117        untried_.erase(untried_.begin());
   118        tried_.insert(to_try);
   119        rewriter->InsertTextAfter(
   120            file_begin_.getLocWithOffset(last_include_offset_),
   121            "\n#include \"" + to_try + "\"\n");
   122        return true;
   123      }
   124      // We have nothing to do, so abort.
   125      state_ = State::kFailure;
   126      return false;
   127    }
   128  
   129    /// \brief Rewrite the old file into a new file, discarding any previously
   130    /// allocated buffers.
   131    /// \param file_id the current ID of the file we are rewriting
   132    /// \param rewriter a valid Rewriter.
   133    void CommitRewrite(clang::FileID file_id, clang::Rewriter* rewriter) {
   134      assert(active_buffer_ < 2);
   135      can_undo_ = true;
   136      active_buffer_ = 1 - active_buffer_;
   137      auto* store = &memory_buffer_backing_store_[active_buffer_];
   138      const clang::RewriteBuffer* buffer = rewriter->getRewriteBufferFor(file_id);
   139      store->clear();
   140      llvm::raw_svector_ostream buffer_stream(*store);
   141      buffer->write(buffer_stream);
   142      // Required null terminator.
   143      store->push_back(0);
   144      const char* start = store->data();
   145      llvm::StringRef data(start, store->size() - 1);
   146      memory_buffer_ = llvm::MemoryBuffer::getMemBuffer(data);
   147    }
   148  
   149    /// \brief Analysis state, maintained across passes.
   150    enum class State {
   151      kBusy,     ///< We are trying to repair this file.
   152      kSuccess,  ///< We have repaired this file (or there is nothing we can do).
   153      kFailure   ///< We are no longer trying to repair this file.
   154    };
   155  
   156    /// \brief Gets the state (busy, OK, or bad) of this FileTracker.
   157    State state() const { return state_; }
   158  
   159    /// \brief Marks that this FileTracker cannot be repaired.
   160    void mark_failed() { state_ = State::kFailure; }
   161  
   162    /// \brief Gets the location at the very top of the file (in this pass).
   163    clang::SourceLocation file_begin() const { return file_begin_; }
   164  
   165    /// \brief Sets the location at the very top of the file (in this pass).
   166    void set_file_begin(clang::SourceLocation location) {
   167      file_begin_ = location;
   168    }
   169  
   170    /// \brief Decode and possibly take action on a diagnostic received during
   171    /// a compilation (sub)pass.
   172    /// \param diagnostic The diagnostic to handle.
   173    void HandleStoredDiagnostic(clang::StoredDiagnostic& diagnostic) {
   174      pass_had_errors_ = true;
   175    }
   176  
   177    /// \brief Add an include to the set of includes to try.
   178    /// \param include_path The include path to try (as a quoted include).
   179    void TryInclude(const std::string& include_path) {
   180      if (!tried_.count(include_path)) {
   181        untried_.insert(include_path);
   182      }
   183    }
   184  
   185    /// \brief Record the initial state of the file before rewriting it.
   186    /// \param content The content of the file.
   187    void SetInitialContent(llvm::StringRef content) {
   188      if (!saw_initial_state_) {
   189        active_buffer_ = 0;
   190        memory_buffer_backing_store_[0].clear();
   191        memory_buffer_backing_store_[0].append(content.begin(), content.end());
   192        memory_buffer_backing_store_[0].push_back(0);
   193        memory_buffer_ = llvm::MemoryBuffer::getMemBuffer(backing_store());
   194        can_undo_ = false;
   195        saw_initial_state_ = true;
   196      }
   197    }
   198  
   199   private:
   200    friend class Action;
   201  
   202    /// Try to undo the previous change to the backing store.
   203    bool Undo() {
   204      assert(active_buffer_ < 2);
   205      if (can_undo_) {
   206        active_buffer_ = 1 - active_buffer_;
   207        memory_buffer_ = llvm::MemoryBuffer::getMemBuffer(backing_store());
   208        can_undo_ = false;
   209        return true;
   210      }
   211      return false;
   212    }
   213  
   214    /// The absolute path to the file this FileTracker tracks. Used as a key
   215    /// to connect between passes.
   216    std::string filename_;
   217  
   218    /// The location of the beginning of the tracked file. This changes after
   219    /// each pass.
   220    clang::SourceLocation file_begin_;
   221  
   222    /// The offset of the last include in the original source file. This will
   223    /// be used as the insertion point for new include directives.
   224    unsigned last_include_offset_ = 0;
   225  
   226    /// If this file has been modified, points to a MemoryBuffer containing
   227    /// the full text of the modified file.
   228    std::unique_ptr<llvm::MemoryBuffer> memory_buffer_ = nullptr;
   229  
   230    /// Data backing the MemoryBuffer. This is double-buffered, allowing for one
   231    /// step of undo. `active_buffer_` selects which buffer we should read from.
   232    llvm::SmallVector<char, 128> memory_buffer_backing_store_[2];
   233  
   234    /// Which backing store is currently active and which is the backup.
   235    /// Always < 2.
   236    size_t active_buffer_ = 0;
   237  
   238    /// Can we undo the previous move?
   239    bool can_undo_ = false;
   240  
   241    /// Have we ever seen the initial state of the file?
   242    bool saw_initial_state_ = false;
   243  
   244    /// The current of this FileTracker independent of pass.
   245    State state_ = State::kBusy;
   246  
   247    /// True if the last subpass had (recoverable) errors.
   248    bool pass_had_errors_ = false;
   249  
   250    /// Includes we've already tried.
   251    std::set<std::string> tried_;
   252  
   253    /// Includes we have left to try.
   254    std::set<std::string> untried_;
   255  };
   256  
   257  /// \brief During non-reparse passes, PreprocessorHooks listens for events
   258  /// indicating the files being analyzed and their preprocessor directives.
   259  class PreprocessorHooks : public clang::PPCallbacks {
   260   public:
   261    /// \param enclosing_pass The `Action` controlling this pass. Not owned.
   262    explicit PreprocessorHooks(Action* enclosing_pass)
   263        : enclosing_pass_(enclosing_pass), tracked_file_(nullptr) {}
   264  
   265    /// \copydoc PPCallbacks::FileChanged
   266    ///
   267    /// Finds the `FileEntry` and starting `SourceLocation` for each tracked
   268    /// file on every pass.
   269    void FileChanged(clang::SourceLocation loc,
   270                     clang::PPCallbacks::FileChangeReason reason,
   271                     clang::SrcMgr::CharacteristicKind file_type,
   272                     clang::FileID prev_fid) override;
   273  
   274    /// \copydoc PPCallbacks::InclusionDirective
   275    ///
   276    /// When \p SourceFile is the file being tracked by the enclosing pass,
   277    /// records details about each inclusion directive encountered (such as
   278    /// the name of the included file, the location of the directive, and so on).
   279    void InclusionDirective(clang::SourceLocation hash_location,
   280                            const clang::Token& include_token,
   281                            llvm::StringRef file_name, bool is_angled,
   282                            clang::CharSourceRange file_name_range,
   283                            clang::OptionalFileEntryRef include_file,
   284                            llvm::StringRef search_path,
   285                            llvm::StringRef relative_path,
   286                            const clang::Module* imported,
   287                            bool is_module_imported,
   288                            clang::SrcMgr::CharacteristicKind FileType) override;
   289  
   290   private:
   291    friend class Action;
   292  
   293    /// The current `Action`. Not owned.
   294    Action* enclosing_pass_;
   295  
   296    /// The `FileEntry` corresponding to the tracker in `enclosing_pass_`.
   297    /// Not owned.
   298    const clang::FileEntry* tracked_file_;
   299  };
   300  
   301  /// \brief Manages a full parse and any subsequent reparses for a single file.
   302  class Action : public clang::ASTFrontendAction,
   303                 public clang::ExternalSemaSource {
   304   public:
   305    explicit Action(ActionFactory& factory) : factory_(factory) {}
   306  
   307    /// \copydoc ASTFrontendAction::BeginInvocation
   308    bool BeginInvocation(clang::CompilerInstance& CI) override {
   309      auto* pp_opts = &CI.getPreprocessorOpts();
   310      pp_opts->RetainRemappedFileBuffers = true;
   311      pp_opts->AllowPCHWithCompilerErrors = true;
   312      factory_.RemapFiles(CI.getHeaderSearchOpts().ResourceDir,
   313                          &pp_opts->RemappedFileBuffers);
   314      return true;
   315    }
   316  
   317    /// \copydoc ASTFrontendAction::CreateASTConsumer
   318    std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
   319        clang::CompilerInstance& compiler, llvm::StringRef in_file) override {
   320      tracker_ = factory_.GetOrCreateTracker(in_file);
   321      // Don't bother starting a new pass if the tracker is finished.
   322      if (tracker_->state() == FileTracker::State::kBusy) {
   323        tracker_->BeginPass();
   324        compiler.getPreprocessor().addPPCallbacks(
   325            std::make_unique<PreprocessorHooks>(this));
   326      }
   327      return std::make_unique<clang::ASTConsumer>();
   328    }
   329  
   330    /// \copydoc ASTFrontendAction::ExecuteAction
   331    void ExecuteAction() override {
   332      // We have to reproduce what ASTFrontendAction::ExecuteAction does, since
   333      // we have to attach ourselves as an ExternalSemaSource to Sema before
   334      // calling ParseAST.
   335  
   336      // Do nothing if we've already given up on or finished this file.
   337      if (tracker_->state() != FileTracker::State::kBusy) {
   338        return;
   339      }
   340  
   341      clang::CompilerInstance* compiler = &getCompilerInstance();
   342      assert(!compiler->hasSema() && "CI already has Sema");
   343  
   344      if (hasCodeCompletionSupport() &&
   345          !compiler->getFrontendOpts().CodeCompletionAt.FileName.empty())
   346        compiler->createCodeCompletionConsumer();
   347  
   348      clang::CodeCompleteConsumer* completion_consumer = nullptr;
   349      if (compiler->hasCodeCompletionConsumer())
   350        completion_consumer = &compiler->getCodeCompletionConsumer();
   351  
   352      compiler->createSema(getTranslationUnitKind(), completion_consumer);
   353      compiler->getSema().addExternalSource(this);
   354  
   355      clang::ParseAST(compiler->getSema(), compiler->getFrontendOpts().ShowStats,
   356                      compiler->getFrontendOpts().SkipFunctionBodies);
   357    }
   358  
   359    /// \brief Copies the tickets from `reply.edge_set` to `request.ticket`.
   360    /// \return false if no tickets were copied
   361    template <typename Reply, typename Request>
   362    bool CopyTicketsFromEdgeSets(const Reply& reply, Request* request) {
   363      for (const auto& edge_set : reply.edge_sets()) {
   364        for (const auto& group : edge_set.second.groups()) {
   365          for (const auto& edge : group.second.edge()) {
   366            request->add_ticket(edge.target_ticket());
   367          }
   368        }
   369      }
   370      return request->ticket_size() != 0;
   371    }
   372  
   373    /// \brief Adds the paths of all /kythe/node/file nodes from `reply.node` to
   374    /// this Action's `FileTracker`'s include list.
   375    template <typename Reply>
   376    void AddFileNodesToTracker(const Reply& reply) {
   377      for (const auto& parent : reply.nodes()) {
   378        bool is_file = false;
   379        for (const auto& fact : parent.second.facts()) {
   380          if (fact.first == kythe::common::schema::kFactNodeKind) {
   381            is_file = (fact.second == "/kythe/node/file");
   382            break;
   383          }
   384        }
   385        if (!is_file) {
   386          continue;
   387        }
   388        auto maybe_uri = URI::FromString(parent.first);
   389        if (maybe_uri.first) {
   390          tracker_->TryInclude(maybe_uri.second.v_name().path());
   391        }
   392      }
   393    }
   394  
   395    /// \copydoc ExternalSemaSource::CorrectTypo
   396    clang::TypoCorrection CorrectTypo(
   397        const clang::DeclarationNameInfo& typo, int lookup_kind,
   398        clang::Scope* scope, clang::CXXScopeSpec* scope_spec,
   399        clang::CorrectionCandidateCallback& callback,
   400        clang::DeclContext* member_context, bool entering_context,
   401        const clang::ObjCObjectPointerType* objc_ptr_type) override {
   402      // Conservatively assume that something went wrong if we had to invoke
   403      // typo correction.
   404      tracker_->pass_had_errors_ = true;
   405      // Look for any name nodes that could help.
   406      proto::VName name_node;
   407      name_node.set_signature(typo.getAsString() + "#n");
   408      name_node.set_language("c++");
   409      proto::EdgesRequest named_edges_request;
   410      auto name_uri = URI(name_node).ToString();
   411      named_edges_request.add_ticket(name_uri);
   412      // We've found at least one interesting name in the graph. Now we need
   413      // to figure out which nodes those names are bound to.
   414      named_edges_request.add_kind(
   415          absl::StrCat("%", kythe::common::schema::kNamed));
   416      proto::EdgesReply named_edges_reply;
   417      std::string error_text;
   418      if (!factory_.xrefs_->Edges(named_edges_request, &named_edges_reply,
   419                                  &error_text)) {
   420        absl::FPrintF(stderr, "Xrefs error (named): %s\n", error_text);
   421        return clang::TypoCorrection();
   422      }
   423      // Get information about the places where those nodes were defined.
   424      proto::EdgesRequest defined_edges_request;
   425      proto::EdgesReply defined_edges_reply;
   426      if (!CopyTicketsFromEdgeSets(named_edges_reply, &defined_edges_request)) {
   427        return clang::TypoCorrection();
   428      }
   429      defined_edges_request.add_kind(
   430          absl::StrCat("%", kythe::common::schema::kDefines));
   431      if (!factory_.xrefs_->Edges(defined_edges_request, &defined_edges_reply,
   432                                  &error_text)) {
   433        absl::FPrintF(stderr, "Xrefs error (defines): %s\n", error_text);
   434        return clang::TypoCorrection();
   435      }
   436      // Finally, figure out whether we can make those definition sites visible
   437      // to the site of the typo by adding an include.
   438      proto::EdgesRequest childof_request;
   439      proto::EdgesReply childof_reply;
   440      if (!CopyTicketsFromEdgeSets(defined_edges_reply, &childof_request)) {
   441        return clang::TypoCorrection();
   442      }
   443      childof_request.add_filter(kythe::common::schema::kFactNodeKind);
   444      childof_request.add_kind(kythe::common::schema::kChildOf);
   445      if (!factory_.xrefs_->Edges(childof_request, &childof_reply, &error_text)) {
   446        absl::FPrintF(stderr, "Xrefs error (childof): %s\n", error_text);
   447        return clang::TypoCorrection();
   448      }
   449      // Add those files to the set of includes to try out.
   450      AddFileNodesToTracker(childof_reply);
   451      return clang::TypoCorrection();
   452    }
   453  
   454    FileTracker* tracker() { return tracker_; }
   455  
   456   private:
   457    /// The `ActionFactory` orchestrating this multipass run.
   458    ActionFactory& factory_;
   459  
   460    /// The `FileTracker` keeping track of the file being processed.
   461    FileTracker* tracker_ = nullptr;
   462  };
   463  
   464  void PreprocessorHooks::FileChanged(clang::SourceLocation loc,
   465                                      clang::PPCallbacks::FileChangeReason reason,
   466                                      clang::SrcMgr::CharacteristicKind file_type,
   467                                      clang::FileID prev_fid) {
   468    if (!enclosing_pass_) {
   469      return;
   470    }
   471    if (reason == clang::PPCallbacks::EnterFile) {
   472      clang::SourceManager* source_manager =
   473          &enclosing_pass_->getCompilerInstance().getSourceManager();
   474      clang::FileID loc_id = source_manager->getFileID(loc);
   475      if (const clang::OptionalFileEntryRef file_entry =
   476              source_manager->getFileEntryRefForID(loc_id)) {
   477        if (file_entry->getName() == enclosing_pass_->tracker()->filename()) {
   478          enclosing_pass_->tracker()->set_file_begin(loc);
   479          const auto buffer =
   480              source_manager->getMemoryBufferForFileOrNone(*file_entry);
   481          if (buffer) {
   482            enclosing_pass_->tracker()->SetInitialContent(buffer->getBuffer());
   483          }
   484          tracked_file_ = &file_entry->getFileEntry();
   485        }
   486      }
   487    }
   488  }
   489  
   490  void PreprocessorHooks::InclusionDirective(
   491      clang::SourceLocation hash_location, const clang::Token& include_token,
   492      llvm::StringRef file_name, bool is_angled,
   493      clang::CharSourceRange file_name_range,
   494      clang::OptionalFileEntryRef include_file, llvm::StringRef search_path,
   495      llvm::StringRef relative_path, const clang::Module* imported,
   496      bool is_module_imported, clang::SrcMgr::CharacteristicKind FileType) {
   497    if (!enclosing_pass_ || !enclosing_pass_->tracker()) {
   498      return;
   499    }
   500    clang::SourceManager* source_manager =
   501        &enclosing_pass_->getCompilerInstance().getSourceManager();
   502    auto id_position = source_manager->getDecomposedExpansionLoc(hash_location);
   503    const auto* source_file =
   504        source_manager->getFileEntryForID(id_position.first);
   505    if (source_file == nullptr || !include_file) {
   506      return;
   507    }
   508    if (tracked_file_ == source_file) {
   509      enclosing_pass_->tracker()->NextInclude(
   510          source_manager, include_file->getName(), file_name, is_angled,
   511          hash_location, file_name_range.getEnd());
   512    }
   513  }
   514  
   515  ActionFactory::ActionFactory(std::unique_ptr<XrefsClient> xrefs,
   516                               size_t iterations)
   517      : xrefs_(std::move(xrefs)), iterations_(iterations) {
   518    for (const auto* file = builtin_headers_create(); file->name; ++file) {
   519      builtin_headers_.push_back(llvm::MemoryBuffer::getMemBufferCopy(
   520          llvm::StringRef(file->data), file->name));
   521    }
   522  }
   523  
   524  ActionFactory::~ActionFactory() {
   525    for (auto& tracker : file_trackers_) {
   526      delete tracker.second;
   527    }
   528    file_trackers_.clear();
   529  }
   530  
   531  void ActionFactory::RemapFiles(
   532      llvm::StringRef resource_dir,
   533      std::vector<std::pair<std::string, llvm::MemoryBuffer*>>*
   534          remapped_buffers) {
   535    remapped_buffers->clear();
   536    for (FileTrackerMap::iterator I = file_trackers_.begin(),
   537                                  E = file_trackers_.end();
   538         I != E; ++I) {
   539      FileTracker* tracker = I->second;
   540      if (llvm::MemoryBuffer* buffer = tracker->memory_buffer()) {
   541        remapped_buffers->push_back(
   542            std::make_pair(std::string(tracker->filename()), buffer));
   543      }
   544    }
   545    for (const auto& buffer : builtin_headers_) {
   546      llvm::SmallString<1024> out_path = resource_dir;
   547      llvm::sys::path::append(out_path, "include");
   548      llvm::sys::path::append(out_path, buffer->getBufferIdentifier());
   549      remapped_buffers->push_back(std::make_pair(out_path.c_str(), buffer.get()));
   550    }
   551  }
   552  
   553  FileTracker* ActionFactory::GetOrCreateTracker(llvm::StringRef filename) {
   554    FileTrackerMap::iterator i = file_trackers_.find(filename);
   555    if (i == file_trackers_.end()) {
   556      FileTracker* new_tracker = new FileTracker(filename);
   557      file_trackers_[filename] = new_tracker;
   558      return new_tracker;
   559    }
   560    return i->second;
   561  }
   562  
   563  void ActionFactory::BeginNextIteration() {
   564    assert(iterations_ > 0);
   565    --iterations_;
   566  }
   567  
   568  bool ActionFactory::ShouldRunAgain() { return iterations_ > 0; }
   569  
   570  bool ActionFactory::runInvocation(
   571      std::shared_ptr<clang::CompilerInvocation> invocation,
   572      clang::FileManager* files,
   573      std::shared_ptr<clang::PCHContainerOperations> pch_container_ops,
   574      clang::DiagnosticConsumer* diagnostics) {
   575    // ASTUnit::LoadFromCompilerInvocationAction complains about this too, but
   576    // we'll leave in our own assert to document the assumption.
   577    assert(invocation->getFrontendOpts().Inputs.size() == 1);
   578    llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diag_ids(
   579        new clang::DiagnosticIDs());
   580    llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
   581        new clang::DiagnosticsEngine(diag_ids, &invocation->getDiagnosticOpts()));
   582    if (diagnostics) {
   583      diags->setClient(diagnostics, false);
   584    } else {
   585      diagnostics = new clang::TextDiagnosticPrinter(
   586          llvm::errs(), &invocation->getDiagnosticOpts());
   587      diags->setClient(diagnostics, /*ShouldOwnClient*/ true);
   588    }
   589    clang::ASTUnit* ast_unit = nullptr;
   590    // We only consider one full parse on one input file for now, so we only ever
   591    // need one Action.
   592    llvm::IntrusiveRefCntPtr<Action> action(new Action(*this));
   593    do {
   594      BeginNextIteration();
   595      if (!ast_unit) {
   596        ast_unit = clang::ASTUnit::LoadFromCompilerInvocationAction(
   597            invocation, pch_container_ops, diags, action.get(), ast_unit,
   598            /*Persistent*/ false, llvm::StringRef(),
   599            /*OnlyLocalDecls*/ false,
   600            /*CaptureDiagnostics*/ clang::CaptureDiagsKind::All,
   601            /*PrecompilePreamble*/ true,
   602            /*CacheCodeCompletionResults*/ false,
   603            /*UserFilesAreVolatile*/ true,
   604            /*ErrAST*/ nullptr);
   605        // The preprocessor hooks must have configured the FileTracker.
   606        if (action->tracker() == nullptr) {
   607          absl::FPrintF(stderr, "Error: Never entered input file.\n");
   608          return false;
   609        }
   610      } else {
   611        // ASTUnit::Reparse does the following:
   612        //   PreprocessorOptions &PPOpts = Invocation->getPreprocessorOpts();
   613        //   for (const auto &RB : PPOpts.RemappedFileBuffers)
   614        //     delete RB.second;
   615        // It then adds back the buffers that were passed to Reparse.
   616        // Since we don't want our buffers to be deleted, we have to clear out
   617        // the ones ASTUnit might touch, then pass it a new list.
   618        invocation->getPreprocessorOpts().RemappedFileBuffers.clear();
   619        std::vector<std::pair<std::string, llvm::MemoryBuffer*>> buffers;
   620        RemapFiles(invocation->getHeaderSearchOpts().ResourceDir, &buffers);
   621        // Reparse doesn't offer any way to run actions, so we're limited here
   622        // to checking whether our edits were successful (or perhaps to
   623        // driving new edits only from stored diagnostics). If we need to
   624        // start from scratch, we'll have to create a new ASTUnit or re-run the
   625        // invocation entirely. ActionFactory (and FileTracker) are built the
   626        // way they are to permit them to persist beyond SourceManager/FileID
   627        // churn.
   628        ast_unit->Reparse(pch_container_ops, buffers);
   629        clang::SourceLocation old_begin = action->tracker()->file_begin();
   630        clang::FileID old_id = ast_unit->getSourceManager().getFileID(old_begin);
   631        action->tracker()->BeginPass();
   632        // Restore the file begin marker, since we won't get any preprocessor
   633        // events during Reparse. (We can restore other markers if we'd like
   634        // by computing offsets to this marker.)
   635        action->tracker()->set_file_begin(
   636            ast_unit->getSourceManager().getLocForStartOfFile(old_id));
   637      }
   638      // Decide whether we can do anything about the diagnostics.
   639      for (auto d = ast_unit->stored_diag_afterDriver_begin(),
   640                e = ast_unit->stored_diag_end();
   641           d != e; ++d) {
   642        action->tracker()->HandleStoredDiagnostic(*d);
   643      }
   644      clang::Rewriter rewriter(ast_unit->getSourceManager(),
   645                               ast_unit->getLangOpts());
   646      if (action->tracker()->Rewrite(&rewriter)) {
   647        // There are actions we should take.
   648        action->tracker()->CommitRewrite(ast_unit->getSourceManager().getFileID(
   649                                             action->tracker()->file_begin()),
   650                                         &rewriter);
   651      } else if (iterations_ == 0) {
   652        action->tracker()->mark_failed();
   653      }
   654    } while (action->tracker()->state() == FileTracker::State::kBusy &&
   655             ShouldRunAgain());
   656    if (action->tracker()->state() != FileTracker::State::kFailure) {
   657      const auto buffer = action->tracker()->backing_store();
   658      if (!buffer.empty()) {
   659        absl::PrintF("%s", buffer.str());
   660      }
   661    }
   662    return action->tracker()->state() == FileTracker::State::kSuccess;
   663  }
   664  
   665  }  // namespace fyi
   666  }  // namespace kythe