|  | //===-- IncludeFixer.cpp - Include inserter based on sema callbacks -------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "IncludeFixer.h" | 
|  | #include "clang/Format/Format.h" | 
|  | #include "clang/Frontend/CompilerInstance.h" | 
|  | #include "clang/Lex/HeaderSearch.h" | 
|  | #include "clang/Lex/Preprocessor.h" | 
|  | #include "clang/Parse/ParseAST.h" | 
|  | #include "clang/Sema/Sema.h" | 
|  | #include "llvm/Support/Debug.h" | 
|  | #include "llvm/Support/raw_ostream.h" | 
|  |  | 
|  | #define DEBUG_TYPE "clang-include-fixer" | 
|  |  | 
|  | using namespace clang; | 
|  |  | 
|  | namespace clang { | 
|  | namespace include_fixer { | 
|  | namespace { | 
|  | /// Manages the parse, gathers include suggestions. | 
|  | class Action : public clang::ASTFrontendAction { | 
|  | public: | 
|  | explicit Action(SymbolIndexManager &SymbolIndexMgr, bool MinimizeIncludePaths) | 
|  | : SemaSource(SymbolIndexMgr, MinimizeIncludePaths, | 
|  | /*GenerateDiagnostics=*/false) {} | 
|  |  | 
|  | std::unique_ptr<clang::ASTConsumer> | 
|  | CreateASTConsumer(clang::CompilerInstance &Compiler, | 
|  | StringRef InFile) override { | 
|  | SemaSource.setFilePath(InFile); | 
|  | return std::make_unique<clang::ASTConsumer>(); | 
|  | } | 
|  |  | 
|  | void ExecuteAction() override { | 
|  | clang::CompilerInstance *Compiler = &getCompilerInstance(); | 
|  | assert(!Compiler->hasSema() && "CI already has Sema"); | 
|  |  | 
|  | // Set up our hooks into sema and parse the AST. | 
|  | if (hasCodeCompletionSupport() && | 
|  | !Compiler->getFrontendOpts().CodeCompletionAt.FileName.empty()) | 
|  | Compiler->createCodeCompletionConsumer(); | 
|  |  | 
|  | clang::CodeCompleteConsumer *CompletionConsumer = nullptr; | 
|  | if (Compiler->hasCodeCompletionConsumer()) | 
|  | CompletionConsumer = &Compiler->getCodeCompletionConsumer(); | 
|  |  | 
|  | Compiler->createSema(getTranslationUnitKind(), CompletionConsumer); | 
|  | SemaSource.setCompilerInstance(Compiler); | 
|  | Compiler->getSema().addExternalSource(&SemaSource); | 
|  |  | 
|  | clang::ParseAST(Compiler->getSema(), Compiler->getFrontendOpts().ShowStats, | 
|  | Compiler->getFrontendOpts().SkipFunctionBodies); | 
|  | } | 
|  |  | 
|  | IncludeFixerContext | 
|  | getIncludeFixerContext(const clang::SourceManager &SourceManager, | 
|  | clang::HeaderSearch &HeaderSearch) const { | 
|  | return SemaSource.getIncludeFixerContext(SourceManager, HeaderSearch, | 
|  | SemaSource.getMatchedSymbols()); | 
|  | } | 
|  |  | 
|  | private: | 
|  | IncludeFixerSemaSource SemaSource; | 
|  | }; | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | IncludeFixerActionFactory::IncludeFixerActionFactory( | 
|  | SymbolIndexManager &SymbolIndexMgr, | 
|  | std::vector<IncludeFixerContext> &Contexts, StringRef StyleName, | 
|  | bool MinimizeIncludePaths) | 
|  | : SymbolIndexMgr(SymbolIndexMgr), Contexts(Contexts), | 
|  | MinimizeIncludePaths(MinimizeIncludePaths) {} | 
|  |  | 
|  | IncludeFixerActionFactory::~IncludeFixerActionFactory() = default; | 
|  |  | 
|  | bool IncludeFixerActionFactory::runInvocation( | 
|  | std::shared_ptr<clang::CompilerInvocation> Invocation, | 
|  | clang::FileManager *Files, | 
|  | std::shared_ptr<clang::PCHContainerOperations> PCHContainerOps, | 
|  | clang::DiagnosticConsumer *Diagnostics) { | 
|  | assert(Invocation->getFrontendOpts().Inputs.size() == 1); | 
|  |  | 
|  | // Set up Clang. | 
|  | clang::CompilerInstance Compiler(PCHContainerOps); | 
|  | Compiler.setInvocation(std::move(Invocation)); | 
|  | Compiler.setFileManager(Files); | 
|  |  | 
|  | // Create the compiler's actual diagnostics engine. We want to drop all | 
|  | // diagnostics here. | 
|  | Compiler.createDiagnostics(new clang::IgnoringDiagConsumer, | 
|  | /*ShouldOwnClient=*/true); | 
|  | Compiler.createSourceManager(*Files); | 
|  |  | 
|  | // We abort on fatal errors so don't let a large number of errors become | 
|  | // fatal. A missing #include can cause thousands of errors. | 
|  | Compiler.getDiagnostics().setErrorLimit(0); | 
|  |  | 
|  | // Run the parser, gather missing includes. | 
|  | auto ScopedToolAction = | 
|  | std::make_unique<Action>(SymbolIndexMgr, MinimizeIncludePaths); | 
|  | Compiler.ExecuteAction(*ScopedToolAction); | 
|  |  | 
|  | Contexts.push_back(ScopedToolAction->getIncludeFixerContext( | 
|  | Compiler.getSourceManager(), | 
|  | Compiler.getPreprocessor().getHeaderSearchInfo())); | 
|  |  | 
|  | // Technically this should only return true if we're sure that we have a | 
|  | // parseable file. We don't know that though. Only inform users of fatal | 
|  | // errors. | 
|  | return !Compiler.getDiagnostics().hasFatalErrorOccurred(); | 
|  | } | 
|  |  | 
|  | static bool addDiagnosticsForContext(TypoCorrection &Correction, | 
|  | const IncludeFixerContext &Context, | 
|  | StringRef Code, SourceLocation StartOfFile, | 
|  | ASTContext &Ctx) { | 
|  | auto Reps = createIncludeFixerReplacements( | 
|  | Code, Context, format::getLLVMStyle(), /*AddQualifiers=*/false); | 
|  | if (!Reps || Reps->size() != 1) | 
|  | return false; | 
|  |  | 
|  | unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID( | 
|  | DiagnosticsEngine::Note, "Add '#include %0' to provide the missing " | 
|  | "declaration [clang-include-fixer]"); | 
|  |  | 
|  | // FIXME: Currently we only generate a diagnostic for the first header. Give | 
|  | // the user choices. | 
|  | const tooling::Replacement &Placed = *Reps->begin(); | 
|  |  | 
|  | auto Begin = StartOfFile.getLocWithOffset(Placed.getOffset()); | 
|  | auto End = Begin.getLocWithOffset(std::max(0, (int)Placed.getLength() - 1)); | 
|  | PartialDiagnostic PD(DiagID, Ctx.getDiagAllocator()); | 
|  | PD << Context.getHeaderInfos().front().Header | 
|  | << FixItHint::CreateReplacement(CharSourceRange::getCharRange(Begin, End), | 
|  | Placed.getReplacementText()); | 
|  | Correction.addExtraDiagnostic(std::move(PD)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /// Callback for incomplete types. If we encounter a forward declaration we | 
|  | /// have the fully qualified name ready. Just query that. | 
|  | bool IncludeFixerSemaSource::MaybeDiagnoseMissingCompleteType( | 
|  | clang::SourceLocation Loc, clang::QualType T) { | 
|  | // Ignore spurious callbacks from SFINAE contexts. | 
|  | if (CI->getSema().isSFINAEContext()) | 
|  | return false; | 
|  |  | 
|  | clang::ASTContext &context = CI->getASTContext(); | 
|  | std::string QueryString = QualType(T->getUnqualifiedDesugaredType(), 0) | 
|  | .getAsString(context.getPrintingPolicy()); | 
|  | LLVM_DEBUG(llvm::dbgs() << "Query missing complete type '" << QueryString | 
|  | << "'"); | 
|  | // Pass an empty range here since we don't add qualifier in this case. | 
|  | std::vector<find_all_symbols::SymbolInfo> MatchedSymbols = | 
|  | query(QueryString, "", tooling::Range()); | 
|  |  | 
|  | if (!MatchedSymbols.empty() && GenerateDiagnostics) { | 
|  | TypoCorrection Correction; | 
|  | FileID FID = CI->getSourceManager().getFileID(Loc); | 
|  | StringRef Code = CI->getSourceManager().getBufferData(FID); | 
|  | SourceLocation StartOfFile = | 
|  | CI->getSourceManager().getLocForStartOfFile(FID); | 
|  | addDiagnosticsForContext( | 
|  | Correction, | 
|  | getIncludeFixerContext(CI->getSourceManager(), | 
|  | CI->getPreprocessor().getHeaderSearchInfo(), | 
|  | MatchedSymbols), | 
|  | Code, StartOfFile, CI->getASTContext()); | 
|  | for (const PartialDiagnostic &PD : Correction.getExtraDiagnostics()) | 
|  | CI->getSema().Diag(Loc, PD); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /// Callback for unknown identifiers. Try to piece together as much | 
|  | /// qualification as we can get and do a query. | 
|  | clang::TypoCorrection IncludeFixerSemaSource::CorrectTypo( | 
|  | const DeclarationNameInfo &Typo, int LookupKind, Scope *S, CXXScopeSpec *SS, | 
|  | CorrectionCandidateCallback &CCC, DeclContext *MemberContext, | 
|  | bool EnteringContext, const ObjCObjectPointerType *OPT) { | 
|  | // Ignore spurious callbacks from SFINAE contexts. | 
|  | if (CI->getSema().isSFINAEContext()) | 
|  | return clang::TypoCorrection(); | 
|  |  | 
|  | // We currently ignore the unidentified symbol which is not from the | 
|  | // main file. | 
|  | // | 
|  | // However, this is not always true due to templates in a non-self contained | 
|  | // header, consider the case: | 
|  | // | 
|  | //   // header.h | 
|  | //   template <typename T> | 
|  | //   class Foo { | 
|  | //     T t; | 
|  | //   }; | 
|  | // | 
|  | //   // test.cc | 
|  | //   // We need to add <bar.h> in test.cc instead of header.h. | 
|  | //   class Bar; | 
|  | //   Foo<Bar> foo; | 
|  | // | 
|  | // FIXME: Add the missing header to the header file where the symbol comes | 
|  | // from. | 
|  | if (!CI->getSourceManager().isWrittenInMainFile(Typo.getLoc())) | 
|  | return clang::TypoCorrection(); | 
|  |  | 
|  | std::string TypoScopeString; | 
|  | if (S) { | 
|  | // FIXME: Currently we only use namespace contexts. Use other context | 
|  | // types for query. | 
|  | for (const auto *Context = S->getEntity(); Context; | 
|  | Context = Context->getParent()) { | 
|  | if (const auto *ND = dyn_cast<NamespaceDecl>(Context)) { | 
|  | if (!ND->getName().empty()) | 
|  | TypoScopeString = ND->getNameAsString() + "::" + TypoScopeString; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | auto ExtendNestedNameSpecifier = [this](CharSourceRange Range) { | 
|  | StringRef Source = | 
|  | Lexer::getSourceText(Range, CI->getSourceManager(), CI->getLangOpts()); | 
|  |  | 
|  | // Skip forward until we find a character that's neither identifier nor | 
|  | // colon. This is a bit of a hack around the fact that we will only get a | 
|  | // single callback for a long nested name if a part of the beginning is | 
|  | // unknown. For example: | 
|  | // | 
|  | // llvm::sys::path::parent_path(...) | 
|  | // ^~~~  ^~~ | 
|  | //    known | 
|  | //            ^~~~ | 
|  | //      unknown, last callback | 
|  | //                  ^~~~~~~~~~~ | 
|  | //                  no callback | 
|  | // | 
|  | // With the extension we get the full nested name specifier including | 
|  | // parent_path. | 
|  | // FIXME: Don't rely on source text. | 
|  | const char *End = Source.end(); | 
|  | while (isIdentifierBody(*End) || *End == ':') | 
|  | ++End; | 
|  |  | 
|  | return std::string(Source.begin(), End); | 
|  | }; | 
|  |  | 
|  | /// If we have a scope specification, use that to get more precise results. | 
|  | std::string QueryString; | 
|  | tooling::Range SymbolRange; | 
|  | const auto &SM = CI->getSourceManager(); | 
|  | auto CreateToolingRange = [&QueryString, &SM](SourceLocation BeginLoc) { | 
|  | return tooling::Range(SM.getDecomposedLoc(BeginLoc).second, | 
|  | QueryString.size()); | 
|  | }; | 
|  | if (SS && SS->getRange().isValid()) { | 
|  | auto Range = CharSourceRange::getTokenRange(SS->getRange().getBegin(), | 
|  | Typo.getLoc()); | 
|  |  | 
|  | QueryString = ExtendNestedNameSpecifier(Range); | 
|  | SymbolRange = CreateToolingRange(Range.getBegin()); | 
|  | } else if (Typo.getName().isIdentifier() && !Typo.getLoc().isMacroID()) { | 
|  | auto Range = | 
|  | CharSourceRange::getTokenRange(Typo.getBeginLoc(), Typo.getEndLoc()); | 
|  |  | 
|  | QueryString = ExtendNestedNameSpecifier(Range); | 
|  | SymbolRange = CreateToolingRange(Range.getBegin()); | 
|  | } else { | 
|  | QueryString = Typo.getAsString(); | 
|  | SymbolRange = CreateToolingRange(Typo.getLoc()); | 
|  | } | 
|  |  | 
|  | LLVM_DEBUG(llvm::dbgs() << "TypoScopeQualifiers: " << TypoScopeString | 
|  | << "\n"); | 
|  | std::vector<find_all_symbols::SymbolInfo> MatchedSymbols = | 
|  | query(QueryString, TypoScopeString, SymbolRange); | 
|  |  | 
|  | if (!MatchedSymbols.empty() && GenerateDiagnostics) { | 
|  | TypoCorrection Correction(Typo.getName()); | 
|  | Correction.setCorrectionRange(SS, Typo); | 
|  | FileID FID = SM.getFileID(Typo.getLoc()); | 
|  | StringRef Code = SM.getBufferData(FID); | 
|  | SourceLocation StartOfFile = SM.getLocForStartOfFile(FID); | 
|  | if (addDiagnosticsForContext( | 
|  | Correction, getIncludeFixerContext( | 
|  | SM, CI->getPreprocessor().getHeaderSearchInfo(), | 
|  | MatchedSymbols), | 
|  | Code, StartOfFile, CI->getASTContext())) | 
|  | return Correction; | 
|  | } | 
|  | return TypoCorrection(); | 
|  | } | 
|  |  | 
|  | /// Get the minimal include for a given path. | 
|  | std::string IncludeFixerSemaSource::minimizeInclude( | 
|  | StringRef Include, const clang::SourceManager &SourceManager, | 
|  | clang::HeaderSearch &HeaderSearch) const { | 
|  | if (!MinimizeIncludePaths) | 
|  | return std::string(Include); | 
|  |  | 
|  | // Get the FileEntry for the include. | 
|  | StringRef StrippedInclude = Include.trim("\"<>"); | 
|  | auto Entry = SourceManager.getFileManager().getFile(StrippedInclude); | 
|  |  | 
|  | // If the file doesn't exist return the path from the database. | 
|  | // FIXME: This should never happen. | 
|  | if (!Entry) | 
|  | return std::string(Include); | 
|  |  | 
|  | bool IsSystem = false; | 
|  | std::string Suggestion = | 
|  | HeaderSearch.suggestPathToFileForDiagnostics(*Entry, "", &IsSystem); | 
|  |  | 
|  | return IsSystem ? '<' + Suggestion + '>' : '"' + Suggestion + '"'; | 
|  | } | 
|  |  | 
|  | /// Get the include fixer context for the queried symbol. | 
|  | IncludeFixerContext IncludeFixerSemaSource::getIncludeFixerContext( | 
|  | const clang::SourceManager &SourceManager, | 
|  | clang::HeaderSearch &HeaderSearch, | 
|  | ArrayRef<find_all_symbols::SymbolInfo> MatchedSymbols) const { | 
|  | std::vector<find_all_symbols::SymbolInfo> SymbolCandidates; | 
|  | for (const auto &Symbol : MatchedSymbols) { | 
|  | std::string FilePath = Symbol.getFilePath().str(); | 
|  | std::string MinimizedFilePath = minimizeInclude( | 
|  | ((FilePath[0] == '"' || FilePath[0] == '<') ? FilePath | 
|  | : "\"" + FilePath + "\""), | 
|  | SourceManager, HeaderSearch); | 
|  | SymbolCandidates.emplace_back(Symbol.getName(), Symbol.getSymbolKind(), | 
|  | MinimizedFilePath, Symbol.getContexts()); | 
|  | } | 
|  | return IncludeFixerContext(FilePath, QuerySymbolInfos, SymbolCandidates); | 
|  | } | 
|  |  | 
|  | std::vector<find_all_symbols::SymbolInfo> | 
|  | IncludeFixerSemaSource::query(StringRef Query, StringRef ScopedQualifiers, | 
|  | tooling::Range Range) { | 
|  | assert(!Query.empty() && "Empty query!"); | 
|  |  | 
|  | // Save all instances of an unidentified symbol. | 
|  | // | 
|  | // We use conservative behavior for detecting the same unidentified symbol | 
|  | // here. The symbols which have the same ScopedQualifier and RawIdentifier | 
|  | // are considered equal. So that clang-include-fixer avoids false positives, | 
|  | // and always adds missing qualifiers to correct symbols. | 
|  | if (!GenerateDiagnostics && !QuerySymbolInfos.empty()) { | 
|  | if (ScopedQualifiers == QuerySymbolInfos.front().ScopedQualifiers && | 
|  | Query == QuerySymbolInfos.front().RawIdentifier) { | 
|  | QuerySymbolInfos.push_back( | 
|  | {Query.str(), std::string(ScopedQualifiers), Range}); | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | LLVM_DEBUG(llvm::dbgs() << "Looking up '" << Query << "' at "); | 
|  | LLVM_DEBUG(CI->getSourceManager() | 
|  | .getLocForStartOfFile(CI->getSourceManager().getMainFileID()) | 
|  | .getLocWithOffset(Range.getOffset()) | 
|  | .print(llvm::dbgs(), CI->getSourceManager())); | 
|  | LLVM_DEBUG(llvm::dbgs() << " ..."); | 
|  | llvm::StringRef FileName = CI->getSourceManager().getFilename( | 
|  | CI->getSourceManager().getLocForStartOfFile( | 
|  | CI->getSourceManager().getMainFileID())); | 
|  |  | 
|  | QuerySymbolInfos.push_back( | 
|  | {Query.str(), std::string(ScopedQualifiers), Range}); | 
|  |  | 
|  | // Query the symbol based on C++ name Lookup rules. | 
|  | // Firstly, lookup the identifier with scoped namespace contexts; | 
|  | // If that fails, falls back to look up the identifier directly. | 
|  | // | 
|  | // For example: | 
|  | // | 
|  | // namespace a { | 
|  | // b::foo f; | 
|  | // } | 
|  | // | 
|  | // 1. lookup a::b::foo. | 
|  | // 2. lookup b::foo. | 
|  | std::string QueryString = ScopedQualifiers.str() + Query.str(); | 
|  | // It's unsafe to do nested search for the identifier with scoped namespace | 
|  | // context, it might treat the identifier as a nested class of the scoped | 
|  | // namespace. | 
|  | std::vector<find_all_symbols::SymbolInfo> MatchedSymbols = | 
|  | SymbolIndexMgr.search(QueryString, /*IsNestedSearch=*/false, FileName); | 
|  | if (MatchedSymbols.empty()) | 
|  | MatchedSymbols = | 
|  | SymbolIndexMgr.search(Query, /*IsNestedSearch=*/true, FileName); | 
|  | LLVM_DEBUG(llvm::dbgs() << "Having found " << MatchedSymbols.size() | 
|  | << " symbols\n"); | 
|  | // We store a copy of MatchedSymbols in a place where it's globally reachable. | 
|  | // This is used by the standalone version of the tool. | 
|  | this->MatchedSymbols = MatchedSymbols; | 
|  | return MatchedSymbols; | 
|  | } | 
|  |  | 
|  | llvm::Expected<tooling::Replacements> createIncludeFixerReplacements( | 
|  | StringRef Code, const IncludeFixerContext &Context, | 
|  | const clang::format::FormatStyle &Style, bool AddQualifiers) { | 
|  | if (Context.getHeaderInfos().empty()) | 
|  | return tooling::Replacements(); | 
|  | StringRef FilePath = Context.getFilePath(); | 
|  | std::string IncludeName = | 
|  | "#include " + Context.getHeaderInfos().front().Header + "\n"; | 
|  | // Create replacements for the new header. | 
|  | clang::tooling::Replacements Insertions; | 
|  | auto Err = | 
|  | Insertions.add(tooling::Replacement(FilePath, UINT_MAX, 0, IncludeName)); | 
|  | if (Err) | 
|  | return std::move(Err); | 
|  |  | 
|  | auto CleanReplaces = cleanupAroundReplacements(Code, Insertions, Style); | 
|  | if (!CleanReplaces) | 
|  | return CleanReplaces; | 
|  |  | 
|  | auto Replaces = std::move(*CleanReplaces); | 
|  | if (AddQualifiers) { | 
|  | for (const auto &Info : Context.getQuerySymbolInfos()) { | 
|  | // Ignore the empty range. | 
|  | if (Info.Range.getLength() > 0) { | 
|  | auto R = tooling::Replacement( | 
|  | {FilePath, Info.Range.getOffset(), Info.Range.getLength(), | 
|  | Context.getHeaderInfos().front().QualifiedName}); | 
|  | auto Err = Replaces.add(R); | 
|  | if (Err) { | 
|  | llvm::consumeError(std::move(Err)); | 
|  | R = tooling::Replacement( | 
|  | R.getFilePath(), Replaces.getShiftedCodePosition(R.getOffset()), | 
|  | R.getLength(), R.getReplacementText()); | 
|  | Replaces = Replaces.merge(tooling::Replacements(R)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return formatReplacements(Code, Replaces, Style); | 
|  | } | 
|  |  | 
|  | } // namespace include_fixer | 
|  | } // namespace clang |