diff options
Diffstat (limited to 'clang-include-fixer')
42 files changed, 4934 insertions, 0 deletions
diff --git a/clang-include-fixer/CMakeLists.txt b/clang-include-fixer/CMakeLists.txt new file mode 100644 index 00000000..f27f7403 --- /dev/null +++ b/clang-include-fixer/CMakeLists.txt @@ -0,0 +1,29 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangIncludeFixer + IncludeFixer.cpp + IncludeFixerContext.cpp + InMemorySymbolIndex.cpp + FuzzySymbolIndex.cpp + SymbolIndexManager.cpp + YamlSymbolIndex.cpp + + LINK_LIBS + clangAST + clangBasic + clangFormat + clangFrontend + clangLex + clangParse + clangSema + clangSerialization + clangTooling + clangToolingCore + findAllSymbols + ) + +add_subdirectory(plugin) +add_subdirectory(tool) +add_subdirectory(find-all-symbols) diff --git a/clang-include-fixer/FuzzySymbolIndex.cpp b/clang-include-fixer/FuzzySymbolIndex.cpp new file mode 100644 index 00000000..099d7389 --- /dev/null +++ b/clang-include-fixer/FuzzySymbolIndex.cpp @@ -0,0 +1,142 @@ +//===--- FuzzySymbolIndex.cpp - Lookup symbols for autocomplete -*- C++ -*-===// +// +// 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 "FuzzySymbolIndex.h" +#include "llvm/Support/Regex.h" + +using clang::find_all_symbols::SymbolAndSignals; +using llvm::StringRef; + +namespace clang { +namespace include_fixer { +namespace { + +class MemSymbolIndex : public FuzzySymbolIndex { +public: + MemSymbolIndex(std::vector<SymbolAndSignals> Symbols) { + for (auto &Symbol : Symbols) { + auto Tokens = tokenize(Symbol.Symbol.getName()); + this->Symbols.emplace_back( + StringRef(llvm::join(Tokens.begin(), Tokens.end(), " ")), + std::move(Symbol)); + } + } + + std::vector<SymbolAndSignals> search(StringRef Query) override { + auto Tokens = tokenize(Query); + llvm::Regex Pattern("^" + queryRegexp(Tokens)); + std::vector<SymbolAndSignals> Results; + for (const Entry &E : Symbols) + if (Pattern.match(E.first)) + Results.push_back(E.second); + return Results; + } + +private: + using Entry = std::pair<llvm::SmallString<32>, SymbolAndSignals>; + std::vector<Entry> Symbols; +}; + +// Helpers for tokenize state machine. +enum TokenizeState { + EMPTY, // No pending characters. + ONE_BIG, // Read one uppercase letter, could be WORD or Word. + BIG_WORD, // Reading an uppercase WORD. + SMALL_WORD, // Reading a lowercase word. + NUMBER // Reading a number. +}; + +enum CharType { UPPER, LOWER, DIGIT, MISC }; +CharType classify(char c) { + if (isupper(c)) + return UPPER; + if (islower(c)) + return LOWER; + if (isdigit(c)) + return DIGIT; + return MISC; +} + +} // namespace + +std::vector<std::string> FuzzySymbolIndex::tokenize(StringRef Text) { + std::vector<std::string> Result; + // State describes the treatment of text from Start to I. + // Once text is Flush()ed into Result, we're done with it and advance Start. + TokenizeState State = EMPTY; + size_t Start = 0; + auto Flush = [&](size_t End) { + if (State != EMPTY) { + Result.push_back(Text.substr(Start, End - Start).lower()); + State = EMPTY; + } + Start = End; + }; + for (size_t I = 0; I < Text.size(); ++I) { + CharType Type = classify(Text[I]); + if (Type == MISC) + Flush(I); + else if (Type == LOWER) + switch (State) { + case BIG_WORD: + Flush(I - 1); // FOOBar: first token is FOO, not FOOB. + LLVM_FALLTHROUGH; + case ONE_BIG: + State = SMALL_WORD; + LLVM_FALLTHROUGH; + case SMALL_WORD: + break; + default: + Flush(I); + State = SMALL_WORD; + } + else if (Type == UPPER) + switch (State) { + case ONE_BIG: + State = BIG_WORD; + LLVM_FALLTHROUGH; + case BIG_WORD: + break; + default: + Flush(I); + State = ONE_BIG; + } + else if (Type == DIGIT && State != NUMBER) { + Flush(I); + State = NUMBER; + } + } + Flush(Text.size()); + return Result; +} + +std::string +FuzzySymbolIndex::queryRegexp(const std::vector<std::string> &Tokens) { + std::string Result; + for (size_t I = 0; I < Tokens.size(); ++I) { + if (I) + Result.append("[[:alnum:]]* "); + for (size_t J = 0; J < Tokens[I].size(); ++J) { + if (J) + Result.append("([[:alnum:]]* )?"); + Result.push_back(Tokens[I][J]); + } + } + return Result; +} + +llvm::Expected<std::unique_ptr<FuzzySymbolIndex>> +FuzzySymbolIndex::createFromYAML(StringRef FilePath) { + auto Buffer = llvm::MemoryBuffer::getFile(FilePath); + if (!Buffer) + return llvm::errorCodeToError(Buffer.getError()); + return llvm::make_unique<MemSymbolIndex>( + find_all_symbols::ReadSymbolInfosFromYAML(Buffer.get()->getBuffer())); +} + +} // namespace include_fixer +} // namespace clang diff --git a/clang-include-fixer/FuzzySymbolIndex.h b/clang-include-fixer/FuzzySymbolIndex.h new file mode 100644 index 00000000..27bfadf1 --- /dev/null +++ b/clang-include-fixer/FuzzySymbolIndex.h @@ -0,0 +1,54 @@ +//===--- FuzzySymbolIndex.h - Lookup symbols for autocomplete ---*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H + +#include "SymbolIndex.h" +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include <string> +#include <vector> + +namespace clang { +namespace include_fixer { + +// A FuzzySymbolIndex retrieves top-level symbols matching a query string. +// +// It refines the contract of SymbolIndex::search to do fuzzy matching: +// - symbol names are tokenized: "unique ptr", "string ref". +// - query must match prefixes of symbol tokens: [upt] +// - if the query has multiple tokens, splits must match: [StR], not [STr]. +// Helpers for tokenization and regex matching are provided. +// +// Implementations may choose to truncate results, refuse short queries, etc. +class FuzzySymbolIndex : public SymbolIndex { +public: + // Loads the specified clang-include-fixer database and returns an index serving it. + static llvm::Expected<std::unique_ptr<FuzzySymbolIndex>> + createFromYAML(llvm::StringRef File); + + // Helpers for implementing indexes: + + // Transforms a symbol name or query into a sequence of tokens. + // - URLHandlerCallback --> [url, handler, callback] + // - snake_case11 --> [snake, case, 11] + // - _WTF$ --> [wtf] + static std::vector<std::string> tokenize(llvm::StringRef Text); + + // Transforms query tokens into an unanchored regexp to match symbol tokens. + // - [fe f] --> /f(\w* )?e\w* f/, matches [fee fie foe]. + static std::string queryRegexp(const std::vector<std::string> &Tokens); +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H diff --git a/clang-include-fixer/InMemorySymbolIndex.cpp b/clang-include-fixer/InMemorySymbolIndex.cpp new file mode 100644 index 00000000..e7858939 --- /dev/null +++ b/clang-include-fixer/InMemorySymbolIndex.cpp @@ -0,0 +1,31 @@ +//===-- InMemorySymbolIndex.cpp--------------------------------------------===// +// +// 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 "InMemorySymbolIndex.h" + +using clang::find_all_symbols::SymbolAndSignals; + +namespace clang { +namespace include_fixer { + +InMemorySymbolIndex::InMemorySymbolIndex( + const std::vector<SymbolAndSignals> &Symbols) { + for (const auto &Symbol : Symbols) + LookupTable[Symbol.Symbol.getName()].push_back(Symbol); +} + +std::vector<SymbolAndSignals> +InMemorySymbolIndex::search(llvm::StringRef Identifier) { + auto I = LookupTable.find(Identifier); + if (I != LookupTable.end()) + return I->second; + return {}; +} + +} // namespace include_fixer +} // namespace clang diff --git a/clang-include-fixer/InMemorySymbolIndex.h b/clang-include-fixer/InMemorySymbolIndex.h new file mode 100644 index 00000000..bea8be91 --- /dev/null +++ b/clang-include-fixer/InMemorySymbolIndex.h @@ -0,0 +1,37 @@ +//===-- InMemorySymbolIndex.h -----------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H + +#include "SymbolIndex.h" +#include <map> +#include <string> +#include <vector> + +namespace clang { +namespace include_fixer { + +/// Xref database with fixed content. +class InMemorySymbolIndex : public SymbolIndex { +public: + InMemorySymbolIndex( + const std::vector<find_all_symbols::SymbolAndSignals> &Symbols); + + std::vector<find_all_symbols::SymbolAndSignals> + search(llvm::StringRef Identifier) override; + +private: + std::map<std::string, std::vector<find_all_symbols::SymbolAndSignals>> + LookupTable; +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H diff --git a/clang-include-fixer/IncludeFixer.cpp b/clang-include-fixer/IncludeFixer.cpp new file mode 100644 index 00000000..d364021f --- /dev/null +++ b/clang-include-fixer/IncludeFixer.cpp @@ -0,0 +1,444 @@ +//===-- 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 llvm::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 = + llvm::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 Include; + + // Get the FileEntry for the include. + StringRef StrippedInclude = Include.trim("\"<>"); + const FileEntry *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 Include; + + bool IsSystem; + 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(), 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(), 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 diff --git a/clang-include-fixer/IncludeFixer.h b/clang-include-fixer/IncludeFixer.h new file mode 100644 index 00000000..ccab65d2 --- /dev/null +++ b/clang-include-fixer/IncludeFixer.h @@ -0,0 +1,157 @@ +//===-- IncludeFixer.h - Include inserter -----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H + +#include "IncludeFixerContext.h" +#include "SymbolIndexManager.h" +#include "clang/Format/Format.h" +#include "clang/Sema/ExternalSemaSource.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" +#include <memory> +#include <vector> + +namespace clang { + +class CompilerInvocation; +class DiagnosticConsumer; +class FileManager; +class PCHContainerOperations; + +namespace include_fixer { + +class IncludeFixerActionFactory : public clang::tooling::ToolAction { +public: + /// \param SymbolIndexMgr A source for matching symbols to header files. + /// \param Contexts The contexts for the symbols being queried. + /// \param StyleName Fallback style for reformatting. + /// \param MinimizeIncludePaths whether inserted include paths are optimized. + IncludeFixerActionFactory(SymbolIndexManager &SymbolIndexMgr, + std::vector<IncludeFixerContext> &Contexts, + StringRef StyleName, + bool MinimizeIncludePaths = true); + + ~IncludeFixerActionFactory() override; + + bool + runInvocation(std::shared_ptr<clang::CompilerInvocation> Invocation, + clang::FileManager *Files, + std::shared_ptr<clang::PCHContainerOperations> PCHContainerOps, + clang::DiagnosticConsumer *Diagnostics) override; + +private: + /// The client to use to find cross-references. + SymbolIndexManager &SymbolIndexMgr; + + /// Multiple contexts for files being processed. + std::vector<IncludeFixerContext> &Contexts; + + /// Whether inserted include paths should be optimized. + bool MinimizeIncludePaths; + + /// The fallback format style for formatting after insertion if no + /// clang-format config file was found. + std::string FallbackStyle; +}; + +/// Create replacements, which are generated by clang-format, for the +/// missing header and mising qualifiers insertions. The function uses the +/// first header for insertion. +/// +/// \param Code The source code. +/// \param Context The context which contains all information for creating +/// clang-include-fixer replacements. +/// \param Style clang-format style being used. +/// \param AddQualifiers Whether we should add qualifiers to all instances of +/// an unidentified symbol. +/// +/// \return Formatted replacements for inserting, sorting headers and adding +/// qualifiers on success; otherwise, an llvm::Error carrying llvm::StringError +/// is returned. +llvm::Expected<tooling::Replacements> createIncludeFixerReplacements( + StringRef Code, const IncludeFixerContext &Context, + const format::FormatStyle &Style = format::getLLVMStyle(), + bool AddQualifiers = true); + +/// Handles callbacks from sema, does the include lookup and turns it into an +/// IncludeFixerContext. +class IncludeFixerSemaSource : public clang::ExternalSemaSource { +public: + explicit IncludeFixerSemaSource(SymbolIndexManager &SymbolIndexMgr, + bool MinimizeIncludePaths, + bool GenerateDiagnostics) + : SymbolIndexMgr(SymbolIndexMgr), + MinimizeIncludePaths(MinimizeIncludePaths), + GenerateDiagnostics(GenerateDiagnostics) {} + + void setCompilerInstance(CompilerInstance *CI) { this->CI = CI; } + void setFilePath(StringRef FilePath) { this->FilePath = FilePath; } + + /// Callback for incomplete types. If we encounter a forward declaration we + /// have the fully qualified name ready. Just query that. + bool MaybeDiagnoseMissingCompleteType(clang::SourceLocation Loc, + clang::QualType T) override; + + /// Callback for unknown identifiers. Try to piece together as much + /// qualification as we can get and do a query. + clang::TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, + int LookupKind, Scope *S, CXXScopeSpec *SS, + CorrectionCandidateCallback &CCC, + DeclContext *MemberContext, + bool EnteringContext, + const ObjCObjectPointerType *OPT) override; + + /// Get the minimal include for a given path. + std::string minimizeInclude(StringRef Include, + const clang::SourceManager &SourceManager, + clang::HeaderSearch &HeaderSearch) const; + + /// Get the include fixer context for the queried symbol. + IncludeFixerContext getIncludeFixerContext( + const clang::SourceManager &SourceManager, + clang::HeaderSearch &HeaderSearch, + ArrayRef<find_all_symbols::SymbolInfo> MatchedSymbols) const; + + /// Get the global matched symbols. + ArrayRef<find_all_symbols::SymbolInfo> getMatchedSymbols() const { + return MatchedSymbols; + } + +private: + /// Query the database for a given identifier. + std::vector<find_all_symbols::SymbolInfo> + query(StringRef Query, StringRef ScopedQualifiers, tooling::Range Range); + + CompilerInstance *CI; + + /// The client to use to find cross-references. + SymbolIndexManager &SymbolIndexMgr; + + /// The information of the symbols being queried. + std::vector<IncludeFixerContext::QuerySymbolInfo> QuerySymbolInfos; + + /// All symbol candidates which match QuerySymbol. We only include the first + /// discovered identifier to avoid getting caught in results from error + /// recovery. + std::vector<find_all_symbols::SymbolInfo> MatchedSymbols; + + /// The file path to the file being processed. + std::string FilePath; + + /// Whether we should use the smallest possible include path. + bool MinimizeIncludePaths = true; + + /// Whether we should generate diagnostics with fixits for missing symbols. + bool GenerateDiagnostics = false; +}; +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H diff --git a/clang-include-fixer/IncludeFixerContext.cpp b/clang-include-fixer/IncludeFixerContext.cpp new file mode 100644 index 00000000..a9fef45c --- /dev/null +++ b/clang-include-fixer/IncludeFixerContext.cpp @@ -0,0 +1,115 @@ +//===-- IncludeFixerContext.cpp - Include fixer context ---------*- C++ -*-===// +// +// 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 "IncludeFixerContext.h" +#include <algorithm> + +namespace clang { +namespace include_fixer { + +namespace { + +// Splits a multiply qualified names (e.g. a::b::c). +llvm::SmallVector<llvm::StringRef, 8> +SplitQualifiers(llvm::StringRef StringQualifiers) { + llvm::SmallVector<llvm::StringRef, 8> Qualifiers; + StringQualifiers.split(Qualifiers, "::"); + return Qualifiers; +} + +std::string createQualifiedNameForReplacement( + llvm::StringRef RawSymbolName, + llvm::StringRef SymbolScopedQualifiersName, + const find_all_symbols::SymbolInfo &MatchedSymbol) { + // No need to add missing qualifiers if SymbolIndentifer has a global scope + // operator "::". + if (RawSymbolName.startswith("::")) + return RawSymbolName; + + std::string QualifiedName = MatchedSymbol.getQualifiedName(); + + // For nested classes, the qualified name constructed from database misses + // some stripped qualifiers, because when we search a symbol in database, + // we strip qualifiers from the end until we find a result. So append the + // missing stripped qualifiers here. + // + // Get stripped qualifiers. + auto SymbolQualifiers = SplitQualifiers(RawSymbolName); + std::string StrippedQualifiers; + while (!SymbolQualifiers.empty() && + !llvm::StringRef(QualifiedName).endswith(SymbolQualifiers.back())) { + StrippedQualifiers = + "::" + SymbolQualifiers.back().str() + StrippedQualifiers; + SymbolQualifiers.pop_back(); + } + // Append the missing stripped qualifiers. + std::string FullyQualifiedName = QualifiedName + StrippedQualifiers; + + // Try to find and skip the common prefix qualifiers. + auto FullySymbolQualifiers = SplitQualifiers(FullyQualifiedName); + auto ScopedQualifiers = SplitQualifiers(SymbolScopedQualifiersName); + auto FullySymbolQualifiersIter = FullySymbolQualifiers.begin(); + auto SymbolScopedQualifiersIter = ScopedQualifiers.begin(); + while (FullySymbolQualifiersIter != FullySymbolQualifiers.end() && + SymbolScopedQualifiersIter != ScopedQualifiers.end()) { + if (*FullySymbolQualifiersIter != *SymbolScopedQualifiersIter) + break; + ++FullySymbolQualifiersIter; + ++SymbolScopedQualifiersIter; + } + std::string Result; + for (; FullySymbolQualifiersIter != FullySymbolQualifiers.end(); + ++FullySymbolQualifiersIter) { + if (!Result.empty()) + Result += "::"; + Result += *FullySymbolQualifiersIter; + } + return Result; +} + +} // anonymous namespace + +IncludeFixerContext::IncludeFixerContext( + StringRef FilePath, std::vector<QuerySymbolInfo> QuerySymbols, + std::vector<find_all_symbols::SymbolInfo> Symbols) + : FilePath(FilePath), QuerySymbolInfos(std::move(QuerySymbols)), + MatchedSymbols(std::move(Symbols)) { + // Remove replicated QuerySymbolInfos with the same range. + // + // QuerySymbolInfos may contain replicated elements. Because CorrectTypo + // callback doesn't always work as we expected. In somecases, it will be + // triggered at the same position or unidentified symbol multiple times. + std::sort(QuerySymbolInfos.begin(), QuerySymbolInfos.end(), + [&](const QuerySymbolInfo &A, const QuerySymbolInfo &B) { + return std::make_pair(A.Range.getOffset(), A.Range.getLength()) < + std::make_pair(B.Range.getOffset(), B.Range.getLength()); + }); + QuerySymbolInfos.erase( + std::unique(QuerySymbolInfos.begin(), QuerySymbolInfos.end(), + [](const QuerySymbolInfo &A, const QuerySymbolInfo &B) { + return A.Range == B.Range; + }), + QuerySymbolInfos.end()); + for (const auto &Symbol : MatchedSymbols) { + HeaderInfos.push_back( + {Symbol.getFilePath().str(), + createQualifiedNameForReplacement( + QuerySymbolInfos.front().RawIdentifier, + QuerySymbolInfos.front().ScopedQualifiers, Symbol)}); + } + // Deduplicate header infos. + HeaderInfos.erase(std::unique(HeaderInfos.begin(), HeaderInfos.end(), + [](const HeaderInfo &A, const HeaderInfo &B) { + return A.Header == B.Header && + A.QualifiedName == B.QualifiedName; + }), + HeaderInfos.end()); +} + +} // include_fixer +} // clang diff --git a/clang-include-fixer/IncludeFixerContext.h b/clang-include-fixer/IncludeFixerContext.h new file mode 100644 index 00000000..bbb87e2b --- /dev/null +++ b/clang-include-fixer/IncludeFixerContext.h @@ -0,0 +1,94 @@ +//===-- IncludeFixerContext.h - Include fixer context -----------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H + +#include "find-all-symbols/SymbolInfo.h" +#include "clang/Tooling/Core/Replacement.h" +#include <string> +#include <vector> + +namespace clang { +namespace include_fixer { + +/// \brief A context for a file being processed. It includes all query +/// information, e.g. symbols being queried in database, all header candidates. +class IncludeFixerContext { +public: + struct HeaderInfo { + /// \brief The header where QualifiedName comes from. + std::string Header; + /// \brief A symbol name with completed namespace qualifiers which will + /// replace the original symbol. + std::string QualifiedName; + }; + + struct QuerySymbolInfo { + /// \brief The raw symbol name being queried in database. This name might + /// miss some namespace qualifiers, and will be replaced by a fully + /// qualified one. + std::string RawIdentifier; + + /// \brief The qualifiers of the scope in which SymbolIdentifier lookup + /// occurs. It is represented as a sequence of names and scope resolution + /// operatiors ::, ending with a scope resolution operator (e.g. a::b::). + /// Empty if SymbolIdentifier is not in a specific scope. + std::string ScopedQualifiers; + + /// \brief The replacement range of RawIdentifier. + tooling::Range Range; + }; + + IncludeFixerContext() = default; + IncludeFixerContext(StringRef FilePath, + std::vector<QuerySymbolInfo> QuerySymbols, + std::vector<find_all_symbols::SymbolInfo> Symbols); + + /// \brief Get symbol name. + llvm::StringRef getSymbolIdentifier() const { + return QuerySymbolInfos.front().RawIdentifier; + } + + /// \brief Get replacement range of the symbol. + tooling::Range getSymbolRange() const { + return QuerySymbolInfos.front().Range; + } + + /// \brief Get the file path to the file being processed. + StringRef getFilePath() const { return FilePath; } + + /// \brief Get header information. + const std::vector<HeaderInfo> &getHeaderInfos() const { return HeaderInfos; } + + /// \brief Get information of symbols being querid. + const std::vector<QuerySymbolInfo> &getQuerySymbolInfos() const { + return QuerySymbolInfos; + } + +private: + friend struct llvm::yaml::MappingTraits<IncludeFixerContext>; + + /// \brief The file path to the file being processed. + std::string FilePath; + + /// \brief All instances of an unidentified symbol being queried. + std::vector<QuerySymbolInfo> QuerySymbolInfos; + + /// \brief The symbol candidates which match SymbolIdentifier. The symbols are + /// sorted in a descending order based on the popularity info in SymbolInfo. + std::vector<find_all_symbols::SymbolInfo> MatchedSymbols; + + /// \brief The header information. + std::vector<HeaderInfo> HeaderInfos; +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H diff --git a/clang-include-fixer/SymbolIndex.h b/clang-include-fixer/SymbolIndex.h new file mode 100644 index 00000000..ca04d50a --- /dev/null +++ b/clang-include-fixer/SymbolIndex.h @@ -0,0 +1,37 @@ +//===-- SymbolIndex.h - Interface for symbol-header matching ----*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H + +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/ADT/StringRef.h" +#include <vector> + +namespace clang { +namespace include_fixer { + +/// This class provides an interface for finding all `SymbolInfo`s corresponding +/// to a symbol name from a symbol database. +class SymbolIndex { +public: + virtual ~SymbolIndex() = default; + + /// Search for all `SymbolInfo`s corresponding to an identifier. + /// \param Identifier The unqualified identifier being searched for. + /// \returns A list of `SymbolInfo` candidates. + // FIXME: Expose the type name so we can also insert using declarations (or + // fix the usage) + virtual std::vector<find_all_symbols::SymbolAndSignals> + search(llvm::StringRef Identifier) = 0; +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H diff --git a/clang-include-fixer/SymbolIndexManager.cpp b/clang-include-fixer/SymbolIndexManager.cpp new file mode 100644 index 00000000..7b827536 --- /dev/null +++ b/clang-include-fixer/SymbolIndexManager.cpp @@ -0,0 +1,158 @@ +//===-- SymbolIndexManager.cpp - Managing multiple SymbolIndices-*- C++ -*-===// +// +// 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 "SymbolIndexManager.h" +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Path.h" + +#define DEBUG_TYPE "clang-include-fixer" + +namespace clang { +namespace include_fixer { + +using find_all_symbols::SymbolInfo; +using find_all_symbols::SymbolAndSignals; + +// Calculate a score based on whether we think the given header is closely +// related to the given source file. +static double similarityScore(llvm::StringRef FileName, + llvm::StringRef Header) { + // Compute the maximum number of common path segements between Header and + // a suffix of FileName. + // We do not do a full longest common substring computation, as Header + // specifies the path we would directly #include, so we assume it is rooted + // relatively to a subproject of the repository. + int MaxSegments = 1; + for (auto FileI = llvm::sys::path::begin(FileName), + FileE = llvm::sys::path::end(FileName); + FileI != FileE; ++FileI) { + int Segments = 0; + for (auto HeaderI = llvm::sys::path::begin(Header), + HeaderE = llvm::sys::path::end(Header), I = FileI; + HeaderI != HeaderE && *I == *HeaderI && I != FileE; ++I, ++HeaderI) { + ++Segments; + } + MaxSegments = std::max(Segments, MaxSegments); + } + return MaxSegments; +} + +static void rank(std::vector<SymbolAndSignals> &Symbols, + llvm::StringRef FileName) { + llvm::DenseMap<llvm::StringRef, double> Score; + for (const auto &Symbol : Symbols) { + // Calculate a score from the similarity of the header the symbol is in + // with the current file and the popularity of the symbol. + double NewScore = similarityScore(FileName, Symbol.Symbol.getFilePath()) * + (1.0 + std::log2(1 + Symbol.Signals.Seen)); + double &S = Score[Symbol.Symbol.getFilePath()]; + S = std::max(S, NewScore); + } + // Sort by the gathered scores. Use file name as a tie breaker so we can + // deduplicate. + std::sort(Symbols.begin(), Symbols.end(), + [&](const SymbolAndSignals &A, const SymbolAndSignals &B) { + auto AS = Score[A.Symbol.getFilePath()]; + auto BS = Score[B.Symbol.getFilePath()]; + if (AS != BS) + return AS > BS; + return A.Symbol.getFilePath() < B.Symbol.getFilePath(); + }); +} + +std::vector<find_all_symbols::SymbolInfo> +SymbolIndexManager::search(llvm::StringRef Identifier, + bool IsNestedSearch, + llvm::StringRef FileName) const { + // The identifier may be fully qualified, so split it and get all the context + // names. + llvm::SmallVector<llvm::StringRef, 8> Names; + Identifier.split(Names, "::"); + + bool IsFullyQualified = false; + if (Identifier.startswith("::")) { + Names.erase(Names.begin()); // Drop first (empty) element. + IsFullyQualified = true; + } + + // As long as we don't find a result keep stripping name parts from the end. + // This is to support nested classes which aren't recorded in the database. + // Eventually we will either hit a class (namespaces aren't in the database + // either) and can report that result. + bool TookPrefix = false; + std::vector<SymbolAndSignals> MatchedSymbols; + do { + std::vector<SymbolAndSignals> Symbols; + for (const auto &DB : SymbolIndices) { + auto Res = DB.get()->search(Names.back()); + Symbols.insert(Symbols.end(), Res.begin(), Res.end()); + } + + LLVM_DEBUG(llvm::dbgs() << "Searching " << Names.back() << "... got " + << Symbols.size() << " results...\n"); + + for (auto &SymAndSig : Symbols) { + const SymbolInfo &Symbol = SymAndSig.Symbol; + // Match the identifier name without qualifier. + bool IsMatched = true; + auto SymbolContext = Symbol.getContexts().begin(); + auto IdentiferContext = Names.rbegin() + 1; // Skip identifier name. + // Match the remaining context names. + while (IdentiferContext != Names.rend() && + SymbolContext != Symbol.getContexts().end()) { + if (SymbolContext->second == *IdentiferContext) { + ++IdentiferContext; + ++SymbolContext; + } else if (SymbolContext->first == + find_all_symbols::SymbolInfo::ContextType::EnumDecl) { + // Skip non-scoped enum context. + ++SymbolContext; + } else { + IsMatched = false; + break; + } + } + + // If the name was qualified we only want to add results if we evaluated + // all contexts. + if (IsFullyQualified) + IsMatched &= (SymbolContext == Symbol.getContexts().end()); + + // FIXME: Support full match. At this point, we only find symbols in + // database which end with the same contexts with the identifier. + if (IsMatched && IdentiferContext == Names.rend()) { + // If we're in a situation where we took a prefix but the thing we + // found couldn't possibly have a nested member ignore it. + if (TookPrefix && + (Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Function || + Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Variable || + Symbol.getSymbolKind() == + SymbolInfo::SymbolKind::EnumConstantDecl || + Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Macro)) + continue; + + MatchedSymbols.push_back(std::move(SymAndSig)); + } + } + Names.pop_back(); + TookPrefix = true; + } while (MatchedSymbols.empty() && !Names.empty() && IsNestedSearch); + + rank(MatchedSymbols, FileName); + // Strip signals, they are no longer needed. + std::vector<SymbolInfo> Res; + for (auto &SymAndSig : MatchedSymbols) + Res.push_back(std::move(SymAndSig.Symbol)); + return Res; +} + +} // namespace include_fixer +} // namespace clang diff --git a/clang-include-fixer/SymbolIndexManager.h b/clang-include-fixer/SymbolIndexManager.h new file mode 100644 index 00000000..ca2d7399 --- /dev/null +++ b/clang-include-fixer/SymbolIndexManager.h @@ -0,0 +1,65 @@ +//===-- SymbolIndexManager.h - Managing multiple SymbolIndices --*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEXMANAGER_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEXMANAGER_H + +#include "SymbolIndex.h" +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/ADT/StringRef.h" + +#ifdef _MSC_VER +// Disable warnings from ppltasks.h transitively included by <future>. +#pragma warning(push) +#pragma warning(disable:4530) +#endif + +#include <future> + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace clang { +namespace include_fixer { + +/// This class provides an interface for finding the header files corresponding +/// to an identifier in the source code from multiple symbol databases. +class SymbolIndexManager { +public: + void addSymbolIndex(std::function<std::unique_ptr<SymbolIndex>()> F) { +#if LLVM_ENABLE_THREADS + auto Strategy = std::launch::async; +#else + auto Strategy = std::launch::deferred; +#endif + SymbolIndices.push_back(std::async(Strategy, F)); + } + + /// Search for header files to be included for an identifier. + /// \param Identifier The identifier being searched for. May or may not be + /// fully qualified. + /// \param IsNestedSearch Whether searching nested classes. If true, the + /// method tries to strip identifier name parts from the end until it + /// finds the corresponding candidates in database (e.g for identifier + /// "b::foo", the method will try to find "b" if it fails to find + /// "b::foo"). + /// + /// \returns A list of symbol candidates. + std::vector<find_all_symbols::SymbolInfo> + search(llvm::StringRef Identifier, bool IsNestedSearch = true, + llvm::StringRef FileName = "") const; + +private: + std::vector<std::shared_future<std::unique_ptr<SymbolIndex>>> SymbolIndices; +}; + +} // namespace include_fixer +} // namespace clang + +#endif diff --git a/clang-include-fixer/YamlSymbolIndex.cpp b/clang-include-fixer/YamlSymbolIndex.cpp new file mode 100644 index 00000000..de72e9a9 --- /dev/null +++ b/clang-include-fixer/YamlSymbolIndex.cpp @@ -0,0 +1,60 @@ +//===-- YamlSymbolIndex.cpp -----------------------------------------------===// +// +// 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 "YamlSymbolIndex.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include <string> +#include <vector> + +using clang::find_all_symbols::SymbolInfo; +using clang::find_all_symbols::SymbolAndSignals; + +namespace clang { +namespace include_fixer { + +llvm::ErrorOr<std::unique_ptr<YamlSymbolIndex>> +YamlSymbolIndex::createFromFile(llvm::StringRef FilePath) { + auto Buffer = llvm::MemoryBuffer::getFile(FilePath); + if (!Buffer) + return Buffer.getError(); + + return std::unique_ptr<YamlSymbolIndex>(new YamlSymbolIndex( + find_all_symbols::ReadSymbolInfosFromYAML(Buffer.get()->getBuffer()))); +} + +llvm::ErrorOr<std::unique_ptr<YamlSymbolIndex>> +YamlSymbolIndex::createFromDirectory(llvm::StringRef Directory, + llvm::StringRef Name) { + // Walk upwards from Directory, looking for files. + for (llvm::SmallString<128> PathStorage = Directory; !Directory.empty(); + Directory = llvm::sys::path::parent_path(Directory)) { + assert(Directory.size() <= PathStorage.size()); + PathStorage.resize(Directory.size()); // Shrink to parent. + llvm::sys::path::append(PathStorage, Name); + if (auto DB = createFromFile(PathStorage)) + return DB; + } + return llvm::make_error_code(llvm::errc::no_such_file_or_directory); +} + +std::vector<SymbolAndSignals> +YamlSymbolIndex::search(llvm::StringRef Identifier) { + std::vector<SymbolAndSignals> Results; + for (const auto &Symbol : Symbols) { + if (Symbol.Symbol.getName() == Identifier) + Results.push_back(Symbol); + } + return Results; +} + +} // namespace include_fixer +} // namespace clang diff --git a/clang-include-fixer/YamlSymbolIndex.h b/clang-include-fixer/YamlSymbolIndex.h new file mode 100644 index 00000000..3c4f5144 --- /dev/null +++ b/clang-include-fixer/YamlSymbolIndex.h @@ -0,0 +1,45 @@ +//===-- YamlSymbolIndex.h ---------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H + +#include "SymbolIndex.h" +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/Support/ErrorOr.h" +#include <map> +#include <vector> + +namespace clang { +namespace include_fixer { + +/// Yaml format database. +class YamlSymbolIndex : public SymbolIndex { +public: + /// Create a new Yaml db from a file. + static llvm::ErrorOr<std::unique_ptr<YamlSymbolIndex>> + createFromFile(llvm::StringRef FilePath); + /// Look for a file called \c Name in \c Directory and all parent directories. + static llvm::ErrorOr<std::unique_ptr<YamlSymbolIndex>> + createFromDirectory(llvm::StringRef Directory, llvm::StringRef Name); + + std::vector<find_all_symbols::SymbolAndSignals> + search(llvm::StringRef Identifier) override; + +private: + explicit YamlSymbolIndex( + std::vector<find_all_symbols::SymbolAndSignals> Symbols) + : Symbols(std::move(Symbols)) {} + + std::vector<find_all_symbols::SymbolAndSignals> Symbols; +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H diff --git a/clang-include-fixer/find-all-symbols/CMakeLists.txt b/clang-include-fixer/find-all-symbols/CMakeLists.txt new file mode 100644 index 00000000..c5fe19bf --- /dev/null +++ b/clang-include-fixer/find-all-symbols/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(findAllSymbols + FindAllSymbols.cpp + FindAllSymbolsAction.cpp + FindAllMacros.cpp + HeaderMapCollector.cpp + PathConfig.cpp + PragmaCommentHandler.cpp + STLPostfixHeaderMap.cpp + SymbolInfo.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangLex + clangTooling + ) + +add_subdirectory(tool) diff --git a/clang-include-fixer/find-all-symbols/FindAllMacros.cpp b/clang-include-fixer/find-all-symbols/FindAllMacros.cpp new file mode 100644 index 00000000..ed1bc2f4 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/FindAllMacros.cpp @@ -0,0 +1,69 @@ +//===-- FindAllMacros.cpp - find all macros ---------------------*- C++ -*-===// +// +// 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 "FindAllMacros.h" +#include "HeaderMapCollector.h" +#include "PathConfig.h" +#include "SymbolInfo.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/MacroInfo.h" +#include "clang/Lex/Token.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace find_all_symbols { + +llvm::Optional<SymbolInfo> +FindAllMacros::CreateMacroSymbol(const Token &MacroNameTok, + const MacroInfo *info) { + std::string FilePath = + getIncludePath(*SM, info->getDefinitionLoc(), Collector); + if (FilePath.empty()) + return llvm::None; + return SymbolInfo(MacroNameTok.getIdentifierInfo()->getName(), + SymbolInfo::SymbolKind::Macro, FilePath, {}); +} + +void FindAllMacros::MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) { + if (auto Symbol = CreateMacroSymbol(MacroNameTok, MD->getMacroInfo())) + ++FileSymbols[*Symbol].Seen; +} + +void FindAllMacros::MacroUsed(const Token &Name, const MacroDefinition &MD) { + if (!MD || !SM->isInMainFile(SM->getExpansionLoc(Name.getLocation()))) + return; + if (auto Symbol = CreateMacroSymbol(Name, MD.getMacroInfo())) + ++FileSymbols[*Symbol].Used; +} + +void FindAllMacros::MacroExpands(const Token &MacroNameTok, + const MacroDefinition &MD, SourceRange Range, + const MacroArgs *Args) { + MacroUsed(MacroNameTok, MD); +} + +void FindAllMacros::Ifdef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) { + MacroUsed(MacroNameTok, MD); +} + +void FindAllMacros::Ifndef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) { + MacroUsed(MacroNameTok, MD); +} + +void FindAllMacros::EndOfMainFile() { + Reporter->reportSymbols(SM->getFileEntryForID(SM->getMainFileID())->getName(), + FileSymbols); + FileSymbols.clear(); +} + +} // namespace find_all_symbols +} // namespace clang diff --git a/clang-include-fixer/find-all-symbols/FindAllMacros.h b/clang-include-fixer/find-all-symbols/FindAllMacros.h new file mode 100644 index 00000000..5aaf3884 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/FindAllMacros.h @@ -0,0 +1,64 @@ +//===-- FindAllMacros.h - find all macros -----------------------*- C++ -*-===// +// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H + +#include "SymbolInfo.h" +#include "SymbolReporter.h" +#include "clang/Lex/PPCallbacks.h" + +namespace clang { +class MacroInfo; +namespace find_all_symbols { + +class HeaderMapCollector; + +/// \brief A preprocessor that collects all macro symbols. +/// The contexts of a macro will be ignored since they are not available during +/// preprocessing period. +class FindAllMacros : public clang::PPCallbacks { +public: + explicit FindAllMacros(SymbolReporter *Reporter, SourceManager *SM, + HeaderMapCollector *Collector = nullptr) + : Reporter(Reporter), SM(SM), Collector(Collector) {} + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override; + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override; + + void Ifdef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) override; + + void Ifndef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) override; + + void EndOfMainFile() override; + +private: + llvm::Optional<SymbolInfo> CreateMacroSymbol(const Token &MacroNameTok, + const MacroInfo *MD); + // Not a callback, just a common path for all usage types. + void MacroUsed(const Token &Name, const MacroDefinition &MD); + + SymbolInfo::SignalMap FileSymbols; + // Reporter for SymbolInfo. + SymbolReporter *const Reporter; + SourceManager *const SM; + // A remapping header file collector allowing clients to include a different + // header. + HeaderMapCollector *const Collector; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H diff --git a/clang-include-fixer/find-all-symbols/FindAllSymbols.cpp b/clang-include-fixer/find-all-symbols/FindAllSymbols.cpp new file mode 100644 index 00000000..bb6a3fa9 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/FindAllSymbols.cpp @@ -0,0 +1,268 @@ +//===-- FindAllSymbols.cpp - find all symbols--------------------*- C++ -*-===// +// +// 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 "FindAllSymbols.h" +#include "HeaderMapCollector.h" +#include "PathConfig.h" +#include "SymbolInfo.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/FileSystem.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace find_all_symbols { +namespace { + +AST_MATCHER(EnumConstantDecl, isInScopedEnum) { + if (const auto *ED = dyn_cast<EnumDecl>(Node.getDeclContext())) + return ED->isScoped(); + return false; +} + +AST_POLYMORPHIC_MATCHER(isFullySpecialized, + AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl, + CXXRecordDecl)) { + if (Node.getTemplateSpecializationKind() == TSK_ExplicitSpecialization) { + bool IsPartialSpecialization = + llvm::isa<VarTemplatePartialSpecializationDecl>(Node) || + llvm::isa<ClassTemplatePartialSpecializationDecl>(Node); + return !IsPartialSpecialization; + } + return false; +} + +std::vector<SymbolInfo::Context> GetContexts(const NamedDecl *ND) { + std::vector<SymbolInfo::Context> Contexts; + for (const auto *Context = ND->getDeclContext(); Context; + Context = Context->getParent()) { + if (llvm::isa<TranslationUnitDecl>(Context) || + llvm::isa<LinkageSpecDecl>(Context)) + break; + + assert(llvm::isa<NamedDecl>(Context) && + "Expect Context to be a NamedDecl"); + if (const auto *NSD = dyn_cast<NamespaceDecl>(Context)) { + if (!NSD->isInlineNamespace()) + Contexts.emplace_back(SymbolInfo::ContextType::Namespace, + NSD->getName().str()); + } else if (const auto *ED = dyn_cast<EnumDecl>(Context)) { + Contexts.emplace_back(SymbolInfo::ContextType::EnumDecl, + ED->getName().str()); + } else { + const auto *RD = cast<RecordDecl>(Context); + Contexts.emplace_back(SymbolInfo::ContextType::Record, + RD->getName().str()); + } + } + return Contexts; +} + +llvm::Optional<SymbolInfo> +CreateSymbolInfo(const NamedDecl *ND, const SourceManager &SM, + const HeaderMapCollector *Collector) { + SymbolInfo::SymbolKind Type; + if (llvm::isa<VarDecl>(ND)) { + Type = SymbolInfo::SymbolKind::Variable; + } else if (llvm::isa<FunctionDecl>(ND)) { + Type = SymbolInfo::SymbolKind::Function; + } else if (llvm::isa<TypedefNameDecl>(ND)) { + Type = SymbolInfo::SymbolKind::TypedefName; + } else if (llvm::isa<EnumConstantDecl>(ND)) { + Type = SymbolInfo::SymbolKind::EnumConstantDecl; + } else if (llvm::isa<EnumDecl>(ND)) { + Type = SymbolInfo::SymbolKind::EnumDecl; + // Ignore anonymous enum declarations. + if (ND->getName().empty()) + return llvm::None; + } else { + assert(llvm::isa<RecordDecl>(ND) && + "Matched decl must be one of VarDecl, " + "FunctionDecl, TypedefNameDecl, EnumConstantDecl, " + "EnumDecl and RecordDecl!"); + // C-style record decl can have empty name, e.g "struct { ... } var;". + if (ND->getName().empty()) + return llvm::None; + Type = SymbolInfo::SymbolKind::Class; + } + + SourceLocation Loc = SM.getExpansionLoc(ND->getLocation()); + if (!Loc.isValid()) { + llvm::errs() << "Declaration " << ND->getNameAsString() << "(" + << ND->getDeclKindName() + << ") has invalid declaration location."; + return llvm::None; + } + + std::string FilePath = getIncludePath(SM, Loc, Collector); + if (FilePath.empty()) return llvm::None; + + return SymbolInfo(ND->getNameAsString(), Type, FilePath, GetContexts(ND)); +} + +} // namespace + +void FindAllSymbols::registerMatchers(MatchFinder *MatchFinder) { + // FIXME: Handle specialization. + auto IsInSpecialization = hasAncestor( + decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()), + functionDecl(isExplicitTemplateSpecialization())))); + + // Matchers for both C and C++. + // We only match symbols from header files, i.e. not from main files (see + // function's comment for detailed explanation). + auto CommonFilter = + allOf(unless(isImplicit()), unless(isExpansionInMainFile())); + + auto HasNSOrTUCtxMatcher = + hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())); + + // We need seperate rules for C record types and C++ record types since some + // template related matchers are inapplicable on C record declarations. + // + // Matchers specific to C++ code. + // All declarations should be in namespace or translation unit. + auto CCMatcher = + allOf(HasNSOrTUCtxMatcher, unless(IsInSpecialization), + unless(ast_matchers::isTemplateInstantiation()), + unless(isInstantiated()), unless(isFullySpecialized())); + + // Matchers specific to code in extern "C" {...}. + auto ExternCMatcher = hasDeclContext(linkageSpecDecl()); + + // Matchers for variable declarations. + // + // In most cases, `ParmVarDecl` is filtered out by hasDeclContext(...) + // matcher since the declaration context is usually `MethodDecl`. However, + // this assumption does not hold for parameters of a function pointer + // parameter. + // For example, consider a function declaration: + // void Func(void (*)(float), int); + // The float parameter of the function pointer has an empty name, and its + // declaration context is an anonymous namespace; therefore, it won't be + // filtered out by our matchers above. + auto Vars = varDecl(CommonFilter, anyOf(ExternCMatcher, CCMatcher), + unless(parmVarDecl())); + + // Matchers for C-style record declarations in extern "C" {...}. + auto CRecords = recordDecl(CommonFilter, ExternCMatcher, isDefinition()); + // Matchers for C++ record declarations. + auto CXXRecords = cxxRecordDecl(CommonFilter, CCMatcher, isDefinition()); + + // Matchers for function declarations. + // We want to exclude friend declaration, but the `DeclContext` of a friend + // function declaration is not the class in which it is declared, so we need + // to explicitly check if the parent is a `friendDecl`. + auto Functions = functionDecl(CommonFilter, unless(hasParent(friendDecl())), + anyOf(ExternCMatcher, CCMatcher)); + + // Matcher for typedef and type alias declarations. + // + // typedef and type alias can come from C-style headers and C++ headers. + // For C-style headers, `DeclContxet` can be either `TranslationUnitDecl` + // or `LinkageSpecDecl`. + // For C++ headers, `DeclContext ` can be either `TranslationUnitDecl` + // or `NamespaceDecl`. + // With the following context matcher, we can match `typedefNameDecl` from + // both C-style headers and C++ headers (except for those in classes). + // "cc_matchers" are not included since template-related matchers are not + // applicable on `TypedefNameDecl`. + auto Typedefs = + typedefNameDecl(CommonFilter, anyOf(HasNSOrTUCtxMatcher, + hasDeclContext(linkageSpecDecl()))); + + // Matchers for enum declarations. + auto Enums = enumDecl(CommonFilter, isDefinition(), + anyOf(HasNSOrTUCtxMatcher, ExternCMatcher)); + + // Matchers for enum constant declarations. + // We only match the enum constants in non-scoped enum declarations which are + // inside toplevel translation unit or a namespace. + auto EnumConstants = enumConstantDecl( + CommonFilter, unless(isInScopedEnum()), + anyOf(hasDeclContext(enumDecl(HasNSOrTUCtxMatcher)), ExternCMatcher)); + + // Most of the time we care about all matchable decls, or all types. + auto Types = namedDecl(anyOf(CRecords, CXXRecords, Enums)); + auto Decls = namedDecl(anyOf(CRecords, CXXRecords, Enums, Typedefs, Vars, + EnumConstants, Functions)); + + // We want eligible decls bound to "decl"... + MatchFinder->addMatcher(Decls.bind("decl"), this); + + // ... and all uses of them bound to "use". These have many cases: + // Uses of values/functions: these generate a declRefExpr. + MatchFinder->addMatcher( + declRefExpr(isExpansionInMainFile(), to(Decls.bind("use"))), this); + // Uses of function templates: + MatchFinder->addMatcher( + declRefExpr(isExpansionInMainFile(), + to(functionDecl(hasParent( + functionTemplateDecl(has(Functions.bind("use"))))))), + this); + + // Uses of most types: just look at what the typeLoc refers to. + MatchFinder->addMatcher( + typeLoc(isExpansionInMainFile(), + loc(qualType(hasDeclaration(Types.bind("use"))))), + this); + // Uses of typedefs: these are often transparent to hasDeclaration, so we need + // to handle them explicitly. + MatchFinder->addMatcher( + typeLoc(isExpansionInMainFile(), + loc(typedefType(hasDeclaration(Typedefs.bind("use"))))), + this); + // Uses of class templates: + // The typeLoc names the templateSpecializationType. Its declaration is the + // ClassTemplateDecl, which contains the CXXRecordDecl we want. + MatchFinder->addMatcher( + typeLoc(isExpansionInMainFile(), + loc(templateSpecializationType(hasDeclaration( + classTemplateSpecializationDecl(hasSpecializedTemplate( + classTemplateDecl(has(CXXRecords.bind("use"))))))))), + this); +} + +void FindAllSymbols::run(const MatchFinder::MatchResult &Result) { + // Ignore Results in failing TUs. + if (Result.Context->getDiagnostics().hasErrorOccurred()) { + return; + } + + SymbolInfo::Signals Signals; + const NamedDecl *ND; + if ((ND = Result.Nodes.getNodeAs<NamedDecl>("use"))) + Signals.Used = 1; + else if ((ND = Result.Nodes.getNodeAs<NamedDecl>("decl"))) + Signals.Seen = 1; + else + assert(false && "Must match a NamedDecl!"); + + const SourceManager *SM = Result.SourceManager; + if (auto Symbol = CreateSymbolInfo(ND, *SM, Collector)) { + Filename = SM->getFileEntryForID(SM->getMainFileID())->getName(); + FileSymbols[*Symbol] += Signals; + } +} + +void FindAllSymbols::onEndOfTranslationUnit() { + if (Filename != "") { + Reporter->reportSymbols(Filename, FileSymbols); + FileSymbols.clear(); + Filename = ""; + } +} + +} // namespace find_all_symbols +} // namespace clang diff --git a/clang-include-fixer/find-all-symbols/FindAllSymbols.h b/clang-include-fixer/find-all-symbols/FindAllSymbols.h new file mode 100644 index 00000000..d78da668 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/FindAllSymbols.h @@ -0,0 +1,62 @@ +//===-- FindAllSymbols.h - find all symbols----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H + +#include "SymbolInfo.h" +#include "SymbolReporter.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include <string> + +namespace clang { +namespace find_all_symbols { + +class HeaderMapCollector; + +/// \brief FindAllSymbols collects all classes, free standing functions and +/// global variables with some extra information such as the path of the header +/// file, the namespaces they are contained in, the type of variables and the +/// parameter types of functions. +/// +/// NOTE: +/// - Symbols declared in main files are not collected since they can not be +/// included. +/// - Member functions are not collected because accessing them must go +/// through the class. #include fixer only needs the class name to find +/// headers. +/// +class FindAllSymbols : public ast_matchers::MatchFinder::MatchCallback { +public: + explicit FindAllSymbols(SymbolReporter *Reporter, + HeaderMapCollector *Collector = nullptr) + : Reporter(Reporter), Collector(Collector) {} + + void registerMatchers(ast_matchers::MatchFinder *MatchFinder); + + void run(const ast_matchers::MatchFinder::MatchResult &result) override; + +protected: + void onEndOfTranslationUnit() override; + +private: + // Current source file being processed, filled by first symbol found. + std::string Filename; + // Findings for the current source file, flushed on onEndOfTranslationUnit. + SymbolInfo::SignalMap FileSymbols; + // Reporter for SymbolInfo. + SymbolReporter *const Reporter; + // A remapping header file collector allowing clients include a different + // header. + HeaderMapCollector *const Collector; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H diff --git a/clang-include-fixer/find-all-symbols/FindAllSymbolsAction.cpp b/clang-include-fixer/find-all-symbols/FindAllSymbolsAction.cpp new file mode 100644 index 00000000..9f1d31dc --- /dev/null +++ b/clang-include-fixer/find-all-symbols/FindAllSymbolsAction.cpp @@ -0,0 +1,36 @@ +//===-- FindAllSymbolsAction.cpp - find all symbols action --------*- C++ -*-===// +// +// 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 "FindAllSymbolsAction.h" +#include "FindAllMacros.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/STLExtras.h" + +namespace clang { +namespace find_all_symbols { + +FindAllSymbolsAction::FindAllSymbolsAction( + SymbolReporter *Reporter, + const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap) + : Reporter(Reporter), Collector(RegexHeaderMap), Handler(&Collector), + Matcher(Reporter, &Collector) { + Matcher.registerMatchers(&MatchFinder); +} + +std::unique_ptr<ASTConsumer> +FindAllSymbolsAction::CreateASTConsumer(CompilerInstance &Compiler, + StringRef InFile) { + Compiler.getPreprocessor().addCommentHandler(&Handler); + Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique<FindAllMacros>( + Reporter, &Compiler.getSourceManager(), &Collector)); + return MatchFinder.newASTConsumer(); +} + +} // namespace find_all_symbols +} // namespace clang diff --git a/clang-include-fixer/find-all-symbols/FindAllSymbolsAction.h b/clang-include-fixer/find-all-symbols/FindAllSymbolsAction.h new file mode 100644 index 00000000..ccffa4b3 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/FindAllSymbolsAction.h @@ -0,0 +1,62 @@ +//===-- FindAllSymbolsAction.h - find all symbols action --------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H + +#include "FindAllSymbols.h" +#include "HeaderMapCollector.h" +#include "PragmaCommentHandler.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/StringRef.h" +#include <memory> + +namespace clang { +namespace find_all_symbols { + +class FindAllSymbolsAction : public clang::ASTFrontendAction { +public: + explicit FindAllSymbolsAction( + SymbolReporter *Reporter, + const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap = nullptr); + + std::unique_ptr<clang::ASTConsumer> + CreateASTConsumer(clang::CompilerInstance &Compiler, + StringRef InFile) override; + +private: + SymbolReporter *const Reporter; + clang::ast_matchers::MatchFinder MatchFinder; + HeaderMapCollector Collector; + PragmaCommentHandler Handler; + FindAllSymbols Matcher; +}; + +class FindAllSymbolsActionFactory : public tooling::FrontendActionFactory { +public: + FindAllSymbolsActionFactory( + SymbolReporter *Reporter, + const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap = nullptr) + : Reporter(Reporter), RegexHeaderMap(RegexHeaderMap) {} + + clang::FrontendAction *create() override { + return new FindAllSymbolsAction(Reporter, RegexHeaderMap); + } + +private: + SymbolReporter *const Reporter; + const HeaderMapCollector::RegexHeaderMap *const RegexHeaderMap; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H diff --git a/clang-include-fixer/find-all-symbols/HeaderMapCollector.cpp b/clang-include-fixer/find-all-symbols/HeaderMapCollector.cpp new file mode 100644 index 00000000..6ec49cae --- /dev/null +++ b/clang-include-fixer/find-all-symbols/HeaderMapCollector.cpp @@ -0,0 +1,44 @@ +//===-- HeaderMapCoolector.h - find all symbols------------------*- C++ -*-===// +// +// 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 "HeaderMapCollector.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace find_all_symbols { + +HeaderMapCollector::HeaderMapCollector( + const RegexHeaderMap *RegexHeaderMappingTable) { + assert(RegexHeaderMappingTable); + this->RegexHeaderMappingTable.reserve(RegexHeaderMappingTable->size()); + for (const auto &Entry : *RegexHeaderMappingTable) { + this->RegexHeaderMappingTable.emplace_back(llvm::Regex(Entry.first), + Entry.second); + } +} + +llvm::StringRef +HeaderMapCollector::getMappedHeader(llvm::StringRef Header) const { + auto Iter = HeaderMappingTable.find(Header); + if (Iter != HeaderMappingTable.end()) + return Iter->second; + // If there is no complete header name mapping for this header, check the + // regex header mapping. + for (auto &Entry : RegexHeaderMappingTable) { +#ifndef NDEBUG + std::string Dummy; + assert(Entry.first.isValid(Dummy) && "Regex should never be invalid!"); +#endif + if (Entry.first.match(Header)) + return Entry.second; + } + return Header; +} + +} // namespace find_all_symbols +} // namespace clang diff --git a/clang-include-fixer/find-all-symbols/HeaderMapCollector.h b/clang-include-fixer/find-all-symbols/HeaderMapCollector.h new file mode 100644 index 00000000..21358275 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/HeaderMapCollector.h @@ -0,0 +1,56 @@ +//===-- HeaderMapCoolector.h - find all symbols------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H + +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include <string> +#include <vector> + +namespace clang { +namespace find_all_symbols { + +/// \brief HeaderMappCollector collects all remapping header files. This maps +/// complete header names or header name regex patterns to header names. +class HeaderMapCollector { +public: + typedef llvm::StringMap<std::string> HeaderMap; + typedef std::vector<std::pair<const char *, const char *>> RegexHeaderMap; + + HeaderMapCollector() = default; + explicit HeaderMapCollector(const RegexHeaderMap *RegexHeaderMappingTable); + + void addHeaderMapping(llvm::StringRef OrignalHeaderPath, + llvm::StringRef MappingHeaderPath) { + HeaderMappingTable[OrignalHeaderPath] = MappingHeaderPath; + }; + + /// Check if there is a mapping from \p Header or a regex pattern that matches + /// it to another header name. + /// \param Header A header name. + /// \return \p Header itself if there is no mapping for it; otherwise, return + /// a mapped header name. + llvm::StringRef getMappedHeader(llvm::StringRef Header) const; + +private: + /// A string-to-string map saving the mapping relationship. + HeaderMap HeaderMappingTable; + + // A map from header patterns to header names. + // The header names are not owned. This is only threadsafe because the regexes + // never fail. + mutable std::vector<std::pair<llvm::Regex, const char *>> + RegexHeaderMappingTable; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H diff --git a/clang-include-fixer/find-all-symbols/PathConfig.cpp b/clang-include-fixer/find-all-symbols/PathConfig.cpp new file mode 100644 index 00000000..4f1ebc77 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/PathConfig.cpp @@ -0,0 +1,41 @@ +//===-- PathConfig.cpp - Process paths of symbols ---------------*- C++ -*-===// +// +// +// 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 "PathConfig.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace find_all_symbols { + +std::string getIncludePath(const SourceManager &SM, SourceLocation Loc, + const HeaderMapCollector *Collector) { + llvm::StringRef FilePath; + // Walk up the include stack to skip .inc files. + while (true) { + if (!Loc.isValid() || SM.isInMainFile(Loc)) + return ""; + FilePath = SM.getFilename(Loc); + if (FilePath.empty()) + return ""; + if (!FilePath.endswith(".inc")) + break; + FileID ID = SM.getFileID(Loc); + Loc = SM.getIncludeLoc(ID); + } + + if (Collector) + FilePath = Collector->getMappedHeader(FilePath); + SmallString<256> CleanedFilePath = FilePath; + llvm::sys::path::remove_dots(CleanedFilePath, /*remove_dot_dot=*/false); + + return CleanedFilePath.str(); +} + +} // namespace find_all_symbols +} // namespace clang diff --git a/clang-include-fixer/find-all-symbols/PathConfig.h b/clang-include-fixer/find-all-symbols/PathConfig.h new file mode 100644 index 00000000..9c430f25 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/PathConfig.h @@ -0,0 +1,36 @@ +//===-- PathConfig.h - Process paths of symbols -----------------*- C++ -*-===// +// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H + +#include "HeaderMapCollector.h" +#include "clang/Basic/SourceManager.h" +#include <string> + +namespace clang { +namespace find_all_symbols { + +/// \brief This calculates the include path for \p Loc. +/// +/// \param SM SourceManager. +/// \param Loc A SourceLocation. +/// \param Collector An optional header mapping collector. +/// +/// \return The file path (or mapped file path if Collector is provided) of the +/// header that includes \p Loc. If \p Loc comes from .inc header file, \p Loc +/// is set to the location from which the .inc header file is included. If \p +/// Loc is invalid or comes from a main file, this returns an empty string. +std::string getIncludePath(const SourceManager &SM, SourceLocation Loc, + const HeaderMapCollector *Collector = nullptr); + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H diff --git a/clang-include-fixer/find-all-symbols/PragmaCommentHandler.cpp b/clang-include-fixer/find-all-symbols/PragmaCommentHandler.cpp new file mode 100644 index 00000000..49489752 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/PragmaCommentHandler.cpp @@ -0,0 +1,36 @@ +//===-- PragmaCommentHandler.cpp - find all symbols -----------------------===// +// +// 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 "PragmaCommentHandler.h" +#include "FindAllSymbols.h" +#include "HeaderMapCollector.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace find_all_symbols { +namespace { +const char IWYUPragma[] = "// IWYU pragma: private, include "; +} // namespace + +bool PragmaCommentHandler::HandleComment(Preprocessor &PP, SourceRange Range) { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + size_t Pos = Text.find(IWYUPragma); + if (Pos == StringRef::npos) + return false; + StringRef RemappingFilePath = Text.substr(Pos + std::strlen(IWYUPragma)); + Collector->addHeaderMapping( + PP.getSourceManager().getFilename(Range.getBegin()), + RemappingFilePath.trim("\"<>")); + return false; +} + +} // namespace find_all_symbols +} // namespace clang diff --git a/clang-include-fixer/find-all-symbols/PragmaCommentHandler.h b/clang-include-fixer/find-all-symbols/PragmaCommentHandler.h new file mode 100644 index 00000000..752c82f5 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/PragmaCommentHandler.h @@ -0,0 +1,40 @@ +//===-- PragmaCommentHandler.h - find all symbols----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H + +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Preprocessor.h" +#include <map> + +namespace clang { +namespace find_all_symbols { + +class HeaderMapCollector; + +/// \brief PragmaCommentHandler parses pragma comment on include files to +/// determine when we should include a different header from the header that +/// directly defines a symbol. +/// +/// Currently it only supports IWYU private pragma: +/// https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md#iwyu-pragma-private +class PragmaCommentHandler : public clang::CommentHandler { +public: + PragmaCommentHandler(HeaderMapCollector *Collector) : Collector(Collector) {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override; + +private: + HeaderMapCollector *const Collector; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H diff --git a/clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp b/clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp new file mode 100644 index 00000000..0d0bbd9f --- /dev/null +++ b/clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp @@ -0,0 +1,653 @@ +//===-- STLPostfixHeaderMap.h - hardcoded STL header map --------*- C++ -*-===// +// +// 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 "STLPostfixHeaderMap.h" + +namespace clang { +namespace find_all_symbols { + +const HeaderMapCollector::RegexHeaderMap *getSTLPostfixHeaderMap() { + static const HeaderMapCollector::RegexHeaderMap STLPostfixHeaderMap = { + {"include/__stddef_max_align_t.h$", "<cstddef>"}, + {"include/__wmmintrin_aes.h$", "<wmmintrin.h>"}, + {"include/__wmmintrin_pclmul.h$", "<wmmintrin.h>"}, + {"include/adxintrin.h$", "<immintrin.h>"}, + {"include/ammintrin.h$", "<ammintrin.h>"}, + {"include/avx2intrin.h$", "<immintrin.h>"}, + {"include/avx512bwintrin.h$", "<immintrin.h>"}, + {"include/avx512cdintrin.h$", "<immintrin.h>"}, + {"include/avx512dqintrin.h$", "<immintrin.h>"}, + {"include/avx512erintrin.h$", "<immintrin.h>"}, + {"include/avx512fintrin.h$", "<immintrin.h>"}, + {"include/avx512ifmaintrin.h$", "<immintrin.h>"}, + {"include/avx512ifmavlintrin.h$", "<immintrin.h>"}, + {"include/avx512pfintrin.h$", "<immintrin.h>"}, + {"include/avx512vbmiintrin.h$", "<immintrin.h>"}, + {"include/avx512vbmivlintrin.h$", "<immintrin.h>"}, + {"include/avx512vlbwintrin.h$", "<immintrin.h>"}, + {"include/avx512vlcdintrin.h$", "<immintrin.h>"}, + {"include/avx512vldqintrin.h$", "<immintrin.h>"}, + {"include/avx512vlintrin.h$", "<immintrin.h>"}, + {"include/avxintrin.h$", "<immintrin.h>"}, + {"include/bmi2intrin.h$", "<x86intrin.h>"}, + {"include/bmiintrin.h$", "<x86intrin.h>"}, + {"include/emmintrin.h$", "<emmintrin.h>"}, + {"include/f16cintrin.h$", "<emmintrin.h>"}, + {"include/float.h$", "<cfloat>"}, + {"include/fma4intrin.h$", "<x86intrin.h>"}, + {"include/fmaintrin.h$", "<immintrin.h>"}, + {"include/fxsrintrin.h$", "<immintrin.h>"}, + {"include/ia32intrin.h$", "<x86intrin.h>"}, + {"include/immintrin.h$", "<immintrin.h>"}, + {"include/inttypes.h$", "<cinttypes>"}, + {"include/limits.h$", "<climits>"}, + {"include/lzcntintrin.h$", "<x86intrin.h>"}, + {"include/mm3dnow.h$", "<mm3dnow.h>"}, + {"include/mm_malloc.h$", "<mm_malloc.h>"}, + {"include/mmintrin.h$", "<mmintrin>"}, + {"include/mwaitxintrin.h$", "<x86intrin.h>"}, + {"include/pkuintrin.h$", "<immintrin.h>"}, + {"include/pmmintrin.h$", "<pmmintrin.h>"}, + {"include/popcntintrin.h$", "<popcntintrin.h>"}, + {"include/prfchwintrin.h$", "<x86intrin.h>"}, + {"include/rdseedintrin.h$", "<x86intrin.h>"}, + {"include/rtmintrin.h$", "<immintrin.h>"}, + {"include/shaintrin.h$", "<immintrin.h>"}, + {"include/smmintrin.h$", "<smmintrin.h>"}, + {"include/stdalign.h$", "<cstdalign>"}, + {"include/stdarg.h$", "<cstdarg>"}, + {"include/stdbool.h$", "<cstdbool>"}, + {"include/stddef.h$", "<cstddef>"}, + {"include/stdint.h$", "<cstdint>"}, + {"include/tbmintrin.h$", "<x86intrin.h>"}, + {"include/tmmintrin.h$", "<tmmintrin.h>"}, + {"include/wmmintrin.h$", "<wmmintrin.h>"}, + {"include/x86intrin.h$", "<x86intrin.h>"}, + {"include/xmmintrin.h$", "<xmmintrin.h>"}, + {"include/xopintrin.h$", "<x86intrin.h>"}, + {"include/xsavecintrin.h$", "<immintrin.h>"}, + {"include/xsaveintrin.h$", "<immintrin.h>"}, + {"include/xsaveoptintrin.h$", "<immintrin.h>"}, + {"include/xsavesintrin.h$", "<immintrin.h>"}, + {"include/xtestintrin.h$", "<immintrin.h>"}, + {"include/_G_config.h$", "<cstdio>"}, + {"include/assert.h$", "<cassert>"}, + {"algorithm$", "<algorithm>"}, + {"array$", "<array>"}, + {"atomic$", "<atomic>"}, + {"backward/auto_ptr.h$", "<memory>"}, + {"backward/binders.h$", "<string>"}, + {"bits/algorithmfwd.h$", "<algorithm>"}, + {"bits/alloc_traits.h$", "<unordered_set>"}, + {"bits/allocator.h$", "<string>"}, + {"bits/atomic_base.h$", "<atomic>"}, + {"bits/atomic_lockfree_defines.h$", "<exception>"}, + {"bits/basic_ios.h$", "<ios>"}, + {"bits/basic_ios.tcc$", "<ios>"}, + {"bits/basic_string.h$", "<string>"}, + {"bits/basic_string.tcc$", "<string>"}, + {"bits/char_traits.h$", "<string>"}, + {"bits/codecvt.h$", "<locale>"}, + {"bits/concept_check.h$", "<numeric>"}, + {"bits/cpp_type_traits.h$", "<cmath>"}, + {"bits/cxxabi_forced.h$", "<cxxabi.h>"}, + {"bits/deque.tcc$", "<deque>"}, + {"bits/exception_defines.h$", "<exception>"}, + {"bits/exception_ptr.h$", "<exception>"}, + {"bits/forward_list.h$", "<forward_list>"}, + {"bits/forward_list.tcc$", "<forward_list>"}, + {"bits/fstream.tcc$", "<fstream>"}, + {"bits/functexcept.h$", "<list>"}, + {"bits/functional_hash.h$", "<string>"}, + {"bits/gslice.h$", "<valarray>"}, + {"bits/gslice_array.h$", "<valarray>"}, + {"bits/hash_bytes.h$", "<typeinfo>"}, + {"bits/hashtable.h$", "<unordered_set>"}, + {"bits/hashtable_policy.h$", "<unordered_set>"}, + {"bits/indirect_array.h$", "<valarray>"}, + {"bits/ios_base.h$", "<ios>"}, + {"bits/istream.tcc$", "<istream>"}, + {"bits/list.tcc$", "<list>"}, + {"bits/locale_classes.h$", "<locale>"}, + {"bits/locale_classes.tcc$", "<locale>"}, + {"bits/locale_facets.h$", "<locale>"}, + {"bits/locale_facets.tcc$", "<locale>"}, + {"bits/locale_facets_nonio.h$", "<locale>"}, + {"bits/locale_facets_nonio.tcc$", "<locale>"}, + {"bits/localefwd.h$", "<locale>"}, + {"bits/mask_array.h$", "<valarray>"}, + {"bits/memoryfwd.h$", "<memory>"}, + {"bits/move.h$", "<utility>"}, + {"bits/nested_exception.h$", "<exception>"}, + {"bits/ostream.tcc$", "<ostream>"}, + {"bits/ostream_insert.h$", "<ostream>"}, + {"bits/postypes.h$", "<iosfwd>"}, + {"bits/ptr_traits.h$", "<memory>"}, + {"bits/random.h$", "<random>"}, + {"bits/random.tcc$", "<random>"}, + {"bits/range_access.h$", "<iterator>"}, + {"bits/regex.h$", "<regex>"}, + {"bits/regex_compiler.h$", "<regex>"}, + {"bits/regex_constants.h$", "<regex>"}, + {"bits/regex_cursor.h$", "<regex>"}, + {"bits/regex_error.h$", "<regex>"}, + {"bits/regex_grep_matcher.h$", "<regex>"}, + {"bits/regex_grep_matcher.tcc$", "<regex>"}, + {"bits/regex_nfa.h$", "<regex>"}, + {"bits/shared_ptr.h$", "<memory>"}, + {"bits/shared_ptr_base.h$", "<memory>"}, + {"bits/slice_array.h$", "<valarray>"}, + {"bits/sstream.tcc$", "<sstream>"}, + {"bits/stl_algo.h$", "<algorithm>"}, + {"bits/stl_algobase.h$", "<list>"}, + {"bits/stl_bvector.h$", "<vector>"}, + {"bits/stl_construct.h$", "<deque>"}, + {"bits/stl_deque.h$", "<deque>"}, + {"bits/stl_function.h$", "<string>"}, + {"bits/stl_heap.h$", "<queue>"}, + {"bits/stl_iterator.h$", "<iterator>"}, + {"bits/stl_iterator_base_funcs.h$", "<iterator>"}, + {"bits/stl_iterator_base_types.h$", "<numeric>"}, + {"bits/stl_list.h$", "<list>"}, + {"bits/stl_map.h$", "<map>"}, + {"bits/stl_multimap.h$", "<map>"}, + {"bits/stl_multiset.h$", "<set>"}, + {"bits/stl_numeric.h$", "<numeric>"}, + {"bits/stl_pair.h$", "<utility>"}, + {"bits/stl_queue.h$", "<queue>"}, + {"bits/stl_raw_storage_iter.h$", "<memory>"}, + {"bits/stl_relops.h$", "<utility>"}, + {"bits/stl_set.h$", "<set>"}, + {"bits/stl_stack.h$", "<stack>"}, + {"bits/stl_tempbuf.h$", "<memory>"}, + {"bits/stl_tree.h$", "<map>"}, + {"bits/stl_uninitialized.h$", "<deque>"}, + {"bits/stl_vector.h$", "<vector>"}, + {"bits/stream_iterator.h$", "<iterator>"}, + {"bits/streambuf.tcc$", "<streambuf>"}, + {"bits/streambuf_iterator.h$", "<iterator>"}, + {"bits/stringfwd.h$", "<string>"}, + {"bits/unique_ptr.h$", "<memory>"}, + {"bits/unordered_map.h$", "<unordered_map>"}, + {"bits/unordered_set.h$", "<unordered_set>"}, + {"bits/uses_allocator.h$", "<tuple>"}, + {"bits/valarray_after.h$", "<valarray>"}, + {"bits/valarray_array.h$", "<valarray>"}, + {"bits/valarray_array.tcc$", "<valarray>"}, + {"bits/valarray_before.h$", "<valarray>"}, + {"bits/vector.tcc$", "<vector>"}, + {"bitset$", "<bitset>"}, + {"ccomplex$", "<ccomplex>"}, + {"cctype$", "<cctype>"}, + {"cerrno$", "<cerrno>"}, + {"cfenv$", "<cfenv>"}, + {"cfloat$", "<cfloat>"}, + {"chrono$", "<chrono>"}, + {"cinttypes$", "<cinttypes>"}, + {"climits$", "<climits>"}, + {"clocale$", "<clocale>"}, + {"cmath$", "<cmath>"}, + {"complex$", "<complex>"}, + {"complex.h$", "<complex.h>"}, + {"condition_variable$", "<condition_variable>"}, + {"csetjmp$", "<csetjmp>"}, + {"csignal$", "<csignal>"}, + {"cstdalign$", "<cstdalign>"}, + {"cstdarg$", "<cstdarg>"}, + {"cstdbool$", "<cstdbool>"}, + {"cstdint$", "<cstdint>"}, + {"cstdio$", "<cstdio>"}, + {"cstdlib$", "<cstdlib>"}, + {"cstring$", "<cstring>"}, + {"ctgmath$", "<ctgmath>"}, + {"ctime$", "<ctime>"}, + {"cwchar$", "<cwchar>"}, + {"cwctype$", "<cwctype>"}, + {"cxxabi.h$", "<cxxabi.h>"}, + {"debug/debug.h$", "<numeric>"}, + {"debug/map.h$", "<map>"}, + {"debug/multimap.h$", "<multimap>"}, + {"debug/multiset.h$", "<multiset>"}, + {"debug/set.h$", "<set>"}, + {"deque$", "<deque>"}, + {"exception$", "<exception>"}, + {"ext/alloc_traits.h$", "<deque>"}, + {"ext/atomicity.h$", "<memory>"}, + {"ext/concurrence.h$", "<memory>"}, + {"ext/new_allocator.h$", "<string>"}, + {"ext/numeric_traits.h$", "<list>"}, + {"ext/string_conversions.h$", "<string>"}, + {"ext/type_traits.h$", "<cmath>"}, + {"fenv.h$", "<fenv.h>"}, + {"forward_list$", "<forward_list>"}, + {"fstream$", "<fstream>"}, + {"functional$", "<functional>"}, + {"future$", "<future>"}, + {"initializer_list$", "<initializer_list>"}, + {"iomanip$", "<iomanip>"}, + {"ios$", "<ios>"}, + {"iosfwd$", "<iosfwd>"}, + {"iostream$", "<iostream>"}, + {"istream$", "<istream>"}, + {"iterator$", "<iterator>"}, + {"limits$", "<limits>"}, + {"list$", "<list>"}, + {"locale$", "<locale>"}, + {"map$", "<map>"}, + {"memory$", "<memory>"}, + {"mutex$", "<mutex>"}, + {"new$", "<new>"}, + {"numeric$", "<numeric>"}, + {"ostream$", "<ostream>"}, + {"queue$", "<queue>"}, + {"random$", "<random>"}, + {"ratio$", "<ratio>"}, + {"regex$", "<regex>"}, + {"scoped_allocator$", "<scoped_allocator>"}, + {"set$", "<set>"}, + {"sstream$", "<sstream>"}, + {"stack$", "<stack>"}, + {"stdexcept$", "<stdexcept>"}, + {"streambuf$", "<streambuf>"}, + {"string$", "<string>"}, + {"system_error$", "<system_error>"}, + {"tgmath.h$", "<tgmath.h>"}, + {"thread$", "<thread>"}, + {"tuple$", "<tuple>"}, + {"type_traits$", "<type_traits>"}, + {"typeindex$", "<typeindex>"}, + {"typeinfo$", "<typeinfo>"}, + {"unordered_map$", "<unordered_map>"}, + {"unordered_set$", "<unordered_set>"}, + {"utility$", "<utility>"}, + {"valarray$", "<valarray>"}, + {"vector$", "<vector>"}, + {"include/complex.h$", "<complex.h>"}, + {"include/ctype.h$", "<cctype>"}, + {"include/errno.h$", "<cerrno>"}, + {"include/fenv.h$", "<fenv.h>"}, + {"include/inttypes.h$", "<cinttypes>"}, + {"include/libio.h$", "<cstdio>"}, + {"include/limits.h$", "<climits>"}, + {"include/locale.h$", "<clocale>"}, + {"include/math.h$", "<cmath>"}, + {"include/setjmp.h$", "<csetjmp>"}, + {"include/signal.h$", "<csignal>"}, + {"include/stdint.h$", "<cstdint>"}, + {"include/stdio.h$", "<cstdio>"}, + {"include/stdlib.h$", "<cstdlib>"}, + {"include/string.h$", "<cstring>"}, + {"include/time.h$", "<ctime>"}, + {"include/wchar.h$", "<cwchar>"}, + {"include/wctype.h$", "<cwctype>"}, + {"bits/cmathcalls.h$", "<complex.h>"}, + {"bits/errno.h$", "<cerrno>"}, + {"bits/fenv.h$", "<fenv.h>"}, + {"bits/huge_val.h$", "<cmath>"}, + {"bits/huge_valf.h$", "<cmath>"}, + {"bits/huge_vall.h$", "<cmath>"}, + {"bits/inf.h$", "<cmath>"}, + {"bits/local_lim.h$", "<climits>"}, + {"bits/locale.h$", "<clocale>"}, + {"bits/mathcalls.h$", "<math.h>"}, + {"bits/mathdef.h$", "<cmath>"}, + {"bits/nan.h$", "<cmath>"}, + {"bits/posix1_lim.h$", "<climits>"}, + {"bits/posix2_lim.h$", "<climits>"}, + {"bits/setjmp.h$", "<csetjmp>"}, + {"bits/sigaction.h$", "<csignal>"}, + {"bits/sigcontext.h$", "<csignal>"}, + {"bits/siginfo.h$", "<csignal>"}, + {"bits/signum.h$", "<csignal>"}, + {"bits/sigset.h$", "<csignal>"}, + {"bits/sigstack.h$", "<csignal>"}, + {"bits/stdio_lim.h$", "<cstdio>"}, + {"bits/sys_errlist.h$", "<cstdio>"}, + {"bits/time.h$", "<ctime>"}, + {"bits/timex.h$", "<ctime>"}, + {"bits/typesizes.h$", "<cstdio>"}, + {"bits/wchar.h$", "<cwchar>"}, + {"bits/wordsize.h$", "<csetjmp>"}, + {"bits/xopen_lim.h$", "<climits>"}, + {"include/xlocale.h$", "<cstring>"}, + {"bits/atomic_word.h$", "<memory>"}, + {"bits/basic_file.h$", "<fstream>"}, + {"bits/c\\+\\+allocator.h$", "<string>"}, + {"bits/c\\+\\+config.h$", "<iosfwd>"}, + {"bits/c\\+\\+io.h$", "<ios>"}, + {"bits/c\\+\\+locale.h$", "<locale>"}, + {"bits/cpu_defines.h$", "<iosfwd>"}, + {"bits/ctype_base.h$", "<locale>"}, + {"bits/cxxabi_tweaks.h$", "<cxxabi.h>"}, + {"bits/error_constants.h$", "<system_error>"}, + {"bits/gthr-default.h$", "<memory>"}, + {"bits/gthr.h$", "<memory>"}, + {"bits/opt_random.h$", "<random>"}, + {"bits/os_defines.h$", "<iosfwd>"}, + // GNU C headers + {"include/aio.h$", "<aio.h>"}, + {"include/aliases.h$", "<aliases.h>"}, + {"include/alloca.h$", "<alloca.h>"}, + {"include/ar.h$", "<ar.h>"}, + {"include/argp.h$", "<argp.h>"}, + {"include/argz.h$", "<argz.h>"}, + {"include/arpa/nameser.h$", "<resolv.h>"}, + {"include/arpa/nameser_compat.h$", "<resolv.h>"}, + {"include/byteswap.h$", "<byteswap.h>"}, + {"include/cpio.h$", "<cpio.h>"}, + {"include/crypt.h$", "<crypt.h>"}, + {"include/dirent.h$", "<dirent.h>"}, + {"include/dlfcn.h$", "<dlfcn.h>"}, + {"include/elf.h$", "<elf.h>"}, + {"include/endian.h$", "<endian.h>"}, + {"include/envz.h$", "<envz.h>"}, + {"include/err.h$", "<err.h>"}, + {"include/error.h$", "<error.h>"}, + {"include/execinfo.h$", "<execinfo.h>"}, + {"include/fcntl.h$", "<fcntl.h>"}, + {"include/features.h$", "<features.h>"}, + {"include/fenv.h$", "<fenv.h>"}, + {"include/fmtmsg.h$", "<fmtmsg.h>"}, + {"include/fnmatch.h$", "<fnmatch.h>"}, + {"include/fstab.h$", "<fstab.h>"}, + {"include/fts.h$", "<fts.h>"}, + {"include/ftw.h$", "<ftw.h>"}, + {"include/gconv.h$", "<gconv.h>"}, + {"include/getopt.h$", "<getopt.h>"}, + {"include/glob.h$", "<glob.h>"}, + {"include/grp.h$", "<grp.h>"}, + {"include/gshadow.h$", "<gshadow.h>"}, + {"include/iconv.h$", "<iconv.h>"}, + {"include/ifaddrs.h$", "<ifaddrs.h>"}, + {"include/kdb.h$", "<kdb.h>"}, + {"include/langinfo.h$", "<langinfo.h>"}, + {"include/libgen.h$", "<libgen.h>"}, + {"include/libintl.h$", "<libintl.h>"}, + {"include/link.h$", "<link.h>"}, + {"include/malloc.h$", "<malloc.h>"}, + {"include/mcheck.h$", "<mcheck.h>"}, + {"include/memory.h$", "<memory.h>"}, + {"include/mntent.h$", "<mntent.h>"}, + {"include/monetary.h$", "<monetary.h>"}, + {"include/mqueue.h$", "<mqueue.h>"}, + {"include/netdb.h$", "<netdb.h>"}, + {"include/netinet/in.h$", "<netinet/in.h>"}, + {"include/nl_types.h$", "<nl_types.h>"}, + {"include/nss.h$", "<nss.h>"}, + {"include/obstack.h$", "<obstack.h>"}, + {"include/panel.h$", "<panel.h>"}, + {"include/paths.h$", "<paths.h>"}, + {"include/printf.h$", "<printf.h>"}, + {"include/profile.h$", "<profile.h>"}, + {"include/pthread.h$", "<pthread.h>"}, + {"include/pty.h$", "<pty.h>"}, + {"include/pwd.h$", "<pwd.h>"}, + {"include/re_comp.h$", "<re_comp.h>"}, + {"include/regex.h$", "<regex.h>"}, + {"include/regexp.h$", "<regexp.h>"}, + {"include/resolv.h$", "<resolv.h>"}, + {"include/rpc/netdb.h$", "<netdb.h>"}, + {"include/sched.h$", "<sched.h>"}, + {"include/search.h$", "<search.h>"}, + {"include/semaphore.h$", "<semaphore.h>"}, + {"include/sgtty.h$", "<sgtty.h>"}, + {"include/shadow.h$", "<shadow.h>"}, + {"include/spawn.h$", "<spawn.h>"}, + {"include/stab.h$", "<stab.h>"}, + {"include/stdc-predef.h$", "<stdc-predef.h>"}, + {"include/stdio_ext.h$", "<stdio_ext.h>"}, + {"include/strings.h$", "<strings.h>"}, + {"include/stropts.h$", "<stropts.h>"}, + {"include/sudo_plugin.h$", "<sudo_plugin.h>"}, + {"include/sysexits.h$", "<sysexits.h>"}, + {"include/tar.h$", "<tar.h>"}, + {"include/tcpd.h$", "<tcpd.h>"}, + {"include/term.h$", "<term.h>"}, + {"include/term_entry.h$", "<term_entry.h>"}, + {"include/termcap.h$", "<termcap.h>"}, + {"include/termios.h$", "<termios.h>"}, + {"include/thread_db.h$", "<thread_db.h>"}, + {"include/tic.h$", "<tic.h>"}, + {"include/ttyent.h$", "<ttyent.h>"}, + {"include/uchar.h$", "<uchar.h>"}, + {"include/ucontext.h$", "<ucontext.h>"}, + {"include/ulimit.h$", "<ulimit.h>"}, + {"include/unctrl.h$", "<unctrl.h>"}, + {"include/unistd.h$", "<unistd.h>"}, + {"include/utime.h$", "<utime.h>"}, + {"include/utmp.h$", "<utmp.h>"}, + {"include/utmpx.h$", "<utmpx.h>"}, + {"include/values.h$", "<values.h>"}, + {"include/wordexp.h$", "<wordexp.h>"}, + {"fpu_control.h$", "<fpu_control.h>"}, + {"ieee754.h$", "<ieee754.h>"}, + {"include/xlocale.h$", "<xlocale.h>"}, + {"gnu/lib-names.h$", "<gnu/lib-names.h>"}, + {"gnu/libc-version.h$", "<gnu/libc-version.h>"}, + {"gnu/option-groups.h$", "<gnu/option-groups.h>"}, + {"gnu/stubs-32.h$", "<gnu/stubs-32.h>"}, + {"gnu/stubs-64.h$", "<gnu/stubs-64.h>"}, + {"gnu/stubs-x32.h$", "<gnu/stubs-x32.h>"}, + {"include/rpc/auth_des.h$", "<rpc/auth_des.h>"}, + {"include/rpc/rpc_msg.h$", "<rpc/rpc_msg.h>"}, + {"include/rpc/pmap_clnt.h$", "<rpc/pmap_clnt.h>"}, + {"include/rpc/rpc.h$", "<rpc/rpc.h>"}, + {"include/rpc/types.h$", "<rpc/types.h>"}, + {"include/rpc/auth_unix.h$", "<rpc/auth_unix.h>"}, + {"include/rpc/key_prot.h$", "<rpc/key_prot.h>"}, + {"include/rpc/pmap_prot.h$", "<rpc/pmap_prot.h>"}, + {"include/rpc/auth.h$", "<rpc/auth.h>"}, + {"include/rpc/svc_auth.h$", "<rpc/svc_auth.h>"}, + {"include/rpc/xdr.h$", "<rpc/xdr.h>"}, + {"include/rpc/pmap_rmt.h$", "<rpc/pmap_rmt.h>"}, + {"include/rpc/des_crypt.h$", "<rpc/des_crypt.h>"}, + {"include/rpc/svc.h$", "<rpc/svc.h>"}, + {"include/rpc/rpc_des.h$", "<rpc/rpc_des.h>"}, + {"include/rpc/clnt.h$", "<rpc/clnt.h>"}, + {"include/scsi/scsi.h$", "<scsi/scsi.h>"}, + {"include/scsi/sg.h$", "<scsi/sg.h>"}, + {"include/scsi/scsi_ioctl.h$", "<scsi/scsi_ioctl>"}, + {"include/netrose/rose.h$", "<netrose/rose.h>"}, + {"include/nfs/nfs.h$", "<nfs/nfs.h>"}, + {"include/netatalk/at.h$", "<netatalk/at.h>"}, + {"include/netinet/ether.h$", "<netinet/ether.h>"}, + {"include/netinet/icmp6.h$", "<netinet/icmp6.h>"}, + {"include/netinet/if_ether.h$", "<netinet/if_ether.h>"}, + {"include/netinet/if_fddi.h$", "<netinet/if_fddi.h>"}, + {"include/netinet/if_tr.h$", "<netinet/if_tr.h>"}, + {"include/netinet/igmp.h$", "<netinet/igmp.h>"}, + {"include/netinet/in.h$", "<netinet/in.h>"}, + {"include/netinet/in_systm.h$", "<netinet/in_systm.h>"}, + {"include/netinet/ip.h$", "<netinet/ip.h>"}, + {"include/netinet/ip6.h$", "<netinet/ip6.h>"}, + {"include/netinet/ip_icmp.h$", "<netinet/ip_icmp.h>"}, + {"include/netinet/tcp.h$", "<netinet/tcp.h>"}, + {"include/netinet/udp.h$", "<netinet/udp.h>"}, + {"include/netrom/netrom.h$", "<netrom/netrom.h>"}, + {"include/protocols/routed.h$", "<protocols/routed.h>"}, + {"include/protocols/rwhod.h$", "<protocols/rwhod.h>"}, + {"include/protocols/talkd.h$", "<protocols/talkd.h>"}, + {"include/protocols/timed.h$", "<protocols/timed.h>"}, + {"include/rpcsvc/klm_prot.x$", "<rpcsvc/klm_prot.x>"}, + {"include/rpcsvc/rstat.h$", "<rpcsvc/rstat.h>"}, + {"include/rpcsvc/spray.x$", "<rpcsvc/spray.x>"}, + {"include/rpcsvc/nlm_prot.x$", "<rpcsvc/nlm_prot.x>"}, + {"include/rpcsvc/nis_callback.x$", "<rpcsvc/nis_callback.x>"}, + {"include/rpcsvc/yp.h$", "<rpcsvc/yp.h>"}, + {"include/rpcsvc/yp.x$", "<rpcsvc/yp.x>"}, + {"include/rpcsvc/nfs_prot.h$", "<rpcsvc/nfs_prot.h>"}, + {"include/rpcsvc/rex.h$", "<rpcsvc/rex.h>"}, + {"include/rpcsvc/yppasswd.h$", "<rpcsvc/yppasswd.h>"}, + {"include/rpcsvc/rex.x$", "<rpcsvc/rex.x>"}, + {"include/rpcsvc/nis_tags.h$", "<rpcsvc/nis_tags.h>"}, + {"include/rpcsvc/nis_callback.h$", "<rpcsvc/nis_callback.h>"}, + {"include/rpcsvc/nfs_prot.x$", "<rpcsvc/nfs_prot.x>"}, + {"include/rpcsvc/bootparam_prot.x$", "<rpcsvc/bootparam_prot.x>"}, + {"include/rpcsvc/rusers.x$", "<rpcsvc/rusers.x>"}, + {"include/rpcsvc/rquota.x$", "<rpcsvc/rquota.x>"}, + {"include/rpcsvc/nis.h$", "<rpcsvc/nis.h>"}, + {"include/rpcsvc/nislib.h$", "<rpcsvc/nislib.h>"}, + {"include/rpcsvc/ypupd.h$", "<rpcsvc/ypupd.h>"}, + {"include/rpcsvc/bootparam.h$", "<rpcsvc/bootparam.h>"}, + {"include/rpcsvc/spray.h$", "<rpcsvc/spray.h>"}, + {"include/rpcsvc/key_prot.h$", "<rpcsvc/key_prot.h>"}, + {"include/rpcsvc/klm_prot.h$", "<rpcsvc/klm_prot.h>"}, + {"include/rpcsvc/sm_inter.h$", "<rpcsvc/sm_inter.h>"}, + {"include/rpcsvc/nlm_prot.h$", "<rpcsvc/nlm_prot.h>"}, + {"include/rpcsvc/yp_prot.h$", "<rpcsvc/yp_prot.h>"}, + {"include/rpcsvc/ypclnt.h$", "<rpcsvc/ypclnt.h>"}, + {"include/rpcsvc/rstat.x$", "<rpcsvc/rstat.x>"}, + {"include/rpcsvc/rusers.h$", "<rpcsvc/rusers.h>"}, + {"include/rpcsvc/key_prot.x$", "<rpcsvc/key_prot.x>"}, + {"include/rpcsvc/sm_inter.x$", "<rpcsvc/sm_inter.x>"}, + {"include/rpcsvc/rquota.h$", "<rpcsvc/rquota.h>"}, + {"include/rpcsvc/nis.x$", "<rpcsvc/nis.x>"}, + {"include/rpcsvc/bootparam_prot.h$", "<rpcsvc/bootparam_prot.h>"}, + {"include/rpcsvc/mount.h$", "<rpcsvc/mount.h>"}, + {"include/rpcsvc/mount.x$", "<rpcsvc/mount.x>"}, + {"include/rpcsvc/nis_object.x$", "<rpcsvc/nis_object.x>"}, + {"include/rpcsvc/yppasswd.x$", "<rpcsvc/yppasswd.x>"}, + {"sys/acct.h$", "<sys/acct.h>"}, + {"sys/auxv.h$", "<sys/auxv.h>"}, + {"sys/cdefs.h$", "<sys/cdefs.h>"}, + {"sys/debugreg.h$", "<sys/debugreg.h>"}, + {"sys/dir.h$", "<sys/dir.h>"}, + {"sys/elf.h$", "<sys/elf.h>"}, + {"sys/epoll.h$", "<sys/epoll.h>"}, + {"sys/eventfd.h$", "<sys/eventfd.h>"}, + {"sys/fanotify.h$", "<sys/fanotify.h>"}, + {"sys/file.h$", "<sys/file.h>"}, + {"sys/fsuid.h$", "<sys/fsuid.h>"}, + {"sys/gmon.h$", "<sys/gmon.h>"}, + {"sys/gmon_out.h$", "<sys/gmon_out.h>"}, + {"sys/inotify.h$", "<sys/inotify.h>"}, + {"sys/io.h$", "<sys/io.h>"}, + {"sys/ioctl.h$", "<sys/ioctl.h>"}, + {"sys/ipc.h$", "<sys/ipc.h>"}, + {"sys/kd.h$", "<sys/kd.h>"}, + {"sys/kdaemon.h$", "<sys/kdaemon.h>"}, + {"sys/klog.h$", "<sys/klog.h>"}, + {"sys/mman.h$", "<sys/mman.h>"}, + {"sys/mount.h$", "<sys/mount.h>"}, + {"sys/msg.h$", "<sys/msg.h>"}, + {"sys/mtio.h$", "<sys/mtio.h>"}, + {"sys/param.h$", "<sys/param.h>"}, + {"sys/pci.h$", "<sys/pci.h>"}, + {"sys/perm.h$", "<sys/perm.h>"}, + {"sys/personality.h$", "<sys/personality.h>"}, + {"sys/poll.h$", "<sys/poll.h>"}, + {"sys/prctl.h$", "<sys/prctl.h>"}, + {"sys/procfs.h$", "<sys/procfs.h>"}, + {"sys/profil.h$", "<sys/profil.h>"}, + {"sys/ptrace.h$", "<sys/ptrace.h>"}, + {"sys/queue.h$", "<sys/queue.h>"}, + {"sys/quota.h$", "<sys/quota.h>"}, + {"sys/raw.h$", "<sys/raw.h>"}, + {"sys/reboot.h$", "<sys/reboot.h>"}, + {"sys/reg.h$", "<sys/reg.h>"}, + {"sys/resource.h$", "<sys/resource.h>"}, + {"sys/select.h$", "<sys/select.h>"}, + {"sys/sem.h$", "<sys/sem.h>"}, + {"sys/sendfile.h$", "<sys/sendfile.h>"}, + {"sys/shm.h$", "<sys/shm.h>"}, + {"sys/signalfd.h$", "<sys/signalfd.h>"}, + {"sys/socket.h$", "<sys/socket.h>"}, + {"sys/stat.h$", "<sys/stat.h>"}, + {"sys/statfs.h$", "<sys/statfs.h>"}, + {"sys/statvfs.h$", "<sys/statvfs.h>"}, + {"sys/swap.h$", "<sys/swap.h>"}, + {"sys/syscall.h$", "<sys/syscall.h>"}, + {"sys/sysctl.h$", "<sys/sysctl.h>"}, + {"sys/sysinfo.h$", "<sys/sysinfo.h>"}, + {"sys/syslog.h$", "<sys/syslog.h>"}, + {"sys/sysmacros.h$", "<sys/sysmacros.h>"}, + {"sys/termios.h$", "<sys/termios.h>"}, + {"sys/time.h$", "<sys/select.h>"}, + {"sys/timeb.h$", "<sys/timeb.h>"}, + {"sys/timerfd.h$", "<sys/timerfd.h>"}, + {"sys/times.h$", "<sys/times.h>"}, + {"sys/timex.h$", "<sys/timex.h>"}, + {"sys/ttychars.h$", "<sys/ttychars.h>"}, + {"sys/ttydefaults.h$", "<sys/ttydefaults.h>"}, + {"sys/types.h$", "<sys/types.h>"}, + {"sys/ucontext.h$", "<sys/ucontext.h>"}, + {"sys/uio.h$", "<sys/uio.h>"}, + {"sys/un.h$", "<sys/un.h>"}, + {"sys/user.h$", "<sys/user.h>"}, + {"sys/ustat.h$", "<sys/ustat.h>"}, + {"sys/utsname.h$", "<sys/utsname.h>"}, + {"sys/vlimit.h$", "<sys/vlimit.h>"}, + {"sys/vm86.h$", "<sys/vm86.h>"}, + {"sys/vtimes.h$", "<sys/vtimes.h>"}, + {"sys/wait.h$", "<sys/wait.h>"}, + {"sys/xattr.h$", "<sys/xattr.h>"}, + {"bits/epoll.h$", "<sys/epoll.h>"}, + {"bits/eventfd.h$", "<sys/eventfd.h>"}, + {"bits/inotify.h$", "<sys/inotify.h>"}, + {"bits/ipc.h$", "<sys/ipc.h>"}, + {"bits/ipctypes.h$", "<sys/ipc.h>"}, + {"bits/mman-linux.h$", "<sys/mman.h>"}, + {"bits/mman.h$", "<sys/mman.h>"}, + {"bits/msq.h$", "<sys/msg.h>"}, + {"bits/resource.h$", "<sys/resource.h>"}, + {"bits/sem.h$", "<sys/sem.h>"}, + {"bits/shm.h$", "<sys/shm.h>"}, + {"bits/signalfd.h$", "<sys/signalfd.h>"}, + {"bits/statfs.h$", "<sys/statfs.h>"}, + {"bits/statvfs.h$", "<sys/statvfs.h>"}, + {"bits/timerfd.h$", "<sys/timerfd.h>"}, + {"bits/utsname.h$", "<sys/utsname.h>"}, + {"bits/auxv.h$", "<sys/auxv.h>"}, + {"bits/byteswap-16.h$", "<byteswap.h>"}, + {"bits/byteswap.h$", "<byteswap.h>"}, + {"bits/confname.h$", "<unistd.h>"}, + {"bits/dirent.h$", "<dirent.h>"}, + {"bits/dlfcn.h$", "<dlfcn.h>"}, + {"bits/elfclass.h$", "<link.h>"}, + {"bits/endian.h$", "<endian.h>"}, + {"bits/environments.h$", "<unistd.h>"}, + {"bits/fcntl-linux.h$", "<fcntl.h>"}, + {"bits/fcntl.h$", "<fcntl.h>"}, + {"bits/in.h$", "<netinet/in.h>"}, + {"bits/ioctl-types.h$", "<sys/ioctl.h>"}, + {"bits/ioctls.h$", "<sys/ioctl.h>"}, + {"bits/link.h$", "<link.h>"}, + {"bits/mqueue.h$", "<mqueue.h>"}, + {"bits/netdb.h$", "<netdb.h>"}, + {"bits/param.h$", "<sys/param.h>"}, + {"bits/poll.h$", "<sys/poll.h>"}, + {"bits/posix_opt.h$", "<bits/posix_opt.h>"}, + {"bits/pthreadtypes.h$", "<pthread.h>"}, + {"bits/sched.h$", "<sched.h>"}, + {"bits/select.h$", "<sys/select.h>"}, + {"bits/semaphore.h$", "<semaphore.h>"}, + {"bits/sigthread.h$", "<pthread.h>"}, + {"bits/sockaddr.h$", "<sys/socket.h>"}, + {"bits/socket.h$", "<sys/socket.h>"}, + {"bits/socket_type.h$", "<sys/socket.h>"}, + {"bits/stab.def$", "<stab.h>"}, + {"bits/stat.h$", "<sys/stat.h>"}, + {"bits/stropts.h$", "<stropts.h>"}, + {"bits/syscall.h$", "<sys/syscall.h>"}, + {"bits/syslog-path.h$", "<sys/syslog.h>"}, + {"bits/termios.h$", "<termios.h>"}, + {"bits/types.h$", "<sys/types.h>"}, + {"bits/typesizes.h$", "<sys/types.h>"}, + {"bits/uio.h$", "<sys/uio.h>"}, + {"bits/ustat.h$", "<sys/ustat.h>"}, + {"bits/utmp.h$", "<utmp.h>"}, + {"bits/utmpx.h$", "<utmpx.h>"}, + {"bits/waitflags.h$", "<sys/wait.h>"}, + {"bits/waitstatus.h$", "<sys/wait.h>"}, + {"bits/xtitypes.h$", "<stropts.h>"}, + }; + return &STLPostfixHeaderMap; +} + +} // namespace find_all_symbols +} // namespace clang diff --git a/clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.h b/clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.h new file mode 100644 index 00000000..49bc5f30 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.h @@ -0,0 +1,22 @@ +//===-- STLPostfixHeaderMap.h - hardcoded header map for STL ----*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H + +#include "HeaderMapCollector.h" + +namespace clang { +namespace find_all_symbols { + +const HeaderMapCollector::RegexHeaderMap *getSTLPostfixHeaderMap(); + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H diff --git a/clang-include-fixer/find-all-symbols/SymbolInfo.cpp b/clang-include-fixer/find-all-symbols/SymbolInfo.cpp new file mode 100644 index 00000000..e5b4dba4 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/SymbolInfo.cpp @@ -0,0 +1,136 @@ +//===-- SymbolInfo.cpp - Symbol Info ----------------------------*- C++ -*-===// +// +// 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 "SymbolInfo.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +using llvm::yaml::MappingTraits; +using llvm::yaml::IO; +using llvm::yaml::Input; +using ContextType = clang::find_all_symbols::SymbolInfo::ContextType; +using clang::find_all_symbols::SymbolInfo; +using clang::find_all_symbols::SymbolAndSignals; +using SymbolKind = clang::find_all_symbols::SymbolInfo::SymbolKind; + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(SymbolAndSignals) +LLVM_YAML_IS_SEQUENCE_VECTOR(SymbolInfo::Context) + +namespace llvm { +namespace yaml { +template <> struct MappingTraits<SymbolAndSignals> { + static void mapping(IO &io, SymbolAndSignals &Symbol) { + io.mapRequired("Name", Symbol.Symbol.Name); + io.mapRequired("Contexts", Symbol.Symbol.Contexts); + io.mapRequired("FilePath", Symbol.Symbol.FilePath); + io.mapRequired("Type", Symbol.Symbol.Type); + io.mapRequired("Seen", Symbol.Signals.Seen); + io.mapRequired("Used", Symbol.Signals.Used); + } +}; + +template <> struct ScalarEnumerationTraits<ContextType> { + static void enumeration(IO &io, ContextType &value) { + io.enumCase(value, "Record", ContextType::Record); + io.enumCase(value, "Namespace", ContextType::Namespace); + io.enumCase(value, "EnumDecl", ContextType::EnumDecl); + } +}; + +template <> struct ScalarEnumerationTraits<SymbolKind> { + static void enumeration(IO &io, SymbolKind &value) { + io.enumCase(value, "Variable", SymbolKind::Variable); + io.enumCase(value, "Function", SymbolKind::Function); + io.enumCase(value, "Class", SymbolKind::Class); + io.enumCase(value, "TypedefName", SymbolKind::TypedefName); + io.enumCase(value, "EnumDecl", SymbolKind::EnumDecl); + io.enumCase(value, "EnumConstantDecl", SymbolKind::EnumConstantDecl); + io.enumCase(value, "Macro", SymbolKind::Macro); + io.enumCase(value, "Unknown", SymbolKind::Unknown); + } +}; + +template <> struct MappingTraits<SymbolInfo::Context> { + static void mapping(IO &io, SymbolInfo::Context &Context) { + io.mapRequired("ContextType", Context.first); + io.mapRequired("ContextName", Context.second); + } +}; + +} // namespace yaml +} // namespace llvm + +namespace clang { +namespace find_all_symbols { + +SymbolInfo::SymbolInfo(llvm::StringRef Name, SymbolKind Type, + llvm::StringRef FilePath, + const std::vector<Context> &Contexts) + : Name(Name), Type(Type), FilePath(FilePath), Contexts(Contexts) {} + +bool SymbolInfo::operator==(const SymbolInfo &Symbol) const { + return std::tie(Name, Type, FilePath, Contexts) == + std::tie(Symbol.Name, Symbol.Type, Symbol.FilePath, Symbol.Contexts); +} + +bool SymbolInfo::operator<(const SymbolInfo &Symbol) const { + return std::tie(Name, Type, FilePath, Contexts) < + std::tie(Symbol.Name, Symbol.Type, Symbol.FilePath, Symbol.Contexts); +} + +std::string SymbolInfo::getQualifiedName() const { + std::string QualifiedName = Name; + for (const auto &Context : Contexts) { + if (Context.first == ContextType::EnumDecl) + continue; + QualifiedName = Context.second + "::" + QualifiedName; + } + return QualifiedName; +} + +SymbolInfo::Signals &SymbolInfo::Signals::operator+=(const Signals &RHS) { + Seen += RHS.Seen; + Used += RHS.Used; + return *this; +} + +SymbolInfo::Signals SymbolInfo::Signals::operator+(const Signals &RHS) const { + Signals Result = *this; + Result += RHS; + return Result; +} + +bool SymbolInfo::Signals::operator==(const Signals &RHS) const { + return std::tie(Seen, Used) == std::tie(RHS.Seen, RHS.Used); +} + +bool SymbolAndSignals::operator==(const SymbolAndSignals& RHS) const { + return std::tie(Symbol, Signals) == std::tie(RHS.Symbol, RHS.Signals); +} + +bool WriteSymbolInfosToStream(llvm::raw_ostream &OS, + const SymbolInfo::SignalMap &Symbols) { + llvm::yaml::Output yout(OS); + for (const auto &Symbol : Symbols) { + SymbolAndSignals S{Symbol.first, Symbol.second}; + yout << S; + } + return true; +} + +std::vector<SymbolAndSignals> ReadSymbolInfosFromYAML(llvm::StringRef Yaml) { + std::vector<SymbolAndSignals> Symbols; + llvm::yaml::Input yin(Yaml); + yin >> Symbols; + return Symbols; +} + +} // namespace find_all_symbols +} // namespace clang diff --git a/clang-include-fixer/find-all-symbols/SymbolInfo.h b/clang-include-fixer/find-all-symbols/SymbolInfo.h new file mode 100644 index 00000000..6def1c70 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/SymbolInfo.h @@ -0,0 +1,142 @@ +//===-- SymbolInfo.h - Symbol Info ------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H + +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include <set> +#include <string> +#include <vector> + +namespace clang { +namespace find_all_symbols { +/// \brief Describes a named symbol from a header. +/// Symbols with the same qualified name and type (e.g. function overloads) +/// that appear in the same header are represented by a single SymbolInfo. +/// +/// TODO: keep track of instances, e.g. overload locations and signatures. +class SymbolInfo { +public: + /// \brief The SymbolInfo Type. + enum class SymbolKind { + Function, + Class, + Variable, + TypedefName, + EnumDecl, + EnumConstantDecl, + Macro, + Unknown, + }; + + /// \brief The Context Type. + enum class ContextType { + Namespace, // Symbols declared in a namespace. + Record, // Symbols declared in a class. + EnumDecl, // Enum constants declared in a enum declaration. + }; + + /// \brief A pair of <ContextType, ContextName>. + typedef std::pair<ContextType, std::string> Context; + + // \brief Signals are signals gathered by observing how a symbol is used. + // These are used to rank results. + struct Signals { + Signals() {} + Signals(unsigned Seen, unsigned Used) : Seen(Seen), Used(Used) {} + + // Number of times this symbol was visible to a TU. + unsigned Seen = 0; + + // Number of times this symbol was referenced a TU's main file. + unsigned Used = 0; + + Signals &operator+=(const Signals &RHS); + Signals operator+(const Signals &RHS) const; + bool operator==(const Signals &RHS) const; + }; + + using SignalMap = std::map<SymbolInfo, Signals>; + + // The default constructor is required by YAML traits in + // LLVM_YAML_IS_DOCUMENT_LIST_VECTOR. + SymbolInfo() : Type(SymbolKind::Unknown) {} + + SymbolInfo(llvm::StringRef Name, SymbolKind Type, llvm::StringRef FilePath, + const std::vector<Context> &Contexts); + + void SetFilePath(llvm::StringRef Path) { FilePath = Path; } + + /// \brief Get symbol name. + llvm::StringRef getName() const { return Name; } + + /// \brief Get the fully-qualified symbol name. + std::string getQualifiedName() const; + + /// \brief Get symbol type. + SymbolKind getSymbolKind() const { return Type; } + + /// \brief Get a relative file path where symbol comes from. + llvm::StringRef getFilePath() const { return FilePath; } + + /// \brief Get symbol contexts. + const std::vector<SymbolInfo::Context> &getContexts() const { + return Contexts; + } + + bool operator<(const SymbolInfo &Symbol) const; + + bool operator==(const SymbolInfo &Symbol) const; + +private: + friend struct llvm::yaml::MappingTraits<struct SymbolAndSignals>; + + /// \brief Identifier name. + std::string Name; + + /// \brief Symbol type. + SymbolKind Type; + + /// \brief The file path where the symbol comes from. It's a relative file + /// path based on the build directory. + std::string FilePath; + + /// \brief Contains information about symbol contexts. Context information is + /// stored from the inner-most level to outer-most level. + /// + /// For example, if a symbol 'x' is declared as: + /// namespace na { namespace nb { class A { int x; } } } + /// The contexts would be { {RECORD, "A"}, {NAMESPACE, "nb"}, {NAMESPACE, + /// "na"} }. + /// The name of an anonymous namespace is "". + /// + /// If the symbol is declared in `TranslationUnitDecl`, it has no context. + std::vector<Context> Contexts; +}; + +struct SymbolAndSignals { + SymbolInfo Symbol; + SymbolInfo::Signals Signals; + bool operator==(const SymbolAndSignals& RHS) const; +}; + +/// \brief Write SymbolInfos to a stream (YAML format). +bool WriteSymbolInfosToStream(llvm::raw_ostream &OS, + const SymbolInfo::SignalMap &Symbols); + +/// \brief Read SymbolInfos from a YAML document. +std::vector<SymbolAndSignals> ReadSymbolInfosFromYAML(llvm::StringRef Yaml); + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H diff --git a/clang-include-fixer/find-all-symbols/SymbolReporter.h b/clang-include-fixer/find-all-symbols/SymbolReporter.h new file mode 100644 index 00000000..25e86219 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/SymbolReporter.h @@ -0,0 +1,29 @@ +//===--- SymbolReporter.h - Symbol Reporter ---------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H + +#include "SymbolInfo.h" + +namespace clang { +namespace find_all_symbols { + +/// \brief An interface for classes that collect symbols. +class SymbolReporter { +public: + virtual ~SymbolReporter() = default; + + virtual void reportSymbols(llvm::StringRef FileName, + const SymbolInfo::SignalMap &Symbols) = 0; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H diff --git a/clang-include-fixer/find-all-symbols/tool/CMakeLists.txt b/clang-include-fixer/find-all-symbols/tool/CMakeLists.txt new file mode 100644 index 00000000..64278ad1 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/tool/CMakeLists.txt @@ -0,0 +1,24 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(find-all-symbols + FindAllSymbolsMain.cpp + ) + +target_link_libraries(find-all-symbols + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangLex + clangSerialization + clangTooling + findAllSymbols + ) + +install(TARGETS find-all-symbols + RUNTIME DESTINATION bin) + +install(PROGRAMS run-find-all-symbols.py + DESTINATION share/clang + COMPONENT find-all-symbols) diff --git a/clang-include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp b/clang-include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp new file mode 100644 index 00000000..dbbe0738 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp @@ -0,0 +1,151 @@ +//===-- FindAllSymbolsMain.cpp - find all symbols tool ----------*- C++ -*-===// +// +// 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 "FindAllSymbolsAction.h" +#include "STLPostfixHeaderMap.h" +#include "SymbolInfo.h" +#include "SymbolReporter.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/raw_ostream.h" +#include <map> +#include <mutex> +#include <set> +#include <string> +#include <system_error> +#include <vector> + +using namespace clang::tooling; +using namespace llvm; +using SymbolInfo = clang::find_all_symbols::SymbolInfo; + +// Apply a custom category to all command-line options so that they are the +// only ones displayed. +static cl::OptionCategory FindAllSymbolsCategory("find_all_symbols options"); + +// CommonOptionsParser declares HelpMessage with a description of the common +// command-line options related to the compilation database and input files. +// It's nice to have this help message in all tools. +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); + +// A help message for this specific tool can be added afterwards. +static cl::extrahelp MoreHelp("\nMore help text..."); + +static cl::opt<std::string> OutputDir("output-dir", cl::desc(R"( +The output directory for saving the results.)"), + cl::init("."), + cl::cat(FindAllSymbolsCategory)); + +static cl::opt<std::string> MergeDir("merge-dir", cl::desc(R"( +The directory for merging symbols.)"), + cl::init(""), + cl::cat(FindAllSymbolsCategory)); +namespace clang { +namespace find_all_symbols { + +class YamlReporter : public SymbolReporter { +public: + void reportSymbols(StringRef FileName, + const SymbolInfo::SignalMap &Symbols) override { + int FD; + SmallString<128> ResultPath; + llvm::sys::fs::createUniqueFile( + OutputDir + "/" + llvm::sys::path::filename(FileName) + "-%%%%%%.yaml", + FD, ResultPath); + llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true); + WriteSymbolInfosToStream(OS, Symbols); + } +}; + +bool Merge(llvm::StringRef MergeDir, llvm::StringRef OutputFile) { + std::error_code EC; + SymbolInfo::SignalMap Symbols; + std::mutex SymbolMutex; + auto AddSymbols = [&](ArrayRef<SymbolAndSignals> NewSymbols) { + // Synchronize set accesses. + std::unique_lock<std::mutex> LockGuard(SymbolMutex); + for (const auto &Symbol : NewSymbols) { + Symbols[Symbol.Symbol] += Symbol.Signals; + } + }; + + // Load all symbol files in MergeDir. + { + llvm::ThreadPool Pool; + for (llvm::sys::fs::directory_iterator Dir(MergeDir, EC), DirEnd; + Dir != DirEnd && !EC; Dir.increment(EC)) { + // Parse YAML files in parallel. + Pool.async( + [&AddSymbols](std::string Path) { + auto Buffer = llvm::MemoryBuffer::getFile(Path); + if (!Buffer) { + llvm::errs() << "Can't open " << Path << "\n"; + return; + } + std::vector<SymbolAndSignals> Symbols = + ReadSymbolInfosFromYAML(Buffer.get()->getBuffer()); + for (auto &Symbol : Symbols) { + // Only count one occurrence per file, to avoid spam. + Symbol.Signals.Seen = std::min(Symbol.Signals.Seen, 1u); + Symbol.Signals.Used = std::min(Symbol.Signals.Used, 1u); + } + // FIXME: Merge without creating such a heavy contention point. + AddSymbols(Symbols); + }, + Dir->path()); + } + } + + llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Can't open '" << OutputFile << "': " << EC.message() + << '\n'; + return false; + } + WriteSymbolInfosToStream(OS, Symbols); + return true; +} + +} // namespace clang +} // namespace find_all_symbols + +int main(int argc, const char **argv) { + CommonOptionsParser OptionsParser(argc, argv, FindAllSymbolsCategory); + ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + + std::vector<std::string> sources = OptionsParser.getSourcePathList(); + if (sources.empty()) { + llvm::errs() << "Must specify at least one one source file.\n"; + return 1; + } + if (!MergeDir.empty()) { + clang::find_all_symbols::Merge(MergeDir, sources[0]); + return 0; + } + + clang::find_all_symbols::YamlReporter Reporter; + + auto Factory = + llvm::make_unique<clang::find_all_symbols::FindAllSymbolsActionFactory>( + &Reporter, clang::find_all_symbols::getSTLPostfixHeaderMap()); + return Tool.run(Factory.get()); +} diff --git a/clang-include-fixer/find-all-symbols/tool/run-find-all-symbols.py b/clang-include-fixer/find-all-symbols/tool/run-find-all-symbols.py new file mode 100755 index 00000000..5e9dde72 --- /dev/null +++ b/clang-include-fixer/find-all-symbols/tool/run-find-all-symbols.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# +#=- run-find-all-symbols.py - Parallel find-all-symbols runner -*- python -*-=# +# +# 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 +# +#===------------------------------------------------------------------------===# + +""" +Parallel find-all-symbols runner +================================ + +Runs find-all-symbols over all files in a compilation database. + +Example invocations. +- Run find-all-symbols on all files in the current working directory. + run-find-all-symbols.py <source-file> + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +import argparse +import json +import multiprocessing +import os +import Queue +import shutil +import subprocess +import sys +import tempfile +import threading + + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = './' + while not os.path.isfile(os.path.join(result, path)): + if os.path.realpath(result) == '/': + print 'Error: could not find compilation database.' + sys.exit(1) + result += '../' + return os.path.realpath(result) + + +def MergeSymbols(directory, args): + """Merge all symbol files (yaml) in a given directaory into a single file.""" + invocation = [args.binary, '-merge-dir='+directory, args.saving_path] + subprocess.call(invocation) + print 'Merge is finished. Saving results in ' + args.saving_path + + +def run_find_all_symbols(args, tmpdir, build_path, queue): + """Takes filenames out of queue and runs find-all-symbols on them.""" + while True: + name = queue.get() + invocation = [args.binary, name, '-output-dir='+tmpdir, '-p='+build_path] + sys.stdout.write(' '.join(invocation) + '\n') + subprocess.call(invocation) + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser(description='Runs find-all-symbols over all' + 'files in a compilation database.') + parser.add_argument('-binary', metavar='PATH', + default='./bin/find-all-symbols', + help='path to find-all-symbols binary') + parser.add_argument('-j', type=int, default=0, + help='number of instances to be run in parallel.') + parser.add_argument('-p', dest='build_path', + help='path used to read a compilation database.') + parser.add_argument('-saving-path', default='./find_all_symbols_db.yaml', + help='result saving path') + args = parser.parse_args() + + db_path = 'compile_commands.json' + + if args.build_path is not None: + build_path = args.build_path + else: + build_path = find_compilation_database(db_path) + + tmpdir = tempfile.mkdtemp() + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = [entry['file'] for entry in database] + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + try: + # Spin up a bunch of tidy-launching threads. + queue = Queue.Queue(max_task) + for _ in range(max_task): + t = threading.Thread(target=run_find_all_symbols, + args=(args, tmpdir, build_path, queue)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + queue.put(name) + + # Wait for all threads to be done. + queue.join() + + MergeSymbols(tmpdir, args) + + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print '\nCtrl-C detected, goodbye.' + os.kill(0, 9) + + +if __name__ == '__main__': + main() diff --git a/clang-include-fixer/plugin/CMakeLists.txt b/clang-include-fixer/plugin/CMakeLists.txt new file mode 100644 index 00000000..df792ea1 --- /dev/null +++ b/clang-include-fixer/plugin/CMakeLists.txt @@ -0,0 +1,13 @@ +add_clang_library(clangIncludeFixerPlugin + IncludeFixerPlugin.cpp + + LINK_LIBS + clangAST + clangBasic + clangFrontend + clangIncludeFixer + clangParse + clangSema + clangTooling + ${LLVM_PTHREAD_LIB} + ) diff --git a/clang-include-fixer/plugin/IncludeFixerPlugin.cpp b/clang-include-fixer/plugin/IncludeFixerPlugin.cpp new file mode 100644 index 00000000..bc9c4973 --- /dev/null +++ b/clang-include-fixer/plugin/IncludeFixerPlugin.cpp @@ -0,0 +1,99 @@ +//===- IncludeFixerPlugin.cpp - clang-include-fixer as a clang plugin -----===// +// +// 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 "../YamlSymbolIndex.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/Parse/ParseAST.h" +#include "clang/Sema/Sema.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace include_fixer { + +/// The core include fixer plugin action. This just provides the AST consumer +/// and command line flag parsing for using include fixer as a clang plugin. +class ClangIncludeFixerPluginAction : public PluginASTAction { + /// ASTConsumer to keep the symbol index alive. We don't really need an + /// ASTConsumer for this plugin (everything is funneled on the side through + /// Sema) but we have to keep the symbol index alive until sema is done. + struct ASTConsumerManagerWrapper : public ASTConsumer { + ASTConsumerManagerWrapper(std::shared_ptr<SymbolIndexManager> SIM) + : SymbolIndexMgr(std::move(SIM)) {} + std::shared_ptr<SymbolIndexManager> SymbolIndexMgr; + }; + +public: + explicit ClangIncludeFixerPluginAction() + : SymbolIndexMgr(std::make_shared<SymbolIndexManager>()), + SemaSource(new IncludeFixerSemaSource(*SymbolIndexMgr, + /*MinimizeIncludePaths=*/true, + /*GenerateDiagnostics=*/true)) {} + + std::unique_ptr<clang::ASTConsumer> + CreateASTConsumer(clang::CompilerInstance &CI, StringRef InFile) override { + CI.setExternalSemaSource(SemaSource); + SemaSource->setFilePath(InFile); + SemaSource->setCompilerInstance(&CI); + return llvm::make_unique<ASTConsumerManagerWrapper>(SymbolIndexMgr); + } + + void ExecuteAction() override {} // Do nothing. + + bool ParseArgs(const CompilerInstance &CI, + const std::vector<std::string> &Args) override { + StringRef DB = "yaml"; + StringRef Input; + + // Parse the extra command line args. + // FIXME: This is very limited at the moment. + for (StringRef Arg : Args) { + if (Arg.startswith("-db=")) + DB = Arg.substr(strlen("-db=")); + else if (Arg.startswith("-input=")) + Input = Arg.substr(strlen("-input=")); + } + + std::string InputFile = CI.getFrontendOpts().Inputs[0].getFile(); + auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> { + llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> SymbolIdx( + nullptr); + if (DB == "yaml") { + if (!Input.empty()) { + SymbolIdx = include_fixer::YamlSymbolIndex::createFromFile(Input); + } else { + // If we don't have any input file, look in the directory of the first + // file and its parents. + SmallString<128> AbsolutePath(tooling::getAbsolutePath(InputFile)); + StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); + SymbolIdx = include_fixer::YamlSymbolIndex::createFromDirectory( + Directory, "find_all_symbols_db.yaml"); + } + } + return std::move(*SymbolIdx); + }; + + SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx)); + return true; + } + +private: + std::shared_ptr<SymbolIndexManager> SymbolIndexMgr; + IntrusiveRefCntPtr<IncludeFixerSemaSource> SemaSource; +}; +} // namespace include_fixer +} // namespace clang + +// This anchor is used to force the linker to link in the generated object file +// and thus register the include fixer plugin. +volatile int ClangIncludeFixerPluginAnchorSource = 0; + +static clang::FrontendPluginRegistry::Add< + clang::include_fixer::ClangIncludeFixerPluginAction> + X("clang-include-fixer", "clang-include-fixer"); diff --git a/clang-include-fixer/tool/CMakeLists.txt b/clang-include-fixer/tool/CMakeLists.txt new file mode 100644 index 00000000..207995aa --- /dev/null +++ b/clang-include-fixer/tool/CMakeLists.txt @@ -0,0 +1,28 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_tool(clang-include-fixer + ClangIncludeFixer.cpp + ) + +target_link_libraries(clang-include-fixer + PRIVATE + clangBasic + clangFormat + clangFrontend + clangIncludeFixer + clangRewrite + clangSerialization + clangTooling + clangToolingCore + findAllSymbols + ) + +install(TARGETS clang-include-fixer + RUNTIME DESTINATION bin) + +install(PROGRAMS clang-include-fixer.el + DESTINATION share/clang + COMPONENT clang-include-fixer) +install(PROGRAMS clang-include-fixer.py + DESTINATION share/clang + COMPONENT clang-include-fixer) diff --git a/clang-include-fixer/tool/ClangIncludeFixer.cpp b/clang-include-fixer/tool/ClangIncludeFixer.cpp new file mode 100644 index 00000000..15f6ed29 --- /dev/null +++ b/clang-include-fixer/tool/ClangIncludeFixer.cpp @@ -0,0 +1,473 @@ +//===-- ClangIncludeFixer.cpp - Standalone include fixer ------------------===// +// +// 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 "FuzzySymbolIndex.h" +#include "InMemorySymbolIndex.h" +#include "IncludeFixer.h" +#include "IncludeFixerContext.h" +#include "SymbolIndexManager.h" +#include "YamlSymbolIndex.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace clang; +using namespace llvm; +using clang::include_fixer::IncludeFixerContext; + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(IncludeFixerContext) +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo) +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo) + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits<tooling::Range> { + struct NormalizedRange { + NormalizedRange(const IO &) : Offset(0), Length(0) {} + + NormalizedRange(const IO &, const tooling::Range &R) + : Offset(R.getOffset()), Length(R.getLength()) {} + + tooling::Range denormalize(const IO &) { + return tooling::Range(Offset, Length); + } + + unsigned Offset; + unsigned Length; + }; + static void mapping(IO &IO, tooling::Range &Info) { + MappingNormalization<NormalizedRange, tooling::Range> Keys(IO, Info); + IO.mapRequired("Offset", Keys->Offset); + IO.mapRequired("Length", Keys->Length); + } +}; + +template <> struct MappingTraits<IncludeFixerContext::HeaderInfo> { + static void mapping(IO &io, IncludeFixerContext::HeaderInfo &Info) { + io.mapRequired("Header", Info.Header); + io.mapRequired("QualifiedName", Info.QualifiedName); + } +}; + +template <> struct MappingTraits<IncludeFixerContext::QuerySymbolInfo> { + static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info) { + io.mapRequired("RawIdentifier", Info.RawIdentifier); + io.mapRequired("Range", Info.Range); + } +}; + +template <> struct MappingTraits<IncludeFixerContext> { + static void mapping(IO &IO, IncludeFixerContext &Context) { + IO.mapRequired("QuerySymbolInfos", Context.QuerySymbolInfos); + IO.mapRequired("HeaderInfos", Context.HeaderInfos); + IO.mapRequired("FilePath", Context.FilePath); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +cl::OptionCategory IncludeFixerCategory("Tool options"); + +enum DatabaseFormatTy { + fixed, ///< Hard-coded mapping. + yaml, ///< Yaml database created by find-all-symbols. + fuzzyYaml, ///< Yaml database with fuzzy-matched identifiers. +}; + +cl::opt<DatabaseFormatTy> DatabaseFormat( + "db", cl::desc("Specify input format"), + cl::values(clEnumVal(fixed, "Hard-coded mapping"), + clEnumVal(yaml, "Yaml database created by find-all-symbols"), + clEnumVal(fuzzyYaml, "Yaml database, with fuzzy-matched names")), + cl::init(yaml), cl::cat(IncludeFixerCategory)); + +cl::opt<std::string> Input("input", + cl::desc("String to initialize the database"), + cl::cat(IncludeFixerCategory)); + +cl::opt<std::string> + QuerySymbol("query-symbol", + cl::desc("Query a given symbol (e.g. \"a::b::foo\") in\n" + "database directly without parsing the file."), + cl::cat(IncludeFixerCategory)); + +cl::opt<bool> + MinimizeIncludePaths("minimize-paths", + cl::desc("Whether to minimize added include paths"), + cl::init(true), cl::cat(IncludeFixerCategory)); + +cl::opt<bool> Quiet("q", cl::desc("Reduce terminal output"), cl::init(false), + cl::cat(IncludeFixerCategory)); + +cl::opt<bool> + STDINMode("stdin", + cl::desc("Override source file's content (in the overlaying\n" + "virtual file system) with input from <stdin> and run\n" + "the tool on the new content with the compilation\n" + "options of the source file. This mode is currently\n" + "used for editor integration."), + cl::init(false), cl::cat(IncludeFixerCategory)); + +cl::opt<bool> OutputHeaders( + "output-headers", + cl::desc("Print the symbol being queried and all its relevant headers in\n" + "JSON format to stdout:\n" + " {\n" + " \"FilePath\": \"/path/to/foo.cc\",\n" + " \"QuerySymbolInfos\": [\n" + " {\"RawIdentifier\": \"foo\",\n" + " \"Range\": {\"Offset\": 0, \"Length\": 3}}\n" + " ],\n" + " \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n" + " \"QualifiedName\": \"a::foo\"} ]\n" + " }"), + cl::init(false), cl::cat(IncludeFixerCategory)); + +cl::opt<std::string> InsertHeader( + "insert-header", + cl::desc("Insert a specific header. This should run with STDIN mode.\n" + "The result is written to stdout. It is currently used for\n" + "editor integration. Support YAML/JSON format:\n" + " -insert-header=\"{\n" + " FilePath: \"/path/to/foo.cc\",\n" + " QuerySymbolInfos: [\n" + " {RawIdentifier: foo,\n" + " Range: {Offset: 0, Length: 3}}\n" + " ],\n" + " HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n" + " QualifiedName: \"a::foo\"} ]}\""), + cl::init(""), cl::cat(IncludeFixerCategory)); + +cl::opt<std::string> + Style("style", + cl::desc("Fallback style for reformatting after inserting new\n" + "headers if there is no clang-format config file found."), + cl::init("llvm"), cl::cat(IncludeFixerCategory)); + +std::unique_ptr<include_fixer::SymbolIndexManager> +createSymbolIndexManager(StringRef FilePath) { + using find_all_symbols::SymbolInfo; + + auto SymbolIndexMgr = llvm::make_unique<include_fixer::SymbolIndexManager>(); + switch (DatabaseFormat) { + case fixed: { + // Parse input and fill the database with it. + // <symbol>=<header><, header...> + // Multiple symbols can be given, separated by semicolons. + std::map<std::string, std::vector<std::string>> SymbolsMap; + SmallVector<StringRef, 4> SemicolonSplits; + StringRef(Input).split(SemicolonSplits, ";"); + std::vector<find_all_symbols::SymbolAndSignals> Symbols; + for (StringRef Pair : SemicolonSplits) { + auto Split = Pair.split('='); + std::vector<std::string> Headers; + SmallVector<StringRef, 4> CommaSplits; + Split.second.split(CommaSplits, ","); + for (size_t I = 0, E = CommaSplits.size(); I != E; ++I) + Symbols.push_back( + {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown, + CommaSplits[I].trim(), {}), + // Use fake "seen" signal for tests, so first header wins. + SymbolInfo::Signals(/*Seen=*/static_cast<unsigned>(E - I), + /*Used=*/0)}); + } + SymbolIndexMgr->addSymbolIndex([=]() { + return llvm::make_unique<include_fixer::InMemorySymbolIndex>(Symbols); + }); + break; + } + case yaml: { + auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> { + llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB( + nullptr); + if (!Input.empty()) { + DB = include_fixer::YamlSymbolIndex::createFromFile(Input); + } else { + // If we don't have any input file, look in the directory of the + // first + // file and its parents. + SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath)); + StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); + DB = include_fixer::YamlSymbolIndex::createFromDirectory( + Directory, "find_all_symbols_db.yaml"); + } + + if (!DB) { + llvm::errs() << "Couldn't find YAML db: " << DB.getError().message() + << '\n'; + return nullptr; + } + return std::move(*DB); + }; + + SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx)); + break; + } + case fuzzyYaml: { + // This mode is not very useful, because we don't correct the identifier. + // It's main purpose is to expose FuzzySymbolIndex to tests. + SymbolIndexMgr->addSymbolIndex( + []() -> std::unique_ptr<include_fixer::SymbolIndex> { + auto DB = include_fixer::FuzzySymbolIndex::createFromYAML(Input); + if (!DB) { + llvm::errs() << "Couldn't load fuzzy YAML db: " + << llvm::toString(DB.takeError()) << '\n'; + return nullptr; + } + return std::move(*DB); + }); + break; + } + } + return SymbolIndexMgr; +} + +void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) { + OS << "{\n" + << " \"FilePath\": \"" + << llvm::yaml::escape(Context.getFilePath()) << "\",\n" + << " \"QuerySymbolInfos\": [\n"; + for (const auto &Info : Context.getQuerySymbolInfos()) { + OS << " {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n"; + OS << " \"Range\":{"; + OS << "\"Offset\":" << Info.Range.getOffset() << ","; + OS << "\"Length\":" << Info.Range.getLength() << "}}"; + if (&Info != &Context.getQuerySymbolInfos().back()) + OS << ",\n"; + } + OS << "\n ],\n"; + OS << " \"HeaderInfos\": [\n"; + const auto &HeaderInfos = Context.getHeaderInfos(); + for (const auto &Info : HeaderInfos) { + OS << " {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n" + << " \"QualifiedName\": \"" << Info.QualifiedName << "\"}"; + if (&Info != &HeaderInfos.back()) + OS << ",\n"; + } + OS << "\n"; + OS << " ]\n"; + OS << "}\n"; +} + +int includeFixerMain(int argc, const char **argv) { + tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory); + tooling::ClangTool tool(options.getCompilations(), + options.getSourcePathList()); + + llvm::StringRef SourceFilePath = options.getSourcePathList().front(); + // In STDINMode, we override the file content with the <stdin> input. + // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of + // the if-block so that `Code` is not released after the if-block. + std::unique_ptr<llvm::MemoryBuffer> Code; + if (STDINMode) { + assert(options.getSourcePathList().size() == 1 && + "Expect exactly one file path in STDINMode."); + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr = + MemoryBuffer::getSTDIN(); + if (std::error_code EC = CodeOrErr.getError()) { + errs() << EC.message() << "\n"; + return 1; + } + Code = std::move(CodeOrErr.get()); + if (Code->getBufferSize() == 0) + return 0; // Skip empty files. + + tool.mapVirtualFile(SourceFilePath, Code->getBuffer()); + } + + if (!InsertHeader.empty()) { + if (!STDINMode) { + errs() << "Should be running in STDIN mode\n"; + return 1; + } + + llvm::yaml::Input yin(InsertHeader); + IncludeFixerContext Context; + yin >> Context; + + const auto &HeaderInfos = Context.getHeaderInfos(); + assert(!HeaderInfos.empty()); + // We only accept one unique header. + // Check all elements in HeaderInfos have the same header. + bool IsUniqueHeader = std::equal( + HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(), + [](const IncludeFixerContext::HeaderInfo &LHS, + const IncludeFixerContext::HeaderInfo &RHS) { + return LHS.Header == RHS.Header; + }); + if (!IsUniqueHeader) { + errs() << "Expect exactly one unique header.\n"; + return 1; + } + + // If a header has multiple symbols, we won't add the missing namespace + // qualifiers because we don't know which one is exactly used. + // + // Check whether all elements in HeaderInfos have the same qualified name. + bool IsUniqueQualifiedName = std::equal( + HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(), + [](const IncludeFixerContext::HeaderInfo &LHS, + const IncludeFixerContext::HeaderInfo &RHS) { + return LHS.QualifiedName == RHS.QualifiedName; + }); + auto InsertStyle = format::getStyle(format::DefaultFormatStyle, + Context.getFilePath(), Style); + if (!InsertStyle) { + llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n"; + return 1; + } + auto Replacements = clang::include_fixer::createIncludeFixerReplacements( + Code->getBuffer(), Context, *InsertStyle, + /*AddQualifiers=*/IsUniqueQualifiedName); + if (!Replacements) { + errs() << "Failed to create replacements: " + << llvm::toString(Replacements.takeError()) << "\n"; + return 1; + } + + auto ChangedCode = + tooling::applyAllReplacements(Code->getBuffer(), *Replacements); + if (!ChangedCode) { + llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; + return 1; + } + llvm::outs() << *ChangedCode; + return 0; + } + + // Set up data source. + std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr = + createSymbolIndexManager(SourceFilePath); + if (!SymbolIndexMgr) + return 1; + + // Query symbol mode. + if (!QuerySymbol.empty()) { + auto MatchedSymbols = SymbolIndexMgr->search( + QuerySymbol, /*IsNestedSearch=*/true, SourceFilePath); + for (auto &Symbol : MatchedSymbols) { + std::string HeaderPath = Symbol.getFilePath().str(); + Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<') + ? HeaderPath + : "\"" + HeaderPath + "\"")); + } + + // We leave an empty symbol range as we don't know the range of the symbol + // being queried in this mode. clang-include-fixer won't add namespace + // qualifiers if the symbol range is empty, which also fits this case. + IncludeFixerContext::QuerySymbolInfo Symbol; + Symbol.RawIdentifier = QuerySymbol; + auto Context = + IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols); + writeToJson(llvm::outs(), Context); + return 0; + } + + // Now run our tool. + std::vector<include_fixer::IncludeFixerContext> Contexts; + include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts, + Style, MinimizeIncludePaths); + + if (tool.run(&Factory) != 0) { + // We suppress all Clang diagnostics (because they would be wrong, + // clang-include-fixer does custom recovery) but still want to give some + // feedback in case there was a compiler error we couldn't recover from. + // The most common case for this is a #include in the file that couldn't be + // found. + llvm::errs() << "Fatal compiler error occurred while parsing file!" + " (incorrect include paths?)\n"; + return 1; + } + + assert(!Contexts.empty()); + + if (OutputHeaders) { + // FIXME: Print contexts of all processing files instead of the first one. + writeToJson(llvm::outs(), Contexts.front()); + return 0; + } + + std::vector<tooling::Replacements> FixerReplacements; + for (const auto &Context : Contexts) { + StringRef FilePath = Context.getFilePath(); + auto InsertStyle = + format::getStyle(format::DefaultFormatStyle, FilePath, Style); + if (!InsertStyle) { + llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n"; + return 1; + } + auto Buffer = llvm::MemoryBuffer::getFile(FilePath); + if (!Buffer) { + errs() << "Couldn't open file: " + FilePath.str() + ": " + << Buffer.getError().message() + "\n"; + return 1; + } + + auto Replacements = clang::include_fixer::createIncludeFixerReplacements( + Buffer.get()->getBuffer(), Context, *InsertStyle); + if (!Replacements) { + errs() << "Failed to create replacement: " + << llvm::toString(Replacements.takeError()) << "\n"; + return 1; + } + FixerReplacements.push_back(*Replacements); + } + + if (!Quiet) { + for (const auto &Context : Contexts) { + if (!Context.getHeaderInfos().empty()) { + llvm::errs() << "Added #include " + << Context.getHeaderInfos().front().Header << " for " + << Context.getFilePath() << "\n"; + } + } + } + + if (STDINMode) { + assert(FixerReplacements.size() == 1); + auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), + FixerReplacements.front()); + if (!ChangedCode) { + llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; + return 1; + } + llvm::outs() << *ChangedCode; + return 0; + } + + // Set up a new source manager for applying the resulting replacements. + IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions); + DiagnosticsEngine Diagnostics(new DiagnosticIDs, &*DiagOpts); + TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts); + SourceManager SM(Diagnostics, tool.getFiles()); + Diagnostics.setClient(&DiagnosticPrinter, false); + + // Write replacements to disk. + Rewriter Rewrites(SM, LangOptions()); + for (const auto &Replacement : FixerReplacements) { + if (!tooling::applyAllReplacements(Replacement, Rewrites)) { + llvm::errs() << "Failed to apply replacements.\n"; + return 1; + } + } + return Rewrites.overwriteChangedFiles(); +} + +} // namespace + +int main(int argc, const char **argv) { + return includeFixerMain(argc, argv); +} diff --git a/clang-include-fixer/tool/clang-include-fixer-test.el b/clang-include-fixer/tool/clang-include-fixer-test.el new file mode 100644 index 00000000..d66d5300 --- /dev/null +++ b/clang-include-fixer/tool/clang-include-fixer-test.el @@ -0,0 +1,65 @@ +;;; clang-include-fixer-test.el --- unit tests for clang-include-fixer.el -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Unit tests for clang-include-fixer.el. + +;;; Code: + +(require 'clang-include-fixer) + +(require 'cc-mode) +(require 'ert) + +(ert-deftest clang-include-fixer--insert-line () + "Unit test for `clang-include-fixer--insert-line'." + (with-temp-buffer + (insert "aa\nab\nac\nad\n") + (let ((from (current-buffer))) + (with-temp-buffer + (insert "aa\nac\nad\n") + (let ((to (current-buffer))) + (should (clang-include-fixer--insert-line from to)) + (should (equal (buffer-string) "aa\nab\nac\nad\n"))))) + (should (equal (buffer-string) "aa\nab\nac\nad\n")))) + +(ert-deftest clang-include-fixer--insert-line-diff-on-empty-line () + "Unit test for `clang-include-fixer--insert-line'." + (with-temp-buffer + (insert "aa\nab\n\nac\nad\n") + (let ((from (current-buffer))) + (with-temp-buffer + (insert "aa\n\nac\nad\n") + (let ((to (current-buffer))) + (should (clang-include-fixer--insert-line from to)) + (should (equal (buffer-string) "aa\nab\n\nac\nad\n"))))) + (should (equal (buffer-string) "aa\nab\n\nac\nad\n")))) + +(ert-deftest clang-include-fixer--symbol-at-point () + "Unit test for `clang-include-fixer--symbol-at-point'." + (with-temp-buffer + (insert "a+bbb::cc") + (c++-mode) + (goto-char (point-min)) + (should (equal (clang-include-fixer--symbol-at-point) "a")) + (forward-char) + ;; Emacs treats the character immediately following a symbol as part of the + ;; symbol. + (should (equal (clang-include-fixer--symbol-at-point) "a")) + (forward-char) + (should (equal (clang-include-fixer--symbol-at-point) "bbb::cc")) + (goto-char (point-max)) + (should (equal (clang-include-fixer--symbol-at-point) "bbb::cc")))) + +(ert-deftest clang-include-fixer--highlight () + (with-temp-buffer + (insert "util::Status foo;\n") + (setq buffer-file-coding-system 'utf-8-unix) + (should (equal nil (clang-include-fixer--highlight + '((Range . ((Offset . 0) (Length . 0))))))) + (let ((overlay (clang-include-fixer--highlight + '((Range . ((Offset . 1) (Length . 12))))))) + (should (equal 2 (overlay-start overlay))) + (should (equal 14 (overlay-end overlay)))))) + +;;; clang-include-fixer-test.el ends here diff --git a/clang-include-fixer/tool/clang-include-fixer.el b/clang-include-fixer/tool/clang-include-fixer.el new file mode 100644 index 00000000..84fff624 --- /dev/null +++ b/clang-include-fixer/tool/clang-include-fixer.el @@ -0,0 +1,460 @@ +;;; clang-include-fixer.el --- Emacs integration of the clang include fixer -*- lexical-binding: t; -*- + +;; Keywords: tools, c +;; Package-Requires: ((cl-lib "0.5") (json "1.2") (let-alist "1.0.4")) + +;;; Commentary: + +;; This package allows Emacs users to invoke the 'clang-include-fixer' within +;; Emacs. 'clang-include-fixer' provides an automated way of adding #include +;; directives for missing symbols in one translation unit, see +;; <http://clang.llvm.org/extra/clang-include-fixer.html>. + +;;; Code: + +(require 'cl-lib) +(require 'json) +(require 'let-alist) + +(defgroup clang-include-fixer nil + "Clang-based include fixer." + :group 'tools) + +(defvar clang-include-fixer-add-include-hook nil + "A hook that will be called for every added include. +The first argument is the filename of the include, the second argument is +non-nil if the include is a system-header.") + +(defcustom clang-include-fixer-executable + "clang-include-fixer" + "Location of the clang-include-fixer executable. + +A string containing the name or the full path of the executable." + :group 'clang-include-fixer + :type '(file :must-match t) + :risky t) + +(defcustom clang-include-fixer-input-format + 'yaml + "Input format for clang-include-fixer. +This string is passed as -db argument to +`clang-include-fixer-executable'." + :group 'clang-include-fixer + :type '(radio + (const :tag "Hard-coded mapping" :fixed) + (const :tag "YAML" yaml) + (symbol :tag "Other")) + :risky t) + +(defcustom clang-include-fixer-init-string + "" + "Database initialization string for clang-include-fixer. +This string is passed as -input argument to +`clang-include-fixer-executable'." + :group 'clang-include-fixer + :type 'string + :risky t) + +(defface clang-include-fixer-highlight '((t :background "green")) + "Used for highlighting the symbol for which a header file is being added.") + +;;;###autoload +(defun clang-include-fixer () + "Invoke the Include Fixer to insert missing C++ headers." + (interactive) + (message (concat "Calling the include fixer. " + "This might take some seconds. Please wait.")) + (clang-include-fixer--start #'clang-include-fixer--add-header + "-output-headers")) + +;;;###autoload +(defun clang-include-fixer-at-point () + "Invoke the Clang include fixer for the symbol at point." + (interactive) + (let ((symbol (clang-include-fixer--symbol-at-point))) + (unless symbol + (user-error "No symbol at current location")) + (clang-include-fixer-from-symbol symbol))) + +;;;###autoload +(defun clang-include-fixer-from-symbol (symbol) + "Invoke the Clang include fixer for the SYMBOL. +When called interactively, prompts the user for a symbol." + (interactive + (list (read-string "Symbol: " (clang-include-fixer--symbol-at-point)))) + (clang-include-fixer--start #'clang-include-fixer--add-header + (format "-query-symbol=%s" symbol))) + +(defun clang-include-fixer--start (callback &rest args) + "Asynchronously start clang-include-fixer with parameters ARGS. +The current file name is passed after ARGS as last argument. If +the call was successful the returned result is stored in a +temporary buffer, and CALLBACK is called with the temporary +buffer as only argument." + (unless buffer-file-name + (user-error "clang-include-fixer works only in buffers that visit a file")) + (let ((process (if (and (fboundp 'make-process) + ;; ‘make-process’ doesn’t support remote files + ;; (https://debbugs.gnu.org/cgi/bugreport.cgi?bug=28691). + (not (find-file-name-handler default-directory + 'start-file-process))) + ;; Prefer using ‘make-process’ if possible, because + ;; ‘start-process’ doesn’t allow us to separate the + ;; standard error from the output. + (clang-include-fixer--make-process callback args) + (clang-include-fixer--start-process callback args)))) + (save-restriction + (widen) + (process-send-region process (point-min) (point-max))) + (process-send-eof process)) + nil) + +(defun clang-include-fixer--make-process (callback args) + "Start a new clang-incude-fixer process using `make-process'. +CALLBACK is called after the process finishes successfully; it is +called with a single argument, the buffer where standard output +has been inserted. ARGS is a list of additional command line +arguments. Return the new process object." + (let ((stdin (current-buffer)) + (stdout (generate-new-buffer "*clang-include-fixer output*")) + (stderr (generate-new-buffer "*clang-include-fixer errors*"))) + (make-process :name "clang-include-fixer" + :buffer stdout + :command (clang-include-fixer--command args) + :coding 'utf-8-unix + :noquery t + :connection-type 'pipe + :sentinel (clang-include-fixer--sentinel stdin stdout stderr + callback) + :stderr stderr))) + +(defun clang-include-fixer--start-process (callback args) + "Start a new clang-incude-fixer process using `start-file-process'. +CALLBACK is called after the process finishes successfully; it is +called with a single argument, the buffer where standard output +has been inserted. ARGS is a list of additional command line +arguments. Return the new process object." + (let* ((stdin (current-buffer)) + (stdout (generate-new-buffer "*clang-include-fixer output*")) + (process-connection-type nil) + (process (apply #'start-file-process "clang-include-fixer" stdout + (clang-include-fixer--command args)))) + (set-process-coding-system process 'utf-8-unix 'utf-8-unix) + (set-process-query-on-exit-flag process nil) + (set-process-sentinel process + (clang-include-fixer--sentinel stdin stdout nil + callback)) + process)) + +(defun clang-include-fixer--command (args) + "Return the clang-include-fixer command line. +Returns a list; the first element is the binary to +execute (`clang-include-fixer-executable'), and the remaining +elements are the command line arguments. Adds proper arguments +for `clang-include-fixer-input-format' and +`clang-include-fixer-init-string'. Appends the current buffer's +file name; prepends ARGS directly in front of it." + (cl-check-type args list) + `(,clang-include-fixer-executable + ,(format "-db=%s" clang-include-fixer-input-format) + ,(format "-input=%s" clang-include-fixer-init-string) + "-stdin" + ,@args + ,(clang-include-fixer--file-local-name buffer-file-name))) + +(defun clang-include-fixer--sentinel (stdin stdout stderr callback) + "Return a process sentinel for clang-include-fixer processes. +STDIN, STDOUT, and STDERR are buffers for the standard streams; +only STDERR may be nil. CALLBACK is called in the case of +success; it is called with a single argument, STDOUT. On +failure, a buffer containing the error output is displayed." + (cl-check-type stdin buffer-live) + (cl-check-type stdout buffer-live) + (cl-check-type stderr (or null buffer-live)) + (cl-check-type callback function) + (lambda (process event) + (cl-check-type process process) + (cl-check-type event string) + (unwind-protect + (if (string-equal event "finished\n") + (progn + (when stderr (kill-buffer stderr)) + (with-current-buffer stdin + (funcall callback stdout)) + (kill-buffer stdout)) + (when stderr (kill-buffer stdout)) + (message "clang-include-fixer failed") + (with-current-buffer (or stderr stdout) + (insert "\nProcess " (process-name process) + ?\s event)) + (display-buffer (or stderr stdout)))) + nil)) + +(defun clang-include-fixer--replace-buffer (stdout) + "Replace current buffer by content of STDOUT." + (cl-check-type stdout buffer-live) + (barf-if-buffer-read-only) + (cond ((fboundp 'replace-buffer-contents) (replace-buffer-contents stdout)) + ((clang-include-fixer--insert-line stdout (current-buffer))) + (t (erase-buffer) (insert-buffer-substring stdout))) + (message "Fix applied") + nil) + +(defun clang-include-fixer--insert-line (from to) + "Insert a single missing line from the buffer FROM into TO. +FROM and TO must be buffers. If the contents of FROM and TO are +equal, do nothing and return non-nil. If FROM contains a single +line missing from TO, insert that line into TO so that the buffer +contents are equal and return non-nil. Otherwise, do nothing and +return nil. Buffer restrictions are ignored." + (cl-check-type from buffer-live) + (cl-check-type to buffer-live) + (with-current-buffer from + (save-excursion + (save-restriction + (widen) + (with-current-buffer to + (save-excursion + (save-restriction + (widen) + ;; Search for the first buffer difference. + (let ((chars (abs (compare-buffer-substrings to nil nil from nil nil)))) + (if (zerop chars) + ;; Buffer contents are equal, nothing to do. + t + (goto-char chars) + ;; We might have ended up in the middle of a line if the + ;; current line partially matches. In this case we would + ;; have to insert more than a line. Move to the beginning of + ;; the line to avoid this situation. + (beginning-of-line) + (with-current-buffer from + (goto-char chars) + (beginning-of-line) + (let ((from-begin (point)) + (from-end (progn (forward-line) (point))) + (to-point (with-current-buffer to (point)))) + ;; Search for another buffer difference after the line in + ;; question. If there is none, we can proceed. + (when (zerop (compare-buffer-substrings from from-end nil + to to-point nil)) + (with-current-buffer to + (insert-buffer-substring from from-begin from-end)) + t)))))))))))) + +(defun clang-include-fixer--add-header (stdout) + "Analyse the result of clang-include-fixer stored in STDOUT. +Add a missing header if there is any. If there are multiple +possible headers the user can select one of them to be included. +Temporarily highlight the affected symbols. Asynchronously call +clang-include-fixer to insert the selected header." + (cl-check-type stdout buffer-live) + (let ((context (clang-include-fixer--parse-json stdout))) + (let-alist context + (cond + ((null .QuerySymbolInfos) + (message "The file is fine, no need to add a header.")) + ((null .HeaderInfos) + (message "Couldn't find header for '%s'" + (let-alist (car .QuerySymbolInfos) .RawIdentifier))) + (t + ;; Users may C-g in prompts, make sure the process sentinel + ;; behaves correctly. + (with-local-quit + ;; Replace the HeaderInfos list by a single header selected by + ;; the user. + (clang-include-fixer--select-header context) + ;; Call clang-include-fixer again to insert the selected header. + (clang-include-fixer--start + (let ((old-tick (buffer-chars-modified-tick))) + (lambda (stdout) + (when (/= old-tick (buffer-chars-modified-tick)) + ;; Replacing the buffer now would undo the user’s changes. + (user-error (concat "The buffer has been changed " + "before the header could be inserted"))) + (clang-include-fixer--replace-buffer stdout) + (let-alist context + (let-alist (car .HeaderInfos) + (with-local-quit + (run-hook-with-args 'clang-include-fixer-add-include-hook + (substring .Header 1 -1) + (string= (substring .Header 0 1) "<"))))))) + (format "-insert-header=%s" + (clang-include-fixer--encode-json context)))))))) + nil) + +(defun clang-include-fixer--select-header (context) + "Prompt the user for a header if necessary. +CONTEXT must be a clang-include-fixer context object in +association list format. If it contains more than one HeaderInfo +element, prompt the user to select one of the headers. CONTEXT +is modified to include only the selected element." + (cl-check-type context cons) + (let-alist context + (if (cdr .HeaderInfos) + (clang-include-fixer--prompt-for-header context) + (message "Only one include is missing: %s" + (let-alist (car .HeaderInfos) .Header)))) + nil) + +(defvar clang-include-fixer--history nil + "History for `clang-include-fixer--prompt-for-header'.") + +(defun clang-include-fixer--prompt-for-header (context) + "Prompt the user for a single header. +The choices are taken from the HeaderInfo elements in CONTEXT. +They are replaced by the single element selected by the user." + (let-alist context + (let ((symbol (clang-include-fixer--symbol-name .QuerySymbolInfos)) + ;; Add temporary highlighting so that the user knows which + ;; symbols the current session is about. + (overlays (remove nil + (mapcar #'clang-include-fixer--highlight .QuerySymbolInfos)))) + (unwind-protect + (save-excursion + ;; While prompting, go to the closest overlay so that the user sees + ;; some context. + (when overlays + (goto-char (clang-include-fixer--closest-overlay overlays))) + (cl-flet ((header (info) (let-alist info .Header))) + ;; The header-infos is already sorted by clang-include-fixer. + (let* ((headers (mapcar #'header .HeaderInfos)) + (header (completing-read + (clang-include-fixer--format-message + "Select include for '%s': " symbol) + headers nil :require-match nil + 'clang-include-fixer--history + ;; Specify a default to prevent the behavior + ;; described in + ;; https://github.com/DarwinAwardWinner/ido-completing-read-plus#why-does-ret-sometimes-not-select-the-first-completion-on-the-list--why-is-there-an-empty-entry-at-the-beginning-of-the-completion-list--what-happened-to-old-style-default-selection. + (car headers))) + (info (cl-find header .HeaderInfos :key #'header :test #'string=))) + (unless info (user-error "No header selected")) + (setcar .HeaderInfos info) + (setcdr .HeaderInfos nil)))) + (mapc #'delete-overlay overlays))))) + +(defun clang-include-fixer--symbol-name (symbol-infos) + "Return the unique symbol name in SYMBOL-INFOS. +Raise a signal if the symbol name is not unique." + (let ((symbols (delete-dups (mapcar (lambda (info) + (let-alist info .RawIdentifier)) + symbol-infos)))) + (when (cdr symbols) + (error "Multiple symbols %s returned" symbols)) + (car symbols))) + +(defun clang-include-fixer--highlight (symbol-info) + "Add an overlay to highlight SYMBOL-INFO, if it points to a non-empty range. +Return the overlay object, or nil." + (let-alist symbol-info + (unless (zerop .Range.Length) + (let ((overlay (make-overlay + (clang-include-fixer--filepos-to-bufferpos + .Range.Offset 'approximate) + (clang-include-fixer--filepos-to-bufferpos + (+ .Range.Offset .Range.Length) 'approximate)))) + (overlay-put overlay 'face 'clang-include-fixer-highlight) + overlay)))) + +(defun clang-include-fixer--closest-overlay (overlays) + "Return the start of the overlay in OVERLAYS that is closest to point." + (cl-check-type overlays cons) + (let ((point (point)) + acc) + (dolist (overlay overlays acc) + (let ((start (overlay-start overlay))) + (when (or (null acc) (< (abs (- point start)) (abs (- point acc)))) + (setq acc start)))))) + +(defun clang-include-fixer--parse-json (buffer) + "Parse a JSON response from clang-include-fixer in BUFFER. +Return the JSON object as an association list." + (with-current-buffer buffer + (save-excursion + (goto-char (point-min)) + (let ((json-object-type 'alist) + (json-array-type 'list) + (json-key-type 'symbol) + (json-false :json-false) + (json-null nil) + (json-pre-element-read-function nil) + (json-post-element-read-function nil)) + (json-read))))) + +(defun clang-include-fixer--encode-json (object) + "Return the JSON representation of OBJECT as a string." + (let ((json-encoding-separator ",") + (json-encoding-default-indentation " ") + (json-encoding-pretty-print nil) + (json-encoding-lisp-style-closings nil) + (json-encoding-object-sort-predicate nil)) + (json-encode object))) + +(defun clang-include-fixer--symbol-at-point () + "Return the qualified symbol at point. +If there is no symbol at point, return nil." + ;; Let ‘bounds-of-thing-at-point’ to do the hard work and deal with edge + ;; cases. + (let ((bounds (bounds-of-thing-at-point 'symbol))) + (when bounds + (let ((beg (car bounds)) + (end (cdr bounds))) + (save-excursion + ;; Extend the symbol range to the left. Skip over namespace + ;; delimiters and parent namespace names. + (goto-char beg) + (while (and (clang-include-fixer--skip-double-colon-backward) + (skip-syntax-backward "w_"))) + ;; Skip over one more namespace delimiter, for absolute names. + (clang-include-fixer--skip-double-colon-backward) + (setq beg (point)) + ;; Extend the symbol range to the right. Skip over namespace + ;; delimiters and child namespace names. + (goto-char end) + (while (and (clang-include-fixer--skip-double-colon-forward) + (skip-syntax-forward "w_"))) + (setq end (point))) + (buffer-substring-no-properties beg end))))) + +(defun clang-include-fixer--skip-double-colon-forward () + "Skip a double colon. +When the next two characters are '::', skip them and return +non-nil. Otherwise return nil." + (let ((end (+ (point) 2))) + (when (and (<= end (point-max)) + (string-equal (buffer-substring-no-properties (point) end) "::")) + (goto-char end) + t))) + +(defun clang-include-fixer--skip-double-colon-backward () + "Skip a double colon. +When the previous two characters are '::', skip them and return +non-nil. Otherwise return nil." + (let ((beg (- (point) 2))) + (when (and (>= beg (point-min)) + (string-equal (buffer-substring-no-properties beg (point)) "::")) + (goto-char beg) + t))) + +;; ‘filepos-to-bufferpos’ is new in Emacs 25.1. Provide a fallback for older +;; versions. +(defalias 'clang-include-fixer--filepos-to-bufferpos + (if (fboundp 'filepos-to-bufferpos) + 'filepos-to-bufferpos + (lambda (byte &optional _quality _coding-system) + (byte-to-position (1+ byte))))) + +;; ‘format-message’ is new in Emacs 25.1. Provide a fallback for older +;; versions. +(defalias 'clang-include-fixer--format-message + (if (fboundp 'format-message) 'format-message 'format)) + +;; ‘file-local-name’ is new in Emacs 26.1. Provide a fallback for older +;; versions. +(defalias 'clang-include-fixer--file-local-name + (if (fboundp 'file-local-name) #'file-local-name + (lambda (file) (or (file-remote-p file 'localname) file)))) + +(provide 'clang-include-fixer) +;;; clang-include-fixer.el ends here diff --git a/clang-include-fixer/tool/clang-include-fixer.py b/clang-include-fixer/tool/clang-include-fixer.py new file mode 100644 index 00000000..4c38f71e --- /dev/null +++ b/clang-include-fixer/tool/clang-include-fixer.py @@ -0,0 +1,210 @@ +# This file is a minimal clang-include-fixer vim-integration. To install: +# - Change 'binary' if clang-include-fixer is not on the path (see below). +# - Add to your .vimrc: +# +# noremap <leader>cf :pyf path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/tool/clang-include-fixer.py<cr> +# +# This enables clang-include-fixer for NORMAL and VISUAL mode. Change +# "<leader>cf" to another binding if you need clang-include-fixer on a +# different key. +# +# To set up clang-include-fixer, see +# http://clang.llvm.org/extra/clang-include-fixer.html +# +# With this integration you can press the bound key and clang-include-fixer will +# be run on the current buffer. +# +# It operates on the current, potentially unsaved buffer and does not create +# or save any files. To revert a fix, just undo. + +import argparse +import difflib +import json +import re +import subprocess +import vim + +# set g:clang_include_fixer_path to the path to clang-include-fixer if it is not +# on the path. +# Change this to the full path if clang-include-fixer is not on the path. +binary = 'clang-include-fixer' +if vim.eval('exists("g:clang_include_fixer_path")') == "1": + binary = vim.eval('g:clang_include_fixer_path') + +maximum_suggested_headers = 3 +if vim.eval('exists("g:clang_include_fixer_maximum_suggested_headers")') == "1": + maximum_suggested_headers = max( + 1, + vim.eval('g:clang_include_fixer_maximum_suggested_headers')) + +increment_num = 5 +if vim.eval('exists("g:clang_include_fixer_increment_num")') == "1": + increment_num = max( + 1, + vim.eval('g:clang_include_fixer_increment_num')) + +jump_to_include = False +if vim.eval('exists("g:clang_include_fixer_jump_to_include")') == "1": + jump_to_include = vim.eval('g:clang_include_fixer_jump_to_include') != "0" + +query_mode = False +if vim.eval('exists("g:clang_include_fixer_query_mode")') == "1": + query_mode = vim.eval('g:clang_include_fixer_query_mode') != "0" + + +def GetUserSelection(message, headers, maximum_suggested_headers): + eval_message = message + '\n' + for idx, header in enumerate(headers[0:maximum_suggested_headers]): + eval_message += "({0}). {1}\n".format(idx + 1, header) + eval_message += "Enter (q) to quit;" + if maximum_suggested_headers < len(headers): + eval_message += " (m) to show {0} more candidates.".format( + min(increment_num, len(headers) - maximum_suggested_headers)) + + eval_message += "\nSelect (default 1): " + res = vim.eval("input('{0}')".format(eval_message)) + if res == '': + # choose the top ranked header by default + idx = 1 + elif res == 'q': + raise Exception(' Insertion cancelled...') + elif res == 'm': + return GetUserSelection(message, + headers, maximum_suggested_headers + increment_num) + else: + try: + idx = int(res) + if idx <= 0 or idx > len(headers): + raise Exception() + except Exception: + # Show a new prompt on invalid option instead of aborting so that users + # don't need to wait for another clang-include-fixer run. + print >> sys.stderr, "Invalid option:", res + return GetUserSelection(message, headers, maximum_suggested_headers) + return headers[idx - 1] + + +def execute(command, text): + p = subprocess.Popen(command, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + return p.communicate(input=text) + + +def InsertHeaderToVimBuffer(header, text): + command = [binary, "-stdin", "-insert-header=" + json.dumps(header), + vim.current.buffer.name] + stdout, stderr = execute(command, text) + if stderr: + raise Exception(stderr) + if stdout: + lines = stdout.splitlines() + sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines) + line_num = None + for op in reversed(sequence.get_opcodes()): + if op[0] != 'equal': + vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]] + if op[0] == 'insert': + # line_num in vim is 1-based. + line_num = op[1] + 1 + + if jump_to_include and line_num: + vim.current.window.cursor = (line_num, 0) + + +# The vim internal implementation (expand("cword"/"cWORD")) doesn't support +# our use case very well, we re-implement our own one. +def get_symbol_under_cursor(): + line = vim.eval("line(\".\")") + # column number in vim is 1-based. + col = int(vim.eval("col(\".\")")) - 1 + line_text = vim.eval("getline({0})".format(line)) + if len(line_text) == 0: return "" + symbol_pos_begin = col + p = re.compile('[a-zA-Z0-9:_]') + while symbol_pos_begin >= 0 and p.match(line_text[symbol_pos_begin]): + symbol_pos_begin -= 1 + + symbol_pos_end = col + while symbol_pos_end < len(line_text) and p.match(line_text[symbol_pos_end]): + symbol_pos_end += 1 + return line_text[symbol_pos_begin+1:symbol_pos_end] + + +def main(): + parser = argparse.ArgumentParser( + description='Vim integration for clang-include-fixer') + parser.add_argument('-db', default='yaml', + help='clang-include-fixer input format.') + parser.add_argument('-input', default='', + help='String to initialize the database.') + # Don't throw exception when parsing unknown arguements to make the script + # work in neovim. + # Neovim (at least v0.2.1) somehow mangles the sys.argv in a weird way: it + # will pass additional arguments (e.g. "-c script_host.py") to sys.argv, + # which makes the script fail. + args, _ = parser.parse_known_args() + + # Get the current text. + buf = vim.current.buffer + text = '\n'.join(buf) + + if query_mode: + symbol = get_symbol_under_cursor() + if len(symbol) == 0: + print "Skip querying empty symbol." + return + command = [binary, "-stdin", "-query-symbol="+get_symbol_under_cursor(), + "-db=" + args.db, "-input=" + args.input, + vim.current.buffer.name] + else: + # Run command to get all headers. + command = [binary, "-stdin", "-output-headers", "-db=" + args.db, + "-input=" + args.input, vim.current.buffer.name] + stdout, stderr = execute(command, text) + if stderr: + print >> sys.stderr, "Error while running clang-include-fixer: " + stderr + return + + include_fixer_context = json.loads(stdout) + query_symbol_infos = include_fixer_context["QuerySymbolInfos"] + if not query_symbol_infos: + print "The file is fine, no need to add a header." + return + symbol = query_symbol_infos[0]["RawIdentifier"] + # The header_infos is already sorted by clang-include-fixer. + header_infos = include_fixer_context["HeaderInfos"] + # Deduplicate headers while keeping the order, so that the same header would + # not be suggested twice. + unique_headers = [] + seen = set() + for header_info in header_infos: + header = header_info["Header"] + if header not in seen: + seen.add(header) + unique_headers.append(header) + + if not unique_headers: + print "Couldn't find a header for {0}.".format(symbol) + return + + try: + selected = unique_headers[0] + inserted_header_infos = header_infos + if len(unique_headers) > 1: + selected = GetUserSelection( + "choose a header file for {0}.".format(symbol), + unique_headers, maximum_suggested_headers) + inserted_header_infos = [ + header for header in header_infos if header["Header"] == selected] + include_fixer_context["HeaderInfos"] = inserted_header_infos + + InsertHeaderToVimBuffer(include_fixer_context, text) + print "Added #include {0} for {1}.".format(selected, symbol) + except Exception as error: + print >> sys.stderr, error.message + return + + +if __name__ == '__main__': + main() |