// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 db; }; struct IndexParam { std::unordered_map uid2file; std::unordered_map uid2multi; struct DeclInfo { Usr usr; std::string short_name; std::string qualified; }; std::unordered_map 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 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(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 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(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(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(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()) baseType = pTy->getPointeeType(); else if (const BlockPointerType *bPy = baseType->getAs()) baseType = bPy->getPointeeType(); else if (const ArrayType *aTy = dyn_cast(baseType)) baseType = aTy->getElementType(); else if (const VectorType *vTy = baseType->getAs()) baseType = vTy->getElementType(); else if (const ReferenceType *rTy = baseType->getAs()) baseType = rTy->getPointeeType(); else if (const ParenType *pTy = baseType->getAs()) baseType = pTy->desugar(); else if (deduce_auto) { if (const AutoType *aTy = baseType->getAs()) 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(tp)->getDecl(); break; case Type::ObjCObject: d = cast(tp)->getInterface(); break; case Type::ObjCInterface: d = cast(tp)->getDecl(); break; case Type::Record: case Type::Enum: d = cast(tp)->getDecl(); break; case Type::TemplateTypeParm: d = cast(tp)->getDecl(); break; case Type::TemplateSpecialization: if (specialization) *specialization = true; if (const RecordType *record = tp->getAs()) d = record->getDecl(); else d = cast(tp)->getTemplateName().getAsTemplateDecl(); break; case Type::Auto: case Type::DeducedTemplateSpecialization: tp = cast(tp)->getDeducedType().getTypePtrOrNull(); if (tp) goto try_again; break; case Type::InjectedClassName: d = cast(tp)->getDecl(); break; // FIXME: Template type parameters! case Type::Elaborated: tp = cast(tp)->getNamedType().getTypePtrOrNull(); goto try_again; default: break; } return d; } const Decl *getAdjustedDecl(const Decl *d) { while (d) { if (auto *r = dyn_cast(d)) { if (auto *s = dyn_cast(r)) { if (!s->isExplicitSpecialization()) { llvm::PointerUnion result = s->getSpecializedTemplateOrPartial(); if (result.is()) d = result.get(); else d = result.get(); continue; } } else if (auto *d1 = r->getInstantiatedFromMemberClass()) { d = d1; continue; } } else if (auto *ed = dyn_cast(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()) 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 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(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 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(d)) { t = vd->getType(); init = vd->getAnyInitializer(); def.storage = vd->getStorageClass(); } else if (auto *fd = dyn_cast(d)) { t = fd->getType(); init = fd->getInClassInitializer(); } else if (auto *bd = dyn_cast(d)) { t = bd->getType(); deduced = true; } if (!t.isNull()) { if (t->getContainedDeducedType()) { deduced = true; } else if (auto *dt = dyn_cast(t)) { // decltype(y) x; while (dt && !dt->getUnderlyingType().isNull()) { t = dt->getUnderlyingType(); dt = dyn_cast(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, 2> stack{{rd, 0}}; llvm::DenseSet 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()) { 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 relations, SourceLocation src_loc, ASTNodeInfo ast_node) override { if (!param.no_linkage) { if (auto *nd = dyn_cast(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(cast(sem_dc))) && nd->isAnonymousNamespace()) sem_dc = nd->getDeclContext()->getRedeclContext(); while ((nd = dyn_cast(cast(lex_dc))) && nd->isAnonymousNamespace()) lex_dc = nd->getDeclContext()->getRedeclContext(); } Role role = static_cast(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(origD)->getNameInfo().getSourceRange(); if (sr.getEnd().isFileID()) loc = fromTokenRange(sm, lang, sr); } break; default: break; } else { // e.g. typedef Foo 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(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(sem_dc); if (getKind(dc, ls_kind) == Kind::Type) db->toType(getUsr(dc)).def.funcs.push_back(usr); } else { const Decl *dc = cast(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(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(d)) t = vd->getType(); if (is_def || is_decl) { const Decl *dc = cast(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(sem_dc)) db->toType(getUsr(dc)).def.vars.emplace_back(usr, -1); if (!t.isNull()) { if (auto *bt = t->getAs()) { Usr usr1 = static_cast(bt->getKind()); var->def.type = usr1; if (!isa(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(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(d); auto *nd1 = cast(nd->getParent()); if (isa(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(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(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(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(d)) if (auto *ord = dyn_cast(origD)) collectRecordMembers(*type, ord); } break; case Decl::ClassTemplateSpecialization: case Decl::ClassTemplatePartialSpecialization: type->def.kind = SymbolKind::Class; if (is_def) { if (auto *ord = dyn_cast(origD)) collectRecordMembers(*type, ord); if (auto *rd = dyn_cast(d)) { Decl *d1 = nullptr; if (auto *sd = dyn_cast(rd)) d1 = sd->getSpecializedTemplate(); else if (auto *sd = dyn_cast(rd)) { llvm::PointerUnion result = sd->getSpecializedTemplateOrPartial(); if (result.is()) d1 = result.get(); else d1 = result.get(); } 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(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 struct B {typedef A 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(d)) { SmallVector 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(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 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 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 dataConsumer; const index::IndexingOptions &indexOpts; IndexParam ¶m; public: IndexFrontendAction(std::shared_ptr dataConsumer, const index::IndexingOptions &indexOpts, IndexParam ¶m) : dataConsumer(std::move(dataConsumer)), indexOpts(indexOpts), param(param) {} std::unique_ptr 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 pp = ci.getPreprocessorPtr(); pp->addPPCallbacks(std::make_unique(pp->getSourceManager(), param)); std::vector> consumers; consumers.push_back(std::make_unique(param)); consumers.push_back(index::createIndexingASTConsumer(dataConsumer, indexOpts, std::move(pp))); return std::make_unique(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 void uniquify(std::vector &a) { std::unordered_set 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 &args, const std::vector> &remapped, bool no_linkage, bool &ok) { ok = true; auto pch = std::make_shared(); llvm::IntrusiveRefCntPtr fs = llvm::vfs::getRealFileSystem(); std::shared_ptr 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> 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(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(std::make_shared(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 &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(t.c_str()); v.range = Range::fromString(s); s = strchr(s, '|'); v.usr = strtoull(s + 1, &s, 10); v.kind = static_cast(strtol(s + 1, &s, 10)); v.role = static_cast(strtol(s + 1, &s, 10)); } void reflect(JsonReader &vis, Use &v) { std::string t = vis.getString(); char *s = const_cast(t.c_str()); v.range = Range::fromString(s); s = strchr(s, '|'); v.role = static_cast(strtol(s + 1, &s, 10)); v.file_id = static_cast(strtol(s + 1, &s, 10)); } void reflect(JsonReader &vis, DeclRef &v) { std::string t = vis.getString(); char *s = const_cast(t.c_str()); v.range = Range::fromString(s); s = strchr(s, '|') + 1; v.extent = Range::fromString(s); s = strchr(s, '|'); v.role = static_cast(strtol(s + 1, &s, 10)); v.file_id = static_cast(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(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(v)); reflect(vis, v.extent); } } // namespace ccls