Imported Upstream version 6.10.0.49

Former-commit-id: 1d6753294b2993e1fbf92de9366bb9544db4189b
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2020-01-16 16:38:04 +00:00
parent d94e79959b
commit 468663ddbb
48518 changed files with 2789335 additions and 61176 deletions

View File

@@ -0,0 +1,28 @@
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
clangTooling
clangToolingCore
findAllSymbols
)
add_subdirectory(plugin)
add_subdirectory(tool)
add_subdirectory(find-all-symbols)

View File

@@ -0,0 +1,143 @@
//===--- FuzzySymbolIndex.cpp - Lookup symbols for autocomplete -*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,55 @@
//===--- FuzzySymbolIndex.h - Lookup symbols for autocomplete ---*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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 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

View File

@@ -0,0 +1,32 @@
//===-- InMemorySymbolIndex.cpp--------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,38 @@
//===-- InMemorySymbolIndex.h -----------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
#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

View File

@@ -0,0 +1,443 @@
//===-- IncludeFixer.cpp - Include inserter based on sema callbacks -------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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 "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());
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());
}
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 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 {};
}
DEBUG(llvm::dbgs() << "Looking up '" << Query << "' at ");
DEBUG(CI->getSourceManager()
.getLocForStartOfFile(CI->getSourceManager().getMainFileID())
.getLocWithOffset(Range.getOffset())
.print(llvm::dbgs(), CI->getSourceManager()));
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);
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

View File

@@ -0,0 +1,158 @@
//===-- IncludeFixer.h - Include inserter -----------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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
/// 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

View File

@@ -0,0 +1,116 @@
//===-- IncludeFixerContext.cpp - Include fixer context ---------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,95 @@
//===-- IncludeFixerContext.h - Include fixer context -----------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,38 @@
//===-- SymbolIndex.h - Interface for symbol-header matching ----*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,159 @@
//===-- SymbolIndexManager.cpp - Managing multiple SymbolIndices-*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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 "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());
}
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

View File

@@ -0,0 +1,66 @@
//===-- SymbolIndexManager.h - Managing multiple SymbolIndices --*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,61 @@
//===-- YamlSymbolIndex.cpp -----------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,46 @@
//===-- YamlSymbolIndex.h ---------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -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)

View File

@@ -0,0 +1,70 @@
//===-- FindAllMacros.cpp - find all macros ---------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,65 @@
//===-- FindAllMacros.h - find all macros -----------------------*- C++ -*-===//
//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,269 @@
//===-- FindAllSymbols.cpp - find all symbols--------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,63 @@
//===-- FindAllSymbols.h - find all symbols----------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

View File

@@ -0,0 +1,37 @@
//===-- FindAllSymbolsAction.cpp - find all symbols action --------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#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

Some files were not shown because too many files have changed in this diff Show More