| /* |
| * Copyright 2015 The Kythe Authors. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "kythe/cxx/tools/fyi/fyi.h" |
| |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "clang/Frontend/ASTUnit.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Frontend/TextDiagnosticPrinter.h" |
| #include "clang/Lex/Preprocessor.h" |
| #include "clang/Parse/ParseAST.h" |
| #include "clang/Rewrite/Core/Rewriter.h" |
| #include "clang/Sema/ExternalSemaSource.h" |
| #include "clang/Sema/Sema.h" |
| #include "kythe/cxx/common/kythe_uri.h" |
| #include "kythe/cxx/common/schema/edges.h" |
| #include "kythe/cxx/common/schema/facts.h" |
| #include "kythe/cxx/indexer/cxx/proto_conversions.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/Timer.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include "third_party/llvm/src/clang_builtin_headers.h" |
| |
| namespace kythe { |
| namespace fyi { |
| |
| /// \brief Tracks changes and edits to a single file identified by its full |
| /// path. |
| /// |
| /// Holds a `llvm::MemoryBuffer` with the results of the most recent edits |
| /// if edits have been made. |
| /// |
| /// This object is first created when the compiler enters a new main source |
| /// file. Before each new compile or reparse pass, the outer loop should call |
| /// ::BeginPass(). FileTracker is then notified of various events involving the |
| /// file being processed. If the FileTracker enters a state that is not kBusy, |
| /// it can make no further progress. Otherwise, when the compile completes, it |
| /// is expected that the outer loop will attempt a call to ::Rewrite() with a |
| /// fresh Rewriter instance. If that succeeds, then ::CommitRewrite will update |
| /// internal buffers for subsequent passes. |
| class FileTracker { |
| public: |
| explicit FileTracker(llvm::StringRef filename) : filename_(filename) {} |
| |
| /// \brief Returns the current rewritten file (or null, if rewriting hasn't |
| /// happend). |
| /// |
| /// The object shares its lifetime with this FileTracker. |
| llvm::MemoryBuffer* memory_buffer() { return memory_buffer_.get(); } |
| |
| llvm::StringRef filename() { return filename_; } |
| |
| const llvm::StringRef backing_store() { |
| assert(active_buffer_ < 2); |
| if (memory_buffer_backing_store_[active_buffer_].empty()) { |
| return ""; |
| } |
| auto* store = &memory_buffer_backing_store_[active_buffer_]; |
| const char* start = store->data(); |
| // Why the - 1?: MemoryBufferBackingStore ends with a NUL terminator. |
| return llvm::StringRef(start, store->size() - 1); |
| } |
| |
| /// \param Start a new pass involving this `FileTracker` |
| void BeginPass() { |
| file_begin_ = clang::SourceLocation(); |
| pass_had_errors_ = false; |
| } |
| |
| /// Called for each include file we discover is in the file during a major |
| /// pass. |
| /// Will catch includes that we've added in earlier major passes as well. |
| /// \param source_manager the active SourceManager |
| /// \param canonical_path the canonical path to the include file |
| /// \param uttered_path the path as it appeared in the program |
| /// \param is_angled whether angle brackets were used |
| /// \param hash_location the source location of the include's \# |
| /// \param end_location the source location following the include |
| void NextInclude(clang::SourceManager* source_manager, |
| llvm::StringRef canonical_path, llvm::StringRef uttered_path, |
| bool IsAngled, clang::SourceLocation hash_location, |
| clang::SourceLocation end_location) { |
| unsigned offset = source_manager->getFileOffset(end_location); |
| if (offset > last_include_offset_) { |
| last_include_offset_ = offset; |
| } |
| } |
| |
| /// \brief Rewrite the associated source file with our tentative suggestions. |
| /// \param rewriter a valid Rewriter. |
| /// \return true if changes will be made, false otherwise. |
| bool Rewrite(clang::Rewriter* rewriter) { |
| if (state_ != State::kBusy) { |
| return false; |
| } |
| if (!pass_had_errors_) { |
| state_ = State::kSuccess; |
| return false; |
| } |
| if (!untried_.empty()) { |
| auto to_try = *untried_.begin(); |
| untried_.erase(untried_.begin()); |
| tried_.insert(to_try); |
| rewriter->InsertTextAfter( |
| file_begin_.getLocWithOffset(last_include_offset_), |
| "\n#include \"" + to_try + "\"\n"); |
| return true; |
| } |
| // We have nothing to do, so abort. |
| state_ = State::kFailure; |
| return false; |
| } |
| |
| /// \brief Rewrite the old file into a new file, discarding any previously |
| /// allocated buffers. |
| /// \param file_id the current ID of the file we are rewriting |
| /// \param rewriter a valid Rewriter. |
| void CommitRewrite(clang::FileID file_id, clang::Rewriter* rewriter) { |
| assert(active_buffer_ < 2); |
| can_undo_ = true; |
| active_buffer_ = 1 - active_buffer_; |
| auto* store = &memory_buffer_backing_store_[active_buffer_]; |
| const clang::RewriteBuffer* buffer = rewriter->getRewriteBufferFor(file_id); |
| store->clear(); |
| llvm::raw_svector_ostream buffer_stream(*store); |
| buffer->write(buffer_stream); |
| // Required null terminator. |
| store->push_back(0); |
| const char* start = store->data(); |
| llvm::StringRef data(start, store->size() - 1); |
| memory_buffer_ = llvm::MemoryBuffer::getMemBuffer(data); |
| } |
| |
| /// \brief Analysis state, maintained across passes. |
| enum class State { |
| kBusy, ///< We are trying to repair this file. |
| kSuccess, ///< We have repaired this file (or there is nothing we can do). |
| kFailure ///< We are no longer trying to repair this file. |
| }; |
| |
| /// \brief Gets the state (busy, OK, or bad) of this FileTracker. |
| State state() const { return state_; } |
| |
| /// \brief Marks that this FileTracker cannot be repaired. |
| void mark_failed() { state_ = State::kFailure; } |
| |
| /// \brief Gets the location at the very top of the file (in this pass). |
| clang::SourceLocation file_begin() const { return file_begin_; } |
| |
| /// \brief Sets the location at the very top of the file (in this pass). |
| void set_file_begin(clang::SourceLocation location) { |
| file_begin_ = location; |
| } |
| |
| /// \brief Decode and possibly take action on a diagnostic received during |
| /// a compilation (sub)pass. |
| /// \param diagnostic The diagnostic to handle. |
| void HandleStoredDiagnostic(clang::StoredDiagnostic& diagnostic) { |
| pass_had_errors_ = true; |
| } |
| |
| /// \brief Add an include to the set of includes to try. |
| /// \param include_path The include path to try (as a quoted include). |
| void TryInclude(const std::string& include_path) { |
| if (!tried_.count(include_path)) { |
| untried_.insert(include_path); |
| } |
| } |
| |
| /// \brief Record the initial state of the file before rewriting it. |
| /// \param content The content of the file. |
| void SetInitialContent(llvm::StringRef content) { |
| if (!saw_initial_state_) { |
| active_buffer_ = 0; |
| memory_buffer_backing_store_[0].clear(); |
| memory_buffer_backing_store_[0].append(content.begin(), content.end()); |
| memory_buffer_backing_store_[0].push_back(0); |
| memory_buffer_ = llvm::MemoryBuffer::getMemBuffer(backing_store()); |
| can_undo_ = false; |
| saw_initial_state_ = true; |
| } |
| } |
| |
| private: |
| friend class Action; |
| |
| /// Try to undo the previous change to the backing store. |
| bool Undo() { |
| assert(active_buffer_ < 2); |
| if (can_undo_) { |
| active_buffer_ = 1 - active_buffer_; |
| memory_buffer_ = llvm::MemoryBuffer::getMemBuffer(backing_store()); |
| can_undo_ = false; |
| return true; |
| } |
| return false; |
| } |
| |
| /// The absolute path to the file this FileTracker tracks. Used as a key |
| /// to connect between passes. |
| std::string filename_; |
| |
| /// The location of the beginning of the tracked file. This changes after |
| /// each pass. |
| clang::SourceLocation file_begin_; |
| |
| /// The offset of the last include in the original source file. This will |
| /// be used as the insertion point for new include directives. |
| unsigned last_include_offset_ = 0; |
| |
| /// If this file has been modified, points to a MemoryBuffer containing |
| /// the full text of the modified file. |
| std::unique_ptr<llvm::MemoryBuffer> memory_buffer_ = nullptr; |
| |
| /// Data backing the MemoryBuffer. This is double-buffered, allowing for one |
| /// step of undo. `active_buffer_` selects which buffer we should read from. |
| llvm::SmallVector<char, 128> memory_buffer_backing_store_[2]; |
| |
| /// Which backing store is currently active and which is the backup. |
| /// Always < 2. |
| size_t active_buffer_ = 0; |
| |
| /// Can we undo the previous move? |
| bool can_undo_ = false; |
| |
| /// Have we ever seen the initial state of the file? |
| bool saw_initial_state_ = false; |
| |
| /// The current of this FileTracker independent of pass. |
| State state_ = State::kBusy; |
| |
| /// True if the last subpass had (recoverable) errors. |
| bool pass_had_errors_ = false; |
| |
| /// Includes we've already tried. |
| std::set<std::string> tried_; |
| |
| /// Includes we have left to try. |
| std::set<std::string> untried_; |
| }; |
| |
| /// \brief During non-reparse passes, PreprocessorHooks listens for events |
| /// indicating the files being analyzed and their preprocessor directives. |
| class PreprocessorHooks : public clang::PPCallbacks { |
| public: |
| /// \param enclosing_pass The `Action` controlling this pass. Not owned. |
| explicit PreprocessorHooks(Action* enclosing_pass) |
| : enclosing_pass_(enclosing_pass), tracked_file_(nullptr) {} |
| |
| /// \copydoc PPCallbacks::FileChanged |
| /// |
| /// Finds the `FileEntry` and starting `SourceLocation` for each tracked |
| /// file on every pass. |
| void FileChanged(clang::SourceLocation loc, |
| clang::PPCallbacks::FileChangeReason reason, |
| clang::SrcMgr::CharacteristicKind file_type, |
| clang::FileID prev_fid) override; |
| |
| /// \copydoc PPCallbacks::InclusionDirective |
| /// |
| /// When \p SourceFile is the file being tracked by the enclosing pass, |
| /// records details about each inclusion directive encountered (such as |
| /// the name of the included file, the location of the directive, and so on). |
| void InclusionDirective(clang::SourceLocation hash_location, |
| const clang::Token& include_token, |
| llvm::StringRef file_name, bool is_angled, |
| clang::CharSourceRange file_name_range, |
| const clang::FileEntry* include_file, |
| llvm::StringRef search_path, |
| llvm::StringRef relative_path, |
| const clang::Module* imported, |
| clang::SrcMgr::CharacteristicKind FileType) override; |
| |
| private: |
| friend class Action; |
| |
| /// The current `Action`. Not owned. |
| Action* enclosing_pass_; |
| |
| /// The `FileEntry` corresponding to the tracker in `enclosing_pass_`. |
| /// Not owned. |
| const clang::FileEntry* tracked_file_; |
| }; |
| |
| /// \brief Manages a full parse and any subsequent reparses for a single file. |
| class Action : public clang::ASTFrontendAction, |
| public clang::ExternalSemaSource { |
| public: |
| explicit Action(ActionFactory& factory) : factory_(factory) {} |
| |
| /// \copydoc ASTFrontendAction::BeginInvocation |
| bool BeginInvocation(clang::CompilerInstance& CI) override { |
| auto* pp_opts = &CI.getPreprocessorOpts(); |
| pp_opts->RetainRemappedFileBuffers = true; |
| pp_opts->AllowPCHWithCompilerErrors = true; |
| factory_.RemapFiles(CI.getHeaderSearchOpts().ResourceDir, |
| &pp_opts->RemappedFileBuffers); |
| return true; |
| } |
| |
| /// \copydoc ASTFrontendAction::CreateASTConsumer |
| std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( |
| clang::CompilerInstance& compiler, llvm::StringRef in_file) override { |
| tracker_ = factory_.GetOrCreateTracker(in_file); |
| // Don't bother starting a new pass if the tracker is finished. |
| if (tracker_->state() == FileTracker::State::kBusy) { |
| tracker_->BeginPass(); |
| compiler.getPreprocessor().addPPCallbacks( |
| llvm::make_unique<PreprocessorHooks>(this)); |
| } |
| return llvm::make_unique<clang::ASTConsumer>(); |
| } |
| |
| /// \copydoc ASTFrontendAction::ExecuteAction |
| void ExecuteAction() override { |
| // We have to reproduce what ASTFrontendAction::ExecuteAction does, since |
| // we have to attach ourselves as an ExternalSemaSource to Sema before |
| // calling ParseAST. |
| |
| // Do nothing if we've already given up on or finished this file. |
| if (tracker_->state() != FileTracker::State::kBusy) { |
| return; |
| } |
| |
| clang::CompilerInstance* compiler = &getCompilerInstance(); |
| assert(!compiler->hasSema() && "CI already has Sema"); |
| |
| if (hasCodeCompletionSupport() && |
| !compiler->getFrontendOpts().CodeCompletionAt.FileName.empty()) |
| compiler->createCodeCompletionConsumer(); |
| |
| clang::CodeCompleteConsumer* completion_consumer = nullptr; |
| if (compiler->hasCodeCompletionConsumer()) |
| completion_consumer = &compiler->getCodeCompletionConsumer(); |
| |
| compiler->createSema(getTranslationUnitKind(), completion_consumer); |
| compiler->getSema().addExternalSource(this); |
| |
| clang::ParseAST(compiler->getSema(), compiler->getFrontendOpts().ShowStats, |
| compiler->getFrontendOpts().SkipFunctionBodies); |
| } |
| |
| /// \brief Copies the tickets from `reply.edge_set` to `request.ticket`. |
| /// \return false if no tickets were copied |
| template <typename Reply, typename Request> |
| bool CopyTicketsFromEdgeSets(const Reply& reply, Request* request) { |
| for (const auto& edge_set : reply.edge_sets()) { |
| for (const auto& group : edge_set.second.groups()) { |
| for (const auto& edge : group.second.edge()) { |
| request->add_ticket(edge.target_ticket()); |
| } |
| } |
| } |
| return request->ticket_size() != 0; |
| } |
| |
| /// \brief Adds the paths of all /kythe/node/file nodes from `reply.node` to |
| /// this Action's `FileTracker`'s include list. |
| template <typename Reply> |
| void AddFileNodesToTracker(const Reply& reply) { |
| for (const auto& parent : reply.nodes()) { |
| bool is_file = false; |
| for (const auto& fact : parent.second.facts()) { |
| if (fact.first == kythe::common::schema::kFactNodeKind) { |
| is_file = (fact.second == "/kythe/node/file"); |
| break; |
| } |
| } |
| if (!is_file) { |
| continue; |
| } |
| auto maybe_uri = URI::FromString(parent.first); |
| if (maybe_uri.first) { |
| tracker_->TryInclude(maybe_uri.second.v_name().path()); |
| } |
| } |
| } |
| |
| /// \copydoc ExternalSemaSource::CorrectTypo |
| clang::TypoCorrection CorrectTypo( |
| const clang::DeclarationNameInfo& typo, int lookup_kind, |
| clang::Scope* scope, clang::CXXScopeSpec* scope_spec, |
| clang::CorrectionCandidateCallback& callback, |
| clang::DeclContext* member_context, bool entering_context, |
| const clang::ObjCObjectPointerType* objc_ptr_type) override { |
| // Conservatively assume that something went wrong if we had to invoke |
| // typo correction. |
| tracker_->pass_had_errors_ = true; |
| // Look for any name nodes that could help. |
| proto::VName name_node; |
| name_node.set_signature(typo.getAsString() + "#n"); |
| name_node.set_language("c++"); |
| proto::EdgesRequest named_edges_request; |
| auto name_uri = URI(name_node).ToString(); |
| named_edges_request.add_ticket(name_uri); |
| // We've found at least one interesting name in the graph. Now we need |
| // to figure out which nodes those names are bound to. |
| named_edges_request.add_kind( |
| absl::StrCat("%", kythe::common::schema::kNamed)); |
| proto::EdgesReply named_edges_reply; |
| std::string error_text; |
| if (!factory_.xrefs_->Edges(named_edges_request, &named_edges_reply, |
| &error_text)) { |
| absl::FPrintF(stderr, "Xrefs error (named): %s\n", error_text); |
| return clang::TypoCorrection(); |
| } |
| // Get information about the places where those nodes were defined. |
| proto::EdgesRequest defined_edges_request; |
| proto::EdgesReply defined_edges_reply; |
| if (!CopyTicketsFromEdgeSets(named_edges_reply, &defined_edges_request)) { |
| return clang::TypoCorrection(); |
| } |
| defined_edges_request.add_kind( |
| ToStringRef(absl::StrCat("%", kythe::common::schema::kDefines))); |
| if (!factory_.xrefs_->Edges(defined_edges_request, &defined_edges_reply, |
| &error_text)) { |
| absl::FPrintF(stderr, "Xrefs error (defines): %s\n", error_text); |
| return clang::TypoCorrection(); |
| } |
| // Finally, figure out whether we can make those definition sites visible |
| // to the site of the typo by adding an include. |
| proto::EdgesRequest childof_request; |
| proto::EdgesReply childof_reply; |
| if (!CopyTicketsFromEdgeSets(defined_edges_reply, &childof_request)) { |
| return clang::TypoCorrection(); |
| } |
| childof_request.add_filter(kythe::common::schema::kFactNodeKind); |
| childof_request.add_kind(kythe::common::schema::kChildOf); |
| if (!factory_.xrefs_->Edges(childof_request, &childof_reply, &error_text)) { |
| absl::FPrintF(stderr, "Xrefs error (childof): %s\n", error_text); |
| return clang::TypoCorrection(); |
| } |
| // Add those files to the set of includes to try out. |
| AddFileNodesToTracker(childof_reply); |
| return clang::TypoCorrection(); |
| } |
| |
| FileTracker* tracker() { return tracker_; } |
| |
| private: |
| /// The `ActionFactory` orchestrating this multipass run. |
| ActionFactory& factory_; |
| |
| /// The `FileTracker` keeping track of the file being processed. |
| FileTracker* tracker_ = nullptr; |
| }; |
| |
| void PreprocessorHooks::FileChanged(clang::SourceLocation loc, |
| clang::PPCallbacks::FileChangeReason reason, |
| clang::SrcMgr::CharacteristicKind file_type, |
| clang::FileID prev_fid) { |
| if (!enclosing_pass_) { |
| return; |
| } |
| if (reason == clang::PPCallbacks::EnterFile) { |
| clang::SourceManager* source_manager = |
| &enclosing_pass_->getCompilerInstance().getSourceManager(); |
| clang::FileID loc_id = source_manager->getFileID(loc); |
| if (const clang::FileEntry* file_entry = |
| source_manager->getFileEntryForID(loc_id)) { |
| if (file_entry->getName() == enclosing_pass_->tracker()->filename()) { |
| enclosing_pass_->tracker()->set_file_begin(loc); |
| bool valid = true; |
| const auto* buffer = |
| source_manager->getMemoryBufferForFile(file_entry, &valid); |
| if (valid && buffer) { |
| enclosing_pass_->tracker()->SetInitialContent(buffer->getBuffer()); |
| } |
| tracked_file_ = file_entry; |
| } |
| } |
| } |
| } |
| |
| void PreprocessorHooks::InclusionDirective( |
| clang::SourceLocation hash_location, const clang::Token& include_token, |
| llvm::StringRef file_name, bool is_angled, |
| clang::CharSourceRange file_name_range, |
| const clang::FileEntry* include_file, llvm::StringRef search_path, |
| llvm::StringRef relative_path, const clang::Module* imported, |
| clang::SrcMgr::CharacteristicKind FileType) { |
| if (!enclosing_pass_ || !enclosing_pass_->tracker()) { |
| return; |
| } |
| clang::SourceManager* source_manager = |
| &enclosing_pass_->getCompilerInstance().getSourceManager(); |
| auto id_position = source_manager->getDecomposedExpansionLoc(hash_location); |
| const auto* source_file = |
| source_manager->getFileEntryForID(id_position.first); |
| if (source_file == nullptr || include_file == nullptr) { |
| return; |
| } |
| if (tracked_file_ == source_file) { |
| enclosing_pass_->tracker()->NextInclude( |
| source_manager, include_file->getName(), file_name, is_angled, |
| hash_location, file_name_range.getEnd()); |
| } |
| } |
| |
| ActionFactory::ActionFactory(std::unique_ptr<XrefsClient> xrefs, |
| size_t iterations) |
| : xrefs_(std::move(xrefs)), iterations_(iterations) { |
| for (const auto* file = builtin_headers_create(); file->name; ++file) { |
| builtin_headers_.push_back(llvm::MemoryBuffer::getMemBufferCopy( |
| llvm::StringRef(file->data), file->name)); |
| } |
| } |
| |
| ActionFactory::~ActionFactory() { |
| for (auto& tracker : file_trackers_) { |
| delete tracker.second; |
| } |
| file_trackers_.clear(); |
| } |
| |
| void ActionFactory::RemapFiles( |
| llvm::StringRef resource_dir, |
| std::vector<std::pair<std::string, llvm::MemoryBuffer*>>* |
| remapped_buffers) { |
| remapped_buffers->clear(); |
| for (FileTrackerMap::iterator I = file_trackers_.begin(), |
| E = file_trackers_.end(); |
| I != E; ++I) { |
| FileTracker* tracker = I->second; |
| if (llvm::MemoryBuffer* buffer = tracker->memory_buffer()) { |
| remapped_buffers->push_back(std::make_pair(tracker->filename(), buffer)); |
| } |
| } |
| for (const auto& buffer : builtin_headers_) { |
| llvm::SmallString<1024> out_path = resource_dir; |
| llvm::sys::path::append(out_path, "include"); |
| llvm::sys::path::append(out_path, buffer->getBufferIdentifier()); |
| remapped_buffers->push_back(std::make_pair(out_path.c_str(), buffer.get())); |
| } |
| } |
| |
| FileTracker* ActionFactory::GetOrCreateTracker(llvm::StringRef filename) { |
| FileTrackerMap::iterator i = file_trackers_.find(filename); |
| if (i == file_trackers_.end()) { |
| FileTracker* new_tracker = new FileTracker(filename); |
| file_trackers_[filename] = new_tracker; |
| return new_tracker; |
| } |
| return i->second; |
| } |
| |
| void ActionFactory::BeginNextIteration() { |
| assert(iterations_ > 0); |
| --iterations_; |
| } |
| |
| bool ActionFactory::ShouldRunAgain() { return iterations_ > 0; } |
| |
| bool ActionFactory::runInvocation( |
| std::shared_ptr<clang::CompilerInvocation> invocation, |
| clang::FileManager* files, |
| std::shared_ptr<clang::PCHContainerOperations> pch_container_ops, |
| clang::DiagnosticConsumer* diagnostics) { |
| // ASTUnit::LoadFromCompilerInvocationAction complains about this too, but |
| // we'll leave in our own assert to document the assumption. |
| assert(invocation->getFrontendOpts().Inputs.size() == 1); |
| llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diag_ids( |
| new clang::DiagnosticIDs()); |
| llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags( |
| new clang::DiagnosticsEngine(diag_ids, &invocation->getDiagnosticOpts())); |
| if (diagnostics) { |
| diags->setClient(diagnostics, false); |
| } else { |
| diagnostics = new clang::TextDiagnosticPrinter( |
| llvm::errs(), &invocation->getDiagnosticOpts()); |
| diags->setClient(diagnostics, /*ShouldOwnClient*/ true); |
| } |
| clang::ASTUnit* ast_unit = nullptr; |
| // We only consider one full parse on one input file for now, so we only ever |
| // need one Action. |
| auto action = llvm::make_unique<Action>(*this); |
| do { |
| BeginNextIteration(); |
| if (!ast_unit) { |
| ast_unit = clang::ASTUnit::LoadFromCompilerInvocationAction( |
| invocation, pch_container_ops, diags, action.get(), ast_unit, |
| /*Persistent*/ false, llvm::StringRef(), |
| /*OnlyLocalDecls*/ false, |
| /*CaptureDiagnostics*/ true, |
| /*PrecompilePreamble*/ true, |
| /*CacheCodeCompletionResults*/ false, |
| /*IncludeBriefCommentsInCodeCompletion*/ false, |
| /*UserFilesAreVolatile*/ true, |
| /*ErrAST*/ nullptr); |
| // The preprocessor hooks must have configured the FileTracker. |
| if (action->tracker() == nullptr) { |
| absl::FPrintF(stderr, "Error: Never entered input file.\n"); |
| return false; |
| } |
| } else { |
| // ASTUnit::Reparse does the following: |
| // PreprocessorOptions &PPOpts = Invocation->getPreprocessorOpts(); |
| // for (const auto &RB : PPOpts.RemappedFileBuffers) |
| // delete RB.second; |
| // It then adds back the buffers that were passed to Reparse. |
| // Since we don't want our buffers to be deleted, we have to clear out |
| // the ones ASTUnit might touch, then pass it a new list. |
| invocation->getPreprocessorOpts().RemappedFileBuffers.clear(); |
| std::vector<std::pair<std::string, llvm::MemoryBuffer*>> buffers; |
| RemapFiles(invocation->getHeaderSearchOpts().ResourceDir, &buffers); |
| // Reparse doesn't offer any way to run actions, so we're limited here |
| // to checking whether our edits were successful (or perhaps to |
| // driving new edits only from stored diagnostics). If we need to |
| // start from scratch, we'll have to create a new ASTUnit or re-run the |
| // invocation entirely. ActionFactory (and FileTracker) are built the |
| // way they are to permit them to persist beyond SourceManager/FileID |
| // churn. |
| ast_unit->Reparse(pch_container_ops, buffers); |
| clang::SourceLocation old_begin = action->tracker()->file_begin(); |
| clang::FileID old_id = ast_unit->getSourceManager().getFileID(old_begin); |
| action->tracker()->BeginPass(); |
| // Restore the file begin marker, since we won't get any preprocessor |
| // events during Reparse. (We can restore other markers if we'd like |
| // by computing offsets to this marker.) |
| action->tracker()->set_file_begin( |
| ast_unit->getSourceManager().getLocForStartOfFile(old_id)); |
| } |
| // Decide whether we can do anything about the diagnostics. |
| for (auto d = ast_unit->stored_diag_afterDriver_begin(), |
| e = ast_unit->stored_diag_end(); |
| d != e; ++d) { |
| action->tracker()->HandleStoredDiagnostic(*d); |
| } |
| clang::Rewriter rewriter(ast_unit->getSourceManager(), |
| ast_unit->getLangOpts()); |
| if (action->tracker()->Rewrite(&rewriter)) { |
| // There are actions we should take. |
| action->tracker()->CommitRewrite(ast_unit->getSourceManager().getFileID( |
| action->tracker()->file_begin()), |
| &rewriter); |
| } else if (iterations_ == 0) { |
| action->tracker()->mark_failed(); |
| } |
| } while (action->tracker()->state() == FileTracker::State::kBusy && |
| ShouldRunAgain()); |
| if (action->tracker()->state() != FileTracker::State::kFailure) { |
| const auto buffer = action->tracker()->backing_store(); |
| if (!buffer.empty()) { |
| absl::PrintF("%s", buffer.str()); |
| } |
| } |
| return action->tracker()->state() == FileTracker::State::kSuccess; |
| } |
| |
| } // namespace fyi |
| } // namespace kythe |