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