mirror of
https://github.com/MaskRay/ccls.git
synced 2025-01-19 03:55:49 +00:00
48f1a006b7
Since the introduction of "ColumnLimit: 120" in .clang-format, the column limit has become 120 characters instead of 80 characters. This prevents clang-format from generating too much changes even if just a small portion of a source file or header file is modified.
1420 lines
48 KiB
C++
1420 lines
48 KiB
C++
// Copyright 2017-2018 ccls Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
#include "indexer.hh"
|
|
|
|
#include "clang_tu.hh"
|
|
#include "log.hh"
|
|
#include "pipeline.hh"
|
|
#include "platform.hh"
|
|
#include "sema_manager.hh"
|
|
|
|
#include <clang/AST/AST.h>
|
|
#include <clang/Basic/TargetInfo.h>
|
|
#include <clang/Frontend/FrontendAction.h>
|
|
#include <clang/Frontend/MultiplexConsumer.h>
|
|
#include <clang/Index/IndexDataConsumer.h>
|
|
#include <clang/Index/IndexingAction.h>
|
|
#include <clang/Index/USRGeneration.h>
|
|
#include <clang/Lex/PreprocessorOptions.h>
|
|
#include <llvm/ADT/DenseSet.h>
|
|
#include <llvm/Support/CrashRecoveryContext.h>
|
|
#include <llvm/Support/Path.h>
|
|
|
|
#include <algorithm>
|
|
#include <inttypes.h>
|
|
#include <map>
|
|
#include <unordered_set>
|
|
|
|
using namespace clang;
|
|
|
|
#if LLVM_VERSION_MAJOR >= 18 // llvmorg-18-init-10631-gedd690b02e16
|
|
#define TTK_Class TagTypeKind::Class
|
|
#define TTK_Enum TagTypeKind::Enum
|
|
#define TTK_Interface TagTypeKind::Interface
|
|
#define TTK_Struct TagTypeKind::Struct
|
|
#define TTK_Union TagTypeKind::Union
|
|
#endif
|
|
|
|
namespace ccls {
|
|
namespace {
|
|
|
|
GroupMatch *multiVersionMatcher;
|
|
|
|
struct File {
|
|
std::string path;
|
|
int64_t mtime;
|
|
std::string content;
|
|
std::unique_ptr<IndexFile> db;
|
|
};
|
|
|
|
struct IndexParam {
|
|
std::unordered_map<FileID, File> uid2file;
|
|
std::unordered_map<FileID, bool> uid2multi;
|
|
struct DeclInfo {
|
|
Usr usr;
|
|
std::string short_name;
|
|
std::string qualified;
|
|
};
|
|
std::unordered_map<const Decl *, DeclInfo> decl2Info;
|
|
|
|
VFS &vfs;
|
|
ASTContext *ctx;
|
|
bool no_linkage;
|
|
IndexParam(VFS &vfs, bool no_linkage) : vfs(vfs), no_linkage(no_linkage) {}
|
|
|
|
void seenFile(FileID fid) {
|
|
// If this is the first time we have seen the file (ignoring if we are
|
|
// generating an index for it):
|
|
auto [it, inserted] = uid2file.try_emplace(fid);
|
|
if (inserted) {
|
|
#if LLVM_VERSION_MAJOR < 19
|
|
const FileEntry *fe = ctx->getSourceManager().getFileEntryForID(fid);
|
|
#else
|
|
OptionalFileEntryRef fe = ctx->getSourceManager().getFileEntryRefForID(fid);
|
|
#endif
|
|
if (!fe)
|
|
return;
|
|
std::string path = pathFromFileEntry(*fe);
|
|
it->second.path = path;
|
|
it->second.mtime = fe->getModificationTime();
|
|
if (!it->second.mtime)
|
|
if (auto tim = lastWriteTime(path))
|
|
it->second.mtime = *tim;
|
|
if (std::optional<std::string> content = readContent(path))
|
|
it->second.content = *content;
|
|
|
|
if (!vfs.stamp(path, it->second.mtime, no_linkage ? 3 : 1))
|
|
return;
|
|
it->second.db = std::make_unique<IndexFile>(path, it->second.content, no_linkage);
|
|
}
|
|
}
|
|
|
|
IndexFile *consumeFile(FileID fid) {
|
|
seenFile(fid);
|
|
return uid2file[fid].db.get();
|
|
}
|
|
|
|
bool useMultiVersion(FileID fid) {
|
|
auto it = uid2multi.try_emplace(fid);
|
|
if (it.second) {
|
|
#if LLVM_VERSION_MAJOR < 19
|
|
if (const FileEntry *fe = ctx->getSourceManager().getFileEntryForID(fid))
|
|
#else
|
|
if (OptionalFileEntryRef fe = ctx->getSourceManager().getFileEntryRefForID(fid))
|
|
#endif
|
|
it.first->second = multiVersionMatcher->matches(pathFromFileEntry(*fe));
|
|
}
|
|
return it.first->second;
|
|
}
|
|
};
|
|
|
|
StringRef getSourceInRange(const SourceManager &sm, const LangOptions &langOpts, SourceRange sr) {
|
|
SourceLocation bloc = sr.getBegin(), eLoc = sr.getEnd();
|
|
std::pair<FileID, unsigned> bInfo = sm.getDecomposedLoc(bloc), eInfo = sm.getDecomposedLoc(eLoc);
|
|
bool invalid = false;
|
|
StringRef buf = sm.getBufferData(bInfo.first, &invalid);
|
|
if (invalid)
|
|
return "";
|
|
return buf.substr(bInfo.second, eInfo.second + Lexer::MeasureTokenLength(eLoc, sm, langOpts) - bInfo.second);
|
|
}
|
|
|
|
Kind getKind(const Decl *d, SymbolKind &kind) {
|
|
switch (d->getKind()) {
|
|
case Decl::LinkageSpec:
|
|
return Kind::Invalid;
|
|
case Decl::Namespace:
|
|
case Decl::NamespaceAlias:
|
|
kind = SymbolKind::Namespace;
|
|
return Kind::Type;
|
|
case Decl::ObjCCategory:
|
|
case Decl::ObjCCategoryImpl:
|
|
case Decl::ObjCImplementation:
|
|
case Decl::ObjCInterface:
|
|
case Decl::ObjCProtocol:
|
|
kind = SymbolKind::Interface;
|
|
return Kind::Type;
|
|
case Decl::ObjCMethod:
|
|
kind = SymbolKind::Method;
|
|
return Kind::Func;
|
|
case Decl::ObjCProperty:
|
|
kind = SymbolKind::Property;
|
|
return Kind::Type;
|
|
case Decl::ClassTemplate:
|
|
kind = SymbolKind::Class;
|
|
return Kind::Type;
|
|
case Decl::FunctionTemplate:
|
|
kind = SymbolKind::Function;
|
|
return Kind::Func;
|
|
case Decl::TypeAliasTemplate:
|
|
kind = SymbolKind::TypeAlias;
|
|
return Kind::Type;
|
|
case Decl::VarTemplate:
|
|
kind = SymbolKind::Variable;
|
|
return Kind::Var;
|
|
case Decl::TemplateTemplateParm:
|
|
kind = SymbolKind::TypeParameter;
|
|
return Kind::Type;
|
|
case Decl::Enum:
|
|
kind = SymbolKind::Enum;
|
|
return Kind::Type;
|
|
case Decl::CXXRecord:
|
|
case Decl::Record:
|
|
kind = SymbolKind::Class;
|
|
// spec has no Union, use Class
|
|
if (auto *rd = dyn_cast<RecordDecl>(d))
|
|
if (rd->getTagKind() == TTK_Struct)
|
|
kind = SymbolKind::Struct;
|
|
return Kind::Type;
|
|
case Decl::ClassTemplateSpecialization:
|
|
case Decl::ClassTemplatePartialSpecialization:
|
|
kind = SymbolKind::Class;
|
|
return Kind::Type;
|
|
case Decl::TemplateTypeParm:
|
|
kind = SymbolKind::TypeParameter;
|
|
return Kind::Type;
|
|
case Decl::TypeAlias:
|
|
case Decl::Typedef:
|
|
case Decl::UnresolvedUsingTypename:
|
|
kind = SymbolKind::TypeAlias;
|
|
return Kind::Type;
|
|
case Decl::Using:
|
|
kind = SymbolKind::Null; // ignored
|
|
return Kind::Invalid;
|
|
case Decl::Binding:
|
|
kind = SymbolKind::Variable;
|
|
return Kind::Var;
|
|
case Decl::Field:
|
|
case Decl::ObjCIvar:
|
|
kind = SymbolKind::Field;
|
|
return Kind::Var;
|
|
case Decl::Function:
|
|
kind = SymbolKind::Function;
|
|
return Kind::Func;
|
|
case Decl::CXXMethod: {
|
|
const auto *md = cast<CXXMethodDecl>(d);
|
|
kind = md->isStatic() ? SymbolKind::StaticMethod : SymbolKind::Method;
|
|
return Kind::Func;
|
|
}
|
|
case Decl::CXXConstructor:
|
|
kind = SymbolKind::Constructor;
|
|
return Kind::Func;
|
|
case Decl::CXXConversion:
|
|
case Decl::CXXDestructor:
|
|
kind = SymbolKind::Method;
|
|
return Kind::Func;
|
|
case Decl::NonTypeTemplateParm:
|
|
// ccls extension
|
|
kind = SymbolKind::Parameter;
|
|
return Kind::Var;
|
|
case Decl::Var: {
|
|
auto vd = cast<VarDecl>(d);
|
|
if (vd->isStaticDataMember()) {
|
|
kind = SymbolKind::Field;
|
|
return Kind::Var;
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
case Decl::Decomposition:
|
|
kind = SymbolKind::Variable;
|
|
return Kind::Var;
|
|
case Decl::ImplicitParam:
|
|
case Decl::ParmVar:
|
|
// ccls extension
|
|
kind = SymbolKind::Parameter;
|
|
return Kind::Var;
|
|
case Decl::VarTemplateSpecialization:
|
|
case Decl::VarTemplatePartialSpecialization:
|
|
kind = SymbolKind::Variable;
|
|
return Kind::Var;
|
|
case Decl::EnumConstant:
|
|
kind = SymbolKind::EnumMember;
|
|
return Kind::Var;
|
|
case Decl::UnresolvedUsingValue:
|
|
kind = SymbolKind::Variable;
|
|
return Kind::Var;
|
|
case Decl::TranslationUnit:
|
|
return Kind::Invalid;
|
|
|
|
default:
|
|
return Kind::Invalid;
|
|
}
|
|
}
|
|
|
|
LanguageId getDeclLanguage(const Decl *d) {
|
|
switch (d->getKind()) {
|
|
default:
|
|
return LanguageId::C;
|
|
case Decl::ImplicitParam:
|
|
case Decl::ObjCAtDefsField:
|
|
case Decl::ObjCCategory:
|
|
case Decl::ObjCCategoryImpl:
|
|
case Decl::ObjCCompatibleAlias:
|
|
case Decl::ObjCImplementation:
|
|
case Decl::ObjCInterface:
|
|
case Decl::ObjCIvar:
|
|
case Decl::ObjCMethod:
|
|
case Decl::ObjCProperty:
|
|
case Decl::ObjCPropertyImpl:
|
|
case Decl::ObjCProtocol:
|
|
case Decl::ObjCTypeParam:
|
|
return LanguageId::ObjC;
|
|
case Decl::CXXConstructor:
|
|
case Decl::CXXConversion:
|
|
case Decl::CXXDestructor:
|
|
case Decl::CXXMethod:
|
|
case Decl::CXXRecord:
|
|
case Decl::ClassTemplate:
|
|
case Decl::ClassTemplatePartialSpecialization:
|
|
case Decl::ClassTemplateSpecialization:
|
|
case Decl::Friend:
|
|
case Decl::FriendTemplate:
|
|
case Decl::FunctionTemplate:
|
|
case Decl::LinkageSpec:
|
|
case Decl::Namespace:
|
|
case Decl::NamespaceAlias:
|
|
case Decl::NonTypeTemplateParm:
|
|
case Decl::StaticAssert:
|
|
case Decl::TemplateTemplateParm:
|
|
case Decl::TemplateTypeParm:
|
|
case Decl::UnresolvedUsingTypename:
|
|
case Decl::UnresolvedUsingValue:
|
|
case Decl::Using:
|
|
case Decl::UsingDirective:
|
|
case Decl::UsingShadow:
|
|
return LanguageId::Cpp;
|
|
}
|
|
}
|
|
|
|
// clang/lib/AST/DeclPrinter.cpp
|
|
QualType getBaseType(QualType t, bool deduce_auto) {
|
|
QualType baseType = t;
|
|
while (!baseType.isNull() && !baseType->isSpecifierType()) {
|
|
if (const PointerType *pTy = baseType->getAs<PointerType>())
|
|
baseType = pTy->getPointeeType();
|
|
else if (const BlockPointerType *bPy = baseType->getAs<BlockPointerType>())
|
|
baseType = bPy->getPointeeType();
|
|
else if (const ArrayType *aTy = dyn_cast<ArrayType>(baseType))
|
|
baseType = aTy->getElementType();
|
|
else if (const VectorType *vTy = baseType->getAs<VectorType>())
|
|
baseType = vTy->getElementType();
|
|
else if (const ReferenceType *rTy = baseType->getAs<ReferenceType>())
|
|
baseType = rTy->getPointeeType();
|
|
else if (const ParenType *pTy = baseType->getAs<ParenType>())
|
|
baseType = pTy->desugar();
|
|
else if (deduce_auto) {
|
|
if (const AutoType *aTy = baseType->getAs<AutoType>())
|
|
baseType = aTy->getDeducedType();
|
|
else
|
|
break;
|
|
} else
|
|
break;
|
|
}
|
|
return baseType;
|
|
}
|
|
|
|
const Decl *getTypeDecl(QualType t, bool *specialization = nullptr) {
|
|
Decl *d = nullptr;
|
|
t = getBaseType(t.getUnqualifiedType(), true);
|
|
const Type *tp = t.getTypePtrOrNull();
|
|
if (!tp)
|
|
return nullptr;
|
|
|
|
try_again:
|
|
switch (tp->getTypeClass()) {
|
|
case Type::Typedef:
|
|
d = cast<TypedefType>(tp)->getDecl();
|
|
break;
|
|
case Type::ObjCObject:
|
|
d = cast<ObjCObjectType>(tp)->getInterface();
|
|
break;
|
|
case Type::ObjCInterface:
|
|
d = cast<ObjCInterfaceType>(tp)->getDecl();
|
|
break;
|
|
case Type::Record:
|
|
case Type::Enum:
|
|
d = cast<TagType>(tp)->getDecl();
|
|
break;
|
|
case Type::TemplateTypeParm:
|
|
d = cast<TemplateTypeParmType>(tp)->getDecl();
|
|
break;
|
|
case Type::TemplateSpecialization:
|
|
if (specialization)
|
|
*specialization = true;
|
|
if (const RecordType *record = tp->getAs<RecordType>())
|
|
d = record->getDecl();
|
|
else
|
|
d = cast<TemplateSpecializationType>(tp)->getTemplateName().getAsTemplateDecl();
|
|
break;
|
|
|
|
case Type::Auto:
|
|
case Type::DeducedTemplateSpecialization:
|
|
tp = cast<DeducedType>(tp)->getDeducedType().getTypePtrOrNull();
|
|
if (tp)
|
|
goto try_again;
|
|
break;
|
|
|
|
case Type::InjectedClassName:
|
|
d = cast<InjectedClassNameType>(tp)->getDecl();
|
|
break;
|
|
|
|
// FIXME: Template type parameters!
|
|
|
|
case Type::Elaborated:
|
|
tp = cast<ElaboratedType>(tp)->getNamedType().getTypePtrOrNull();
|
|
goto try_again;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
const Decl *getAdjustedDecl(const Decl *d) {
|
|
while (d) {
|
|
if (auto *r = dyn_cast<CXXRecordDecl>(d)) {
|
|
if (auto *s = dyn_cast<ClassTemplateSpecializationDecl>(r)) {
|
|
if (!s->isExplicitSpecialization()) {
|
|
llvm::PointerUnion<ClassTemplateDecl *, ClassTemplatePartialSpecializationDecl *> result =
|
|
s->getSpecializedTemplateOrPartial();
|
|
if (result.is<ClassTemplateDecl *>())
|
|
d = result.get<ClassTemplateDecl *>();
|
|
else
|
|
d = result.get<ClassTemplatePartialSpecializationDecl *>();
|
|
continue;
|
|
}
|
|
} else if (auto *d1 = r->getInstantiatedFromMemberClass()) {
|
|
d = d1;
|
|
continue;
|
|
}
|
|
} else if (auto *ed = dyn_cast<EnumDecl>(d)) {
|
|
if (auto *d1 = ed->getInstantiatedFromMemberEnum()) {
|
|
d = d1;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
bool validateRecord(const RecordDecl *rd) {
|
|
for (const auto *i : rd->fields()) {
|
|
QualType fqt = i->getType();
|
|
if (fqt->isIncompleteType() || fqt->isDependentType())
|
|
return false;
|
|
if (const RecordType *childType = i->getType()->getAs<RecordType>())
|
|
if (const RecordDecl *child = childType->getDecl())
|
|
if (!validateRecord(child))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class IndexDataConsumer : public index::IndexDataConsumer {
|
|
public:
|
|
ASTContext *ctx;
|
|
IndexParam ¶m;
|
|
|
|
std::string getComment(const Decl *d) {
|
|
SourceManager &sm = ctx->getSourceManager();
|
|
const RawComment *rc = ctx->getRawCommentForAnyRedecl(d);
|
|
if (!rc)
|
|
return "";
|
|
StringRef raw = rc->getRawText(ctx->getSourceManager());
|
|
SourceRange sr = rc->getSourceRange();
|
|
std::pair<FileID, unsigned> bInfo = sm.getDecomposedLoc(sr.getBegin());
|
|
unsigned start_column = sm.getLineNumber(bInfo.first, bInfo.second);
|
|
std::string ret;
|
|
int pad = -1;
|
|
for (const char *p = raw.data(), *e = raw.end(); p < e;) {
|
|
// The first line starts with a comment marker, but the rest needs
|
|
// un-indenting.
|
|
unsigned skip = start_column - 1;
|
|
for (; skip > 0 && p < e && (*p == ' ' || *p == '\t'); p++)
|
|
skip--;
|
|
const char *q = p;
|
|
while (q < e && *q != '\n')
|
|
q++;
|
|
if (q < e)
|
|
q++;
|
|
// A minimalist approach to skip Doxygen comment markers.
|
|
// See https://www.stack.nl/~dimitri/doxygen/manual/docblocks.html
|
|
if (pad < 0) {
|
|
// First line, detect the length of comment marker and put into |pad|
|
|
const char *begin = p;
|
|
while (p < e && (*p == '/' || *p == '*' || *p == '-' || *p == '='))
|
|
p++;
|
|
if (p < e && (*p == '<' || *p == '!'))
|
|
p++;
|
|
if (p < e && *p == ' ')
|
|
p++;
|
|
if (p + 1 == q)
|
|
p++;
|
|
else
|
|
pad = int(p - begin);
|
|
} else {
|
|
// Other lines, skip |pad| bytes
|
|
int prefix = pad;
|
|
while (prefix > 0 && p < e && (*p == ' ' || *p == '/' || *p == '*' || *p == '<' || *p == '!'))
|
|
prefix--, p++;
|
|
}
|
|
ret.insert(ret.end(), p, q);
|
|
p = q;
|
|
}
|
|
while (ret.size() && isspace(ret.back()))
|
|
ret.pop_back();
|
|
if (StringRef(ret).endswith("*/") || StringRef(ret).endswith("\n/"))
|
|
ret.resize(ret.size() - 2);
|
|
while (ret.size() && isspace(ret.back()))
|
|
ret.pop_back();
|
|
return ret;
|
|
}
|
|
|
|
Usr getUsr(const Decl *d, IndexParam::DeclInfo **info = nullptr) const {
|
|
d = d->getCanonicalDecl();
|
|
auto [it, inserted] = param.decl2Info.try_emplace(d);
|
|
if (inserted) {
|
|
SmallString<256> usr;
|
|
index::generateUSRForDecl(d, usr);
|
|
auto &info = it->second;
|
|
info.usr = hashUsr(usr);
|
|
if (auto *nd = dyn_cast<NamedDecl>(d)) {
|
|
info.short_name = nd->getNameAsString();
|
|
llvm::raw_string_ostream os(info.qualified);
|
|
nd->printQualifiedName(os, getDefaultPolicy());
|
|
simplifyAnonymous(info.qualified);
|
|
}
|
|
}
|
|
if (info)
|
|
*info = &it->second;
|
|
return it->second.usr;
|
|
}
|
|
|
|
PrintingPolicy getDefaultPolicy() const {
|
|
PrintingPolicy pp(ctx->getLangOpts());
|
|
pp.AnonymousTagLocations = false;
|
|
pp.TerseOutput = true;
|
|
pp.PolishForDeclaration = true;
|
|
pp.ConstantsAsWritten = true;
|
|
pp.SuppressTagKeyword = true;
|
|
pp.SuppressUnwrittenScope = g_config->index.name.suppressUnwrittenScope;
|
|
pp.SuppressInitializers = true;
|
|
pp.FullyQualifiedName = false;
|
|
return pp;
|
|
}
|
|
|
|
static void simplifyAnonymous(std::string &name) {
|
|
for (std::string::size_type i = 0;;) {
|
|
if ((i = name.find("(anonymous ", i)) == std::string::npos)
|
|
break;
|
|
i++;
|
|
if (name.size() - i > 19 && name.compare(i + 10, 9, "namespace") == 0)
|
|
name.replace(i, 19, "anon ns");
|
|
else
|
|
name.replace(i, 9, "anon");
|
|
}
|
|
}
|
|
|
|
template <typename Def>
|
|
void setName(const Decl *d, std::string_view short_name, std::string_view qualified, Def &def) {
|
|
SmallString<256> str;
|
|
llvm::raw_svector_ostream os(str);
|
|
d->print(os, getDefaultPolicy());
|
|
|
|
std::string name(str.data(), str.size());
|
|
simplifyAnonymous(name);
|
|
// Remove \n in DeclPrinter.cpp "{\n" + if(!TerseOutput)something + "}"
|
|
for (std::string::size_type i = 0;;) {
|
|
if ((i = name.find("{\n}", i)) == std::string::npos)
|
|
break;
|
|
name.replace(i, 3, "{}");
|
|
}
|
|
auto i = name.find(short_name);
|
|
if (short_name.size())
|
|
while (i != std::string::npos &&
|
|
((i && isAsciiIdentifierContinue(name[i - 1])) || isAsciiIdentifierContinue(name[i + short_name.size()])))
|
|
i = name.find(short_name, i + short_name.size());
|
|
if (i == std::string::npos) {
|
|
// e.g. operator type-parameter-1
|
|
i = 0;
|
|
def.short_name_offset = 0;
|
|
def.short_name_size = name.size();
|
|
} else {
|
|
if (short_name.empty() || (i >= 2 && name[i - 2] == ':')) {
|
|
// Don't replace name with qualified name in ns::name Cls::*name
|
|
def.short_name_offset = i;
|
|
} else {
|
|
name.replace(i, short_name.size(), qualified);
|
|
def.short_name_offset = i + qualified.size() - short_name.size();
|
|
}
|
|
// name may be empty while short_name is not.
|
|
def.short_name_size = name.empty() ? 0 : short_name.size();
|
|
}
|
|
for (int paren = 0; i; i--) {
|
|
// Skip parentheses in "(anon struct)::name"
|
|
if (name[i - 1] == ')')
|
|
paren++;
|
|
else if (name[i - 1] == '(')
|
|
paren--;
|
|
else if (!(paren > 0 || isAsciiIdentifierContinue(name[i - 1]) || name[i - 1] == ':'))
|
|
break;
|
|
}
|
|
def.qual_name_offset = i;
|
|
def.detailed_name = intern(name);
|
|
}
|
|
|
|
void setVarName(const Decl *d, std::string_view short_name, std::string_view qualified, IndexVar::Def &def) {
|
|
QualType t;
|
|
const Expr *init = nullptr;
|
|
bool deduced = false;
|
|
if (auto *vd = dyn_cast<VarDecl>(d)) {
|
|
t = vd->getType();
|
|
init = vd->getAnyInitializer();
|
|
def.storage = vd->getStorageClass();
|
|
} else if (auto *fd = dyn_cast<FieldDecl>(d)) {
|
|
t = fd->getType();
|
|
init = fd->getInClassInitializer();
|
|
} else if (auto *bd = dyn_cast<BindingDecl>(d)) {
|
|
t = bd->getType();
|
|
deduced = true;
|
|
}
|
|
if (!t.isNull()) {
|
|
if (t->getContainedDeducedType()) {
|
|
deduced = true;
|
|
} else if (auto *dt = dyn_cast<DecltypeType>(t)) {
|
|
// decltype(y) x;
|
|
while (dt && !dt->getUnderlyingType().isNull()) {
|
|
t = dt->getUnderlyingType();
|
|
dt = dyn_cast<DecltypeType>(t);
|
|
}
|
|
deduced = true;
|
|
}
|
|
}
|
|
if (!t.isNull() && deduced) {
|
|
SmallString<256> str;
|
|
llvm::raw_svector_ostream os(str);
|
|
PrintingPolicy pp = getDefaultPolicy();
|
|
t.print(os, pp);
|
|
if (str.size() && (str.back() != ' ' && str.back() != '*' && str.back() != '&'))
|
|
str += ' ';
|
|
def.qual_name_offset = str.size();
|
|
def.short_name_offset = str.size() + qualified.size() - short_name.size();
|
|
def.short_name_size = short_name.size();
|
|
str += StringRef(qualified.data(), qualified.size());
|
|
def.detailed_name = intern(str);
|
|
} else {
|
|
setName(d, short_name, qualified, def);
|
|
}
|
|
if (init) {
|
|
SourceManager &sm = ctx->getSourceManager();
|
|
const LangOptions &lang = ctx->getLangOpts();
|
|
SourceRange sr = sm.getExpansionRange(init->getSourceRange()).getAsRange();
|
|
SourceLocation l = d->getLocation();
|
|
if (l.isMacroID() || !sm.isBeforeInTranslationUnit(l, sr.getBegin()))
|
|
return;
|
|
StringRef buf = getSourceInRange(sm, lang, sr);
|
|
Twine init = buf.count('\n') <= g_config->index.maxInitializerLines - 1
|
|
? buf.size() && buf[0] == ':' ? Twine(" ", buf) : Twine(" = ", buf)
|
|
: Twine();
|
|
Twine t = def.detailed_name + init;
|
|
def.hover = def.storage == SC_Static && strncmp(def.detailed_name, "static ", 7) ? intern(("static " + t).str())
|
|
: intern(t.str());
|
|
}
|
|
}
|
|
|
|
static int getFileLID(IndexFile *db, SourceManager &sm, FileID fid) {
|
|
auto [it, inserted] = db->uid2lid_and_path.try_emplace(fid);
|
|
if (inserted) {
|
|
#if LLVM_VERSION_MAJOR < 19
|
|
const FileEntry *fe = sm.getFileEntryForID(fid);
|
|
#else
|
|
OptionalFileEntryRef fe = sm.getFileEntryRefForID(fid);
|
|
#endif
|
|
if (!fe) {
|
|
it->second.first = -1;
|
|
return -1;
|
|
}
|
|
it->second.first = db->uid2lid_and_path.size() - 1;
|
|
it->second.second = pathFromFileEntry(*fe);
|
|
}
|
|
return it->second.first;
|
|
}
|
|
|
|
void addMacroUse(IndexFile *db, SourceManager &sm, Usr usr, Kind kind, SourceLocation sl) const {
|
|
FileID fid = sm.getFileID(sl);
|
|
int lid = getFileLID(db, sm, fid);
|
|
if (lid < 0)
|
|
return;
|
|
Range spell = fromTokenRange(sm, ctx->getLangOpts(), SourceRange(sl, sl));
|
|
Use use{{spell, Role::Dynamic}, lid};
|
|
switch (kind) {
|
|
case Kind::Func:
|
|
db->toFunc(usr).uses.push_back(use);
|
|
break;
|
|
case Kind::Type:
|
|
db->toType(usr).uses.push_back(use);
|
|
break;
|
|
case Kind::Var:
|
|
db->toVar(usr).uses.push_back(use);
|
|
break;
|
|
default:
|
|
llvm_unreachable("");
|
|
}
|
|
}
|
|
|
|
void collectRecordMembers(IndexType &type, const RecordDecl *rd) {
|
|
SmallVector<std::pair<const RecordDecl *, int>, 2> stack{{rd, 0}};
|
|
llvm::DenseSet<const RecordDecl *> seen;
|
|
seen.insert(rd);
|
|
while (stack.size()) {
|
|
int offset;
|
|
std::tie(rd, offset) = stack.back();
|
|
stack.pop_back();
|
|
if (!rd->isCompleteDefinition() || rd->isDependentType() || rd->isInvalidDecl() || !validateRecord(rd))
|
|
offset = -1;
|
|
for (FieldDecl *fd : rd->fields()) {
|
|
int offset1 = offset < 0 ? -1 : int(offset + ctx->getFieldOffset(fd));
|
|
if (fd->getIdentifier())
|
|
type.def.vars.emplace_back(getUsr(fd), offset1);
|
|
else if (const auto *rt1 = fd->getType()->getAs<RecordType>()) {
|
|
if (const RecordDecl *rd1 = rt1->getDecl())
|
|
if (seen.insert(rd1).second)
|
|
stack.push_back({rd1, offset1});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
IndexDataConsumer(IndexParam ¶m) : param(param) {}
|
|
void initialize(ASTContext &ctx) override { this->ctx = param.ctx = &ctx; }
|
|
bool handleDeclOccurrence(const Decl *d, index::SymbolRoleSet roles, ArrayRef<index::SymbolRelation> relations,
|
|
SourceLocation src_loc, ASTNodeInfo ast_node) override {
|
|
if (!param.no_linkage) {
|
|
if (auto *nd = dyn_cast<NamedDecl>(d); nd && nd->hasLinkage())
|
|
;
|
|
else
|
|
return true;
|
|
}
|
|
SourceManager &sm = ctx->getSourceManager();
|
|
const LangOptions &lang = ctx->getLangOpts();
|
|
FileID fid;
|
|
SourceLocation spell = sm.getSpellingLoc(src_loc);
|
|
Range loc;
|
|
auto r = sm.isMacroArgExpansion(src_loc) ? CharSourceRange::getTokenRange(spell) : sm.getExpansionRange(src_loc);
|
|
loc = fromCharSourceRange(sm, lang, r);
|
|
fid = sm.getFileID(r.getBegin());
|
|
if (fid.isInvalid())
|
|
return true;
|
|
int lid = -1;
|
|
IndexFile *db;
|
|
if (g_config->index.multiVersion && param.useMultiVersion(fid)) {
|
|
db = param.consumeFile(sm.getMainFileID());
|
|
if (!db)
|
|
return true;
|
|
param.seenFile(fid);
|
|
if (!sm.isWrittenInMainFile(r.getBegin()))
|
|
lid = getFileLID(db, sm, fid);
|
|
} else {
|
|
db = param.consumeFile(fid);
|
|
if (!db)
|
|
return true;
|
|
}
|
|
|
|
// spell, extent, comments use OrigD while most others use adjusted |D|.
|
|
const Decl *origD = ast_node.OrigD;
|
|
const DeclContext *sem_dc = origD->getDeclContext()->getRedeclContext();
|
|
const DeclContext *lex_dc = ast_node.ContainerDC->getRedeclContext();
|
|
{
|
|
const NamespaceDecl *nd;
|
|
while ((nd = dyn_cast<NamespaceDecl>(cast<Decl>(sem_dc))) && nd->isAnonymousNamespace())
|
|
sem_dc = nd->getDeclContext()->getRedeclContext();
|
|
while ((nd = dyn_cast<NamespaceDecl>(cast<Decl>(lex_dc))) && nd->isAnonymousNamespace())
|
|
lex_dc = nd->getDeclContext()->getRedeclContext();
|
|
}
|
|
Role role = static_cast<Role>(roles);
|
|
db->language = LanguageId((int)db->language | (int)getDeclLanguage(d));
|
|
|
|
bool is_decl = roles & uint32_t(index::SymbolRole::Declaration);
|
|
bool is_def = roles & uint32_t(index::SymbolRole::Definition);
|
|
if (is_decl && d->getKind() == Decl::Binding)
|
|
is_def = true;
|
|
IndexFunc *func = nullptr;
|
|
IndexType *type = nullptr;
|
|
IndexVar *var = nullptr;
|
|
SymbolKind ls_kind = SymbolKind::Unknown;
|
|
Kind kind = getKind(d, ls_kind);
|
|
|
|
if (is_def)
|
|
switch (d->getKind()) {
|
|
case Decl::CXXConversion: // *operator* int => *operator int*
|
|
case Decl::CXXDestructor: // *~*A => *~A*
|
|
case Decl::CXXMethod: // *operator*= => *operator=*
|
|
case Decl::Function: // operator delete
|
|
if (src_loc.isFileID()) {
|
|
SourceRange sr = cast<FunctionDecl>(origD)->getNameInfo().getSourceRange();
|
|
if (sr.getEnd().isFileID())
|
|
loc = fromTokenRange(sm, lang, sr);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
else {
|
|
// e.g. typedef Foo<int> gg; => Foo has an unadjusted `D`
|
|
const Decl *d1 = getAdjustedDecl(d);
|
|
if (d1 && d1 != d)
|
|
d = d1;
|
|
}
|
|
|
|
IndexParam::DeclInfo *info;
|
|
Usr usr = getUsr(d, &info);
|
|
|
|
auto do_def_decl = [&](auto *entity) {
|
|
Use use{{loc, role}, lid};
|
|
if (is_def) {
|
|
SourceRange sr = origD->getSourceRange();
|
|
entity->def.spell = {use, fromTokenRangeDefaulted(sm, lang, sr, fid, loc)};
|
|
entity->def.parent_kind = SymbolKind::File;
|
|
getKind(cast<Decl>(sem_dc), entity->def.parent_kind);
|
|
} else if (is_decl) {
|
|
SourceRange sr = origD->getSourceRange();
|
|
entity->declarations.push_back({use, fromTokenRangeDefaulted(sm, lang, sr, fid, loc)});
|
|
} else {
|
|
entity->uses.push_back(use);
|
|
return;
|
|
}
|
|
if (entity->def.comments[0] == '\0' && g_config->index.comments)
|
|
entity->def.comments = intern(getComment(origD));
|
|
};
|
|
switch (kind) {
|
|
case Kind::Invalid:
|
|
if (ls_kind == SymbolKind::Unknown)
|
|
LOG_S(INFO) << "Unhandled " << int(d->getKind()) << " " << info->qualified << " in " << db->path << ":"
|
|
<< (loc.start.line + 1) << ":" << (loc.start.column + 1);
|
|
return true;
|
|
case Kind::File:
|
|
return true;
|
|
case Kind::Func:
|
|
func = &db->toFunc(usr);
|
|
func->def.kind = ls_kind;
|
|
// Mark as Role::Implicit to span one more column to the left/right.
|
|
if (!is_def && !is_decl && (d->getKind() == Decl::CXXConstructor || d->getKind() == Decl::CXXConversion))
|
|
role = Role(role | Role::Implicit);
|
|
do_def_decl(func);
|
|
if (spell != src_loc)
|
|
addMacroUse(db, sm, usr, Kind::Func, spell);
|
|
if (func->def.detailed_name[0] == '\0')
|
|
setName(d, info->short_name, info->qualified, func->def);
|
|
if (is_def || is_decl) {
|
|
const Decl *dc = cast<Decl>(sem_dc);
|
|
if (getKind(dc, ls_kind) == Kind::Type)
|
|
db->toType(getUsr(dc)).def.funcs.push_back(usr);
|
|
} else {
|
|
const Decl *dc = cast<Decl>(lex_dc);
|
|
if (getKind(dc, ls_kind) == Kind::Func)
|
|
db->toFunc(getUsr(dc)).def.callees.push_back({loc, usr, Kind::Func, role});
|
|
}
|
|
break;
|
|
case Kind::Type:
|
|
type = &db->toType(usr);
|
|
type->def.kind = ls_kind;
|
|
do_def_decl(type);
|
|
if (spell != src_loc)
|
|
addMacroUse(db, sm, usr, Kind::Type, spell);
|
|
if ((is_def || type->def.detailed_name[0] == '\0') && info->short_name.size()) {
|
|
if (d->getKind() == Decl::TemplateTypeParm)
|
|
type->def.detailed_name = intern(info->short_name);
|
|
else
|
|
// OrigD may be detailed, e.g. "struct D : B {}"
|
|
setName(origD, info->short_name, info->qualified, type->def);
|
|
}
|
|
if (is_def || is_decl) {
|
|
const Decl *dc = cast<Decl>(sem_dc);
|
|
if (getKind(dc, ls_kind) == Kind::Type)
|
|
db->toType(getUsr(dc)).def.types.push_back(usr);
|
|
}
|
|
break;
|
|
case Kind::Var:
|
|
var = &db->toVar(usr);
|
|
var->def.kind = ls_kind;
|
|
do_def_decl(var);
|
|
if (spell != src_loc)
|
|
addMacroUse(db, sm, usr, Kind::Var, spell);
|
|
if (var->def.detailed_name[0] == '\0')
|
|
setVarName(d, info->short_name, info->qualified, var->def);
|
|
QualType t;
|
|
if (auto *vd = dyn_cast<ValueDecl>(d))
|
|
t = vd->getType();
|
|
if (is_def || is_decl) {
|
|
const Decl *dc = cast<Decl>(sem_dc);
|
|
Kind kind = getKind(dc, var->def.parent_kind);
|
|
if (kind == Kind::Func)
|
|
db->toFunc(getUsr(dc)).def.vars.push_back(usr);
|
|
else if (kind == Kind::Type && !isa<RecordDecl>(sem_dc))
|
|
db->toType(getUsr(dc)).def.vars.emplace_back(usr, -1);
|
|
if (!t.isNull()) {
|
|
if (auto *bt = t->getAs<BuiltinType>()) {
|
|
Usr usr1 = static_cast<Usr>(bt->getKind());
|
|
var->def.type = usr1;
|
|
if (!isa<EnumConstantDecl>(d))
|
|
db->toType(usr1).instances.push_back(usr);
|
|
} else if (const Decl *d1 = getAdjustedDecl(getTypeDecl(t))) {
|
|
IndexParam::DeclInfo *info1;
|
|
Usr usr1 = getUsr(d1, &info1);
|
|
var->def.type = usr1;
|
|
if (!isa<EnumConstantDecl>(d))
|
|
db->toType(usr1).instances.push_back(usr);
|
|
}
|
|
}
|
|
} else if (!var->def.spell && var->declarations.empty()) {
|
|
// e.g. lambda parameter
|
|
SourceLocation l = d->getLocation();
|
|
if (sm.getFileID(l) == fid) {
|
|
var->def.spell = {Use{{fromTokenRange(sm, lang, {l, l}), Role::Definition}, lid},
|
|
fromTokenRange(sm, lang, d->getSourceRange())};
|
|
var->def.parent_kind = SymbolKind::Method;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (d->getKind()) {
|
|
case Decl::Namespace:
|
|
if (d->isFirstDecl()) {
|
|
auto *nd = cast<NamespaceDecl>(d);
|
|
auto *nd1 = cast<Decl>(nd->getParent());
|
|
if (isa<NamespaceDecl>(nd1)) {
|
|
Usr usr1 = getUsr(nd1);
|
|
type->def.bases.push_back(usr1);
|
|
db->toType(usr1).derived.push_back(usr);
|
|
}
|
|
}
|
|
break;
|
|
case Decl::NamespaceAlias: {
|
|
auto *nad = cast<NamespaceAliasDecl>(d);
|
|
if (const NamespaceDecl *nd = nad->getNamespace()) {
|
|
Usr usr1 = getUsr(nd);
|
|
type->def.alias_of = usr1;
|
|
(void)db->toType(usr1);
|
|
}
|
|
break;
|
|
}
|
|
case Decl::CXXRecord:
|
|
if (is_def) {
|
|
auto *rd = dyn_cast<CXXRecordDecl>(d);
|
|
if (rd && rd->hasDefinition())
|
|
for (const CXXBaseSpecifier &base : rd->bases())
|
|
if (const Decl *baseD = getAdjustedDecl(getTypeDecl(base.getType()))) {
|
|
Usr usr1 = getUsr(baseD);
|
|
type->def.bases.push_back(usr1);
|
|
db->toType(usr1).derived.push_back(usr);
|
|
}
|
|
}
|
|
[[fallthrough]];
|
|
case Decl::Enum:
|
|
case Decl::Record:
|
|
if (auto *tag_d = dyn_cast<TagDecl>(d)) {
|
|
if (type->def.detailed_name[0] == '\0' && info->short_name.empty()) {
|
|
StringRef tag;
|
|
switch (tag_d->getTagKind()) {
|
|
case TTK_Struct:
|
|
tag = "struct";
|
|
break;
|
|
case TTK_Interface:
|
|
tag = "__interface";
|
|
break;
|
|
case TTK_Union:
|
|
tag = "union";
|
|
break;
|
|
case TTK_Class:
|
|
tag = "class";
|
|
break;
|
|
case TTK_Enum:
|
|
tag = "enum";
|
|
break;
|
|
}
|
|
if (TypedefNameDecl *td = tag_d->getTypedefNameForAnonDecl()) {
|
|
StringRef name = td->getName();
|
|
std::string detailed = ("anon " + tag + " " + name).str();
|
|
type->def.detailed_name = intern(detailed);
|
|
type->def.short_name_size = detailed.size();
|
|
} else {
|
|
std::string name = ("anon " + tag).str();
|
|
type->def.detailed_name = intern(name);
|
|
type->def.short_name_size = name.size();
|
|
}
|
|
}
|
|
if (is_def && !isa<EnumDecl>(d))
|
|
if (auto *ord = dyn_cast<RecordDecl>(origD))
|
|
collectRecordMembers(*type, ord);
|
|
}
|
|
break;
|
|
case Decl::ClassTemplateSpecialization:
|
|
case Decl::ClassTemplatePartialSpecialization:
|
|
type->def.kind = SymbolKind::Class;
|
|
if (is_def) {
|
|
if (auto *ord = dyn_cast<RecordDecl>(origD))
|
|
collectRecordMembers(*type, ord);
|
|
if (auto *rd = dyn_cast<CXXRecordDecl>(d)) {
|
|
Decl *d1 = nullptr;
|
|
if (auto *sd = dyn_cast<ClassTemplatePartialSpecializationDecl>(rd))
|
|
d1 = sd->getSpecializedTemplate();
|
|
else if (auto *sd = dyn_cast<ClassTemplateSpecializationDecl>(rd)) {
|
|
llvm::PointerUnion<ClassTemplateDecl *, ClassTemplatePartialSpecializationDecl *> result =
|
|
sd->getSpecializedTemplateOrPartial();
|
|
if (result.is<ClassTemplateDecl *>())
|
|
d1 = result.get<ClassTemplateDecl *>();
|
|
else
|
|
d1 = result.get<ClassTemplatePartialSpecializationDecl *>();
|
|
|
|
} else
|
|
d1 = rd->getInstantiatedFromMemberClass();
|
|
if (d1) {
|
|
Usr usr1 = getUsr(d1);
|
|
type->def.bases.push_back(usr1);
|
|
db->toType(usr1).derived.push_back(usr);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case Decl::TypeAlias:
|
|
case Decl::Typedef:
|
|
case Decl::UnresolvedUsingTypename:
|
|
if (auto *td = dyn_cast<TypedefNameDecl>(d)) {
|
|
bool specialization = false;
|
|
QualType t = td->getUnderlyingType();
|
|
if (const Decl *d1 = getAdjustedDecl(getTypeDecl(t, &specialization))) {
|
|
Usr usr1 = getUsr(d1);
|
|
IndexType &type1 = db->toType(usr1);
|
|
type->def.alias_of = usr1;
|
|
// Not visited template<class T> struct B {typedef A<T> t;};
|
|
if (specialization) {
|
|
const TypeSourceInfo *tsi = td->getTypeSourceInfo();
|
|
SourceLocation l1 = tsi->getTypeLoc().getBeginLoc();
|
|
if (sm.getFileID(l1) == fid)
|
|
type1.uses.push_back({{fromTokenRange(sm, lang, {l1, l1}), Role::Reference}, lid});
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case Decl::CXXMethod:
|
|
if (is_def || is_decl) {
|
|
if (auto *nd = dyn_cast<NamedDecl>(d)) {
|
|
SmallVector<const NamedDecl *, 8> overDecls;
|
|
ctx->getOverriddenMethods(nd, overDecls);
|
|
for (const auto *nd1 : overDecls) {
|
|
Usr usr1 = getUsr(nd1);
|
|
func->def.bases.push_back(usr1);
|
|
db->toFunc(usr1).derived.push_back(usr);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case Decl::EnumConstant:
|
|
if (is_def && strchr(var->def.detailed_name, '=') == nullptr) {
|
|
auto *ecd = cast<EnumConstantDecl>(d);
|
|
const auto &val = ecd->getInitVal();
|
|
std::string init =
|
|
" = " + (val.isSigned() ? std::to_string(val.getSExtValue()) : std::to_string(val.getZExtValue()));
|
|
var->def.hover = intern(var->def.detailed_name + init);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class IndexPPCallbacks : public PPCallbacks {
|
|
SourceManager &sm;
|
|
IndexParam ¶m;
|
|
|
|
std::pair<StringRef, Usr> getMacro(const Token &tok) const {
|
|
StringRef name = tok.getIdentifierInfo()->getName();
|
|
SmallString<256> usr("@macro@");
|
|
usr += name;
|
|
return {name, hashUsr(usr)};
|
|
}
|
|
|
|
public:
|
|
IndexPPCallbacks(SourceManager &sm, IndexParam ¶m) : sm(sm), param(param) {}
|
|
void FileChanged(SourceLocation sl, FileChangeReason reason, SrcMgr::CharacteristicKind, FileID) override {
|
|
if (reason == FileChangeReason::EnterFile)
|
|
(void)param.consumeFile(sm.getFileID(sl));
|
|
}
|
|
void InclusionDirective(SourceLocation hashLoc, const Token &tok, StringRef included, bool isAngled,
|
|
CharSourceRange filenameRange,
|
|
#if LLVM_VERSION_MAJOR >= 16 // llvmorg-16-init-15080-g854c10f8d185
|
|
OptionalFileEntryRef fileRef,
|
|
#elif LLVM_VERSION_MAJOR >= 15 // llvmorg-15-init-7692-gd79ad2f1dbc2
|
|
llvm::Optional<FileEntryRef> fileRef,
|
|
#else
|
|
const FileEntry *file,
|
|
#endif
|
|
StringRef searchPath, StringRef relativePath, const clang::Module *suggestedModule,
|
|
#if LLVM_VERSION_MAJOR >= 19 // llvmorg-19-init-1720-gda95d926f6fc
|
|
bool moduleImported,
|
|
#endif
|
|
SrcMgr::CharacteristicKind fileType) override {
|
|
#if LLVM_VERSION_MAJOR >= 15 // llvmorg-15-init-7692-gd79ad2f1dbc2
|
|
const FileEntry *file = fileRef ? &fileRef->getFileEntry() : nullptr;
|
|
#endif
|
|
if (!file)
|
|
return;
|
|
auto spell = fromCharSourceRange(sm, param.ctx->getLangOpts(), filenameRange, nullptr);
|
|
FileID fid = sm.getFileID(filenameRange.getBegin());
|
|
if (IndexFile *db = param.consumeFile(fid)) {
|
|
#if LLVM_VERSION_MAJOR < 19
|
|
std::string path = pathFromFileEntry(*file);
|
|
#else
|
|
std::string path = pathFromFileEntry(*fileRef);
|
|
#endif
|
|
if (path.size())
|
|
db->includes.push_back({spell.start.line, intern(path)});
|
|
}
|
|
}
|
|
void MacroDefined(const Token &tok, const MacroDirective *md) override {
|
|
const LangOptions &lang = param.ctx->getLangOpts();
|
|
SourceLocation sl = md->getLocation();
|
|
FileID fid = sm.getFileID(sl);
|
|
if (IndexFile *db = param.consumeFile(fid)) {
|
|
auto [name, usr] = getMacro(tok);
|
|
IndexVar &var = db->toVar(usr);
|
|
Range range = fromTokenRange(sm, lang, {sl, sl}, nullptr);
|
|
var.def.kind = SymbolKind::Macro;
|
|
var.def.parent_kind = SymbolKind::File;
|
|
if (var.def.spell)
|
|
var.declarations.push_back(*var.def.spell);
|
|
const MacroInfo *mi = md->getMacroInfo();
|
|
SourceRange sr(mi->getDefinitionLoc(), mi->getDefinitionEndLoc());
|
|
Range extent = fromTokenRange(sm, param.ctx->getLangOpts(), sr);
|
|
var.def.spell = {Use{{range, Role::Definition}}, extent};
|
|
if (var.def.detailed_name[0] == '\0') {
|
|
var.def.detailed_name = intern(name);
|
|
var.def.short_name_size = name.size();
|
|
StringRef buf = getSourceInRange(sm, lang, sr);
|
|
var.def.hover = intern(buf.count('\n') <= g_config->index.maxInitializerLines - 1
|
|
? Twine("#define ", getSourceInRange(sm, lang, sr)).str()
|
|
: Twine("#define ", name).str());
|
|
}
|
|
}
|
|
}
|
|
void MacroExpands(const Token &tok, const MacroDefinition &, SourceRange sr, const MacroArgs *) override {
|
|
SourceLocation sl = sm.getSpellingLoc(sr.getBegin());
|
|
FileID fid = sm.getFileID(sl);
|
|
if (IndexFile *db = param.consumeFile(fid)) {
|
|
IndexVar &var = db->toVar(getMacro(tok).second);
|
|
var.uses.push_back({{fromTokenRange(sm, param.ctx->getLangOpts(), {sl, sl}, nullptr), Role::Dynamic}});
|
|
}
|
|
}
|
|
void MacroUndefined(const Token &tok, const MacroDefinition &md, const MacroDirective *ud) override {
|
|
if (ud) {
|
|
SourceLocation sl = ud->getLocation();
|
|
MacroExpands(tok, md, {sl, sl}, nullptr);
|
|
}
|
|
}
|
|
void SourceRangeSkipped(SourceRange sr, SourceLocation) override {
|
|
Range range = fromCharSourceRange(sm, param.ctx->getLangOpts(), CharSourceRange::getCharRange(sr));
|
|
FileID fid = sm.getFileID(sr.getBegin());
|
|
if (fid.isValid())
|
|
if (IndexFile *db = param.consumeFile(fid))
|
|
db->skipped_ranges.push_back(range);
|
|
}
|
|
};
|
|
|
|
class IndexFrontendAction : public ASTFrontendAction {
|
|
std::shared_ptr<IndexDataConsumer> dataConsumer;
|
|
const index::IndexingOptions &indexOpts;
|
|
IndexParam ¶m;
|
|
|
|
public:
|
|
IndexFrontendAction(std::shared_ptr<IndexDataConsumer> dataConsumer, const index::IndexingOptions &indexOpts,
|
|
IndexParam ¶m)
|
|
: dataConsumer(std::move(dataConsumer)), indexOpts(indexOpts), param(param) {}
|
|
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef inFile) override {
|
|
class SkipProcessed : public ASTConsumer {
|
|
IndexParam ¶m;
|
|
const ASTContext *ctx = nullptr;
|
|
|
|
public:
|
|
SkipProcessed(IndexParam ¶m) : param(param) {}
|
|
void Initialize(ASTContext &ctx) override { this->ctx = &ctx; }
|
|
bool shouldSkipFunctionBody(Decl *d) override {
|
|
const SourceManager &sm = ctx->getSourceManager();
|
|
FileID fid = sm.getFileID(sm.getExpansionLoc(d->getLocation()));
|
|
return !(g_config->index.multiVersion && param.useMultiVersion(fid)) && !param.consumeFile(fid);
|
|
}
|
|
};
|
|
|
|
std::shared_ptr<Preprocessor> pp = ci.getPreprocessorPtr();
|
|
pp->addPPCallbacks(std::make_unique<IndexPPCallbacks>(pp->getSourceManager(), param));
|
|
std::vector<std::unique_ptr<ASTConsumer>> consumers;
|
|
consumers.push_back(std::make_unique<SkipProcessed>(param));
|
|
consumers.push_back(index::createIndexingASTConsumer(dataConsumer, indexOpts, std::move(pp)));
|
|
return std::make_unique<MultiplexConsumer>(std::move(consumers));
|
|
}
|
|
};
|
|
|
|
class IndexDiags : public DiagnosticConsumer {
|
|
public:
|
|
llvm::SmallString<64> message;
|
|
void HandleDiagnostic(DiagnosticsEngine::Level level, const clang::Diagnostic &info) override {
|
|
DiagnosticConsumer::HandleDiagnostic(level, info);
|
|
if (message.empty())
|
|
info.FormatDiagnostic(message);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
const int IndexFile::kMajorVersion = 21;
|
|
const int IndexFile::kMinorVersion = 0;
|
|
|
|
IndexFile::IndexFile(const std::string &path, const std::string &contents, bool no_linkage)
|
|
: path(path), no_linkage(no_linkage), file_contents(contents) {}
|
|
|
|
IndexFunc &IndexFile::toFunc(Usr usr) {
|
|
auto [it, inserted] = usr2func.try_emplace(usr);
|
|
if (inserted)
|
|
it->second.usr = usr;
|
|
return it->second;
|
|
}
|
|
|
|
IndexType &IndexFile::toType(Usr usr) {
|
|
auto [it, inserted] = usr2type.try_emplace(usr);
|
|
if (inserted)
|
|
it->second.usr = usr;
|
|
return it->second;
|
|
}
|
|
|
|
IndexVar &IndexFile::toVar(Usr usr) {
|
|
auto [it, inserted] = usr2var.try_emplace(usr);
|
|
if (inserted)
|
|
it->second.usr = usr;
|
|
return it->second;
|
|
}
|
|
|
|
std::string IndexFile::toString() { return ccls::serialize(SerializeFormat::Json, *this); }
|
|
|
|
template <typename T> void uniquify(std::vector<T> &a) {
|
|
std::unordered_set<T> seen;
|
|
size_t n = 0;
|
|
for (size_t i = 0; i < a.size(); i++)
|
|
if (seen.insert(a[i]).second)
|
|
a[n++] = a[i];
|
|
a.resize(n);
|
|
}
|
|
|
|
namespace idx {
|
|
void init() {
|
|
multiVersionMatcher = new GroupMatch(g_config->index.multiVersionWhitelist, g_config->index.multiVersionBlacklist);
|
|
}
|
|
|
|
IndexResult index(SemaManager *manager, WorkingFiles *wfiles, VFS *vfs, const std::string &opt_wdir,
|
|
const std::string &main, const std::vector<const char *> &args,
|
|
const std::vector<std::pair<std::string, std::string>> &remapped, bool no_linkage, bool &ok) {
|
|
ok = true;
|
|
auto pch = std::make_shared<PCHContainerOperations>();
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs = llvm::vfs::getRealFileSystem();
|
|
std::shared_ptr<CompilerInvocation> ci = buildCompilerInvocation(main, args, fs);
|
|
// e.g. .s
|
|
if (!ci)
|
|
return {};
|
|
ok = false;
|
|
// -fparse-all-comments enables documentation in the indexer and in
|
|
// code completion.
|
|
#if LLVM_VERSION_MAJOR >= 18
|
|
ci->getLangOpts().CommentOpts.ParseAllComments = g_config->index.comments > 1;
|
|
ci->getLangOpts().RetainCommentsFromSystemHeaders = true;
|
|
#else
|
|
ci->getLangOpts()->CommentOpts.ParseAllComments = g_config->index.comments > 1;
|
|
ci->getLangOpts()->RetainCommentsFromSystemHeaders = true;
|
|
#endif
|
|
std::string buf = wfiles->getContent(main);
|
|
std::vector<std::unique_ptr<llvm::MemoryBuffer>> bufs;
|
|
if (buf.size())
|
|
for (auto &[filename, content] : remapped) {
|
|
bufs.push_back(llvm::MemoryBuffer::getMemBuffer(content));
|
|
ci->getPreprocessorOpts().addRemappedFile(filename, bufs.back().get());
|
|
}
|
|
|
|
IndexDiags dc;
|
|
auto clang = std::make_unique<CompilerInstance>(pch);
|
|
clang->setInvocation(std::move(ci));
|
|
clang->createDiagnostics(
|
|
#if LLVM_VERSION_MAJOR >= 20
|
|
*fs,
|
|
#endif
|
|
&dc, false);
|
|
clang->getDiagnostics().setIgnoreAllWarnings(true);
|
|
clang->setTarget(TargetInfo::CreateTargetInfo(clang->getDiagnostics(), clang->getInvocation().TargetOpts));
|
|
if (!clang->hasTarget())
|
|
return {};
|
|
clang->getPreprocessorOpts().RetainRemappedFileBuffers = true;
|
|
clang->createFileManager(fs);
|
|
clang->setSourceManager(new SourceManager(clang->getDiagnostics(), clang->getFileManager(), true));
|
|
|
|
IndexParam param(*vfs, no_linkage);
|
|
|
|
index::IndexingOptions indexOpts;
|
|
indexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All;
|
|
if (no_linkage) {
|
|
indexOpts.IndexFunctionLocals = true;
|
|
indexOpts.IndexImplicitInstantiation = true;
|
|
indexOpts.IndexParametersInDeclarations = g_config->index.parametersInDeclarations;
|
|
indexOpts.IndexTemplateParameters = true;
|
|
}
|
|
|
|
auto action = std::make_unique<IndexFrontendAction>(std::make_shared<IndexDataConsumer>(param), indexOpts, param);
|
|
std::string reason;
|
|
{
|
|
llvm::CrashRecoveryContext crc;
|
|
auto parse = [&]() {
|
|
if (!action->BeginSourceFile(*clang, clang->getFrontendOpts().Inputs[0]))
|
|
return;
|
|
if (llvm::Error e = action->Execute()) {
|
|
reason = llvm::toString(std::move(e));
|
|
return;
|
|
}
|
|
action->EndSourceFile();
|
|
ok = true;
|
|
};
|
|
if (!crc.RunSafely(parse)) {
|
|
LOG_S(ERROR) << "clang crashed for " << main;
|
|
return {};
|
|
}
|
|
}
|
|
if (!ok) {
|
|
LOG_S(ERROR) << "failed to index " << main << (reason.empty() ? "" : ": " + reason);
|
|
return {};
|
|
}
|
|
|
|
IndexResult result;
|
|
result.n_errs = (int)dc.getNumErrors();
|
|
// clang 7 does not implement operator std::string.
|
|
result.first_error = std::string(dc.message.data(), dc.message.size());
|
|
for (auto &it : param.uid2file) {
|
|
if (!it.second.db)
|
|
continue;
|
|
std::unique_ptr<IndexFile> &entry = it.second.db;
|
|
entry->import_file = main;
|
|
entry->args = args;
|
|
for (auto &[_, it] : entry->uid2lid_and_path)
|
|
if (it.first >= 0)
|
|
entry->lid2path.emplace_back(it.first, std::move(it.second));
|
|
entry->uid2lid_and_path.clear();
|
|
for (auto &it : entry->usr2func) {
|
|
// e.g. declaration + out-of-line definition
|
|
uniquify(it.second.derived);
|
|
uniquify(it.second.uses);
|
|
}
|
|
for (auto &it : entry->usr2type) {
|
|
uniquify(it.second.derived);
|
|
uniquify(it.second.uses);
|
|
// e.g. declaration + out-of-line definition
|
|
uniquify(it.second.def.bases);
|
|
uniquify(it.second.def.funcs);
|
|
}
|
|
for (auto &it : entry->usr2var)
|
|
uniquify(it.second.uses);
|
|
|
|
// Update dependencies for the file.
|
|
for (auto &[_, file] : param.uid2file) {
|
|
const std::string &path = file.path;
|
|
if (path.empty())
|
|
continue;
|
|
if (path == entry->path)
|
|
entry->mtime = file.mtime;
|
|
else if (path != entry->import_file)
|
|
entry->dependencies[llvm::CachedHashStringRef(intern(path))] = file.mtime;
|
|
}
|
|
result.indexes.push_back(std::move(entry));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
} // namespace idx
|
|
|
|
void reflect(JsonReader &vis, SymbolRef &v) {
|
|
std::string t = vis.getString();
|
|
char *s = const_cast<char *>(t.c_str());
|
|
v.range = Range::fromString(s);
|
|
s = strchr(s, '|');
|
|
v.usr = strtoull(s + 1, &s, 10);
|
|
v.kind = static_cast<Kind>(strtol(s + 1, &s, 10));
|
|
v.role = static_cast<Role>(strtol(s + 1, &s, 10));
|
|
}
|
|
void reflect(JsonReader &vis, Use &v) {
|
|
std::string t = vis.getString();
|
|
char *s = const_cast<char *>(t.c_str());
|
|
v.range = Range::fromString(s);
|
|
s = strchr(s, '|');
|
|
v.role = static_cast<Role>(strtol(s + 1, &s, 10));
|
|
v.file_id = static_cast<int>(strtol(s + 1, &s, 10));
|
|
}
|
|
void reflect(JsonReader &vis, DeclRef &v) {
|
|
std::string t = vis.getString();
|
|
char *s = const_cast<char *>(t.c_str());
|
|
v.range = Range::fromString(s);
|
|
s = strchr(s, '|') + 1;
|
|
v.extent = Range::fromString(s);
|
|
s = strchr(s, '|');
|
|
v.role = static_cast<Role>(strtol(s + 1, &s, 10));
|
|
v.file_id = static_cast<int>(strtol(s + 1, &s, 10));
|
|
}
|
|
|
|
void reflect(JsonWriter &vis, SymbolRef &v) {
|
|
char buf[99];
|
|
snprintf(buf, sizeof buf, "%s|%" PRIu64 "|%d|%d", v.range.toString().c_str(), v.usr, int(v.kind), int(v.role));
|
|
std::string s(buf);
|
|
reflect(vis, s);
|
|
}
|
|
void reflect(JsonWriter &vis, Use &v) {
|
|
char buf[99];
|
|
snprintf(buf, sizeof buf, "%s|%d|%d", v.range.toString().c_str(), int(v.role), v.file_id);
|
|
std::string s(buf);
|
|
reflect(vis, s);
|
|
}
|
|
void reflect(JsonWriter &vis, DeclRef &v) {
|
|
char buf[99];
|
|
snprintf(buf, sizeof buf, "%s|%s|%d|%d", v.range.toString().c_str(), v.extent.toString().c_str(), int(v.role),
|
|
v.file_id);
|
|
std::string s(buf);
|
|
reflect(vis, s);
|
|
}
|
|
|
|
void reflect(BinaryReader &vis, SymbolRef &v) {
|
|
reflect(vis, v.range);
|
|
reflect(vis, v.usr);
|
|
reflect(vis, v.kind);
|
|
reflect(vis, v.role);
|
|
}
|
|
void reflect(BinaryReader &vis, Use &v) {
|
|
reflect(vis, v.range);
|
|
reflect(vis, v.role);
|
|
reflect(vis, v.file_id);
|
|
}
|
|
void reflect(BinaryReader &vis, DeclRef &v) {
|
|
reflect(vis, static_cast<Use &>(v));
|
|
reflect(vis, v.extent);
|
|
}
|
|
|
|
void reflect(BinaryWriter &vis, SymbolRef &v) {
|
|
reflect(vis, v.range);
|
|
reflect(vis, v.usr);
|
|
reflect(vis, v.kind);
|
|
reflect(vis, v.role);
|
|
}
|
|
void reflect(BinaryWriter &vis, Use &v) {
|
|
reflect(vis, v.range);
|
|
reflect(vis, v.role);
|
|
reflect(vis, v.file_id);
|
|
}
|
|
void reflect(BinaryWriter &vis, DeclRef &v) {
|
|
reflect(vis, static_cast<Use &>(v));
|
|
reflect(vis, v.extent);
|
|
}
|
|
} // namespace ccls
|