glean/lang/clang/ast.cpp (1,915 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #include <boost/variant.hpp> #include <clang/AST/RecursiveASTVisitor.h> #include <clang/Sema/SemaConsumer.h> #include <llvm/Config/llvm-config.h> #include "folly/MapUtil.h" #include "folly/ScopeGuard.h" #include "glean/lang/clang/ast.h" #include "glean/lang/clang/index.h" // This file implements the Clang AST traversal. namespace { using namespace facebook::glean::clangx; using namespace facebook::glean::cpp; template<typename T> T identity(T x) { return x; } /// Track usage of using declarations class UsingTracker { public: explicit UsingTracker(const clang::DeclContext *context, ClangDB& d) : globalContext(getCanonicalDeclContext(context)) , currentContext(globalContext) , db(d) { CHECK_NOTNULL(currentContext); } void addNamespace(const clang::NamespaceDecl *decl) { if (decl->isAnonymousNamespace() || decl->isInline()) { auto parent = getCanonicalDeclContext(decl->getParent()); auto context = getCanonicalDeclContext(decl); forwards[parent].push_back( Forward{decl->getBeginLoc(), context, folly::none}); } } /// Add a UsingDecl to the current DeclContext void addUsingDecl( const clang::UsingDecl *decl, Fact<Cxx::UsingDeclaration> fact) { // TODO: We don't handle class-scope UsingDecls for now as we'd have to // deal with inheritance for that. if (!clang::isa<clang::RecordDecl>(decl->getDeclContext())) { const auto context = getCanonicalDeclContext(decl->getDeclContext()); for (const auto *shadow : decl->shadows()) { if (const auto target = shadow->getTargetDecl()) { if (auto canonical = clang::dyn_cast<clang::NamedDecl>( target->getCanonicalDecl())) { if (auto tpl = clang::dyn_cast<clang::RedeclarableTemplateDecl>( canonical)) { canonical = tpl->getTemplatedDecl(); } usingDecls.insert({{context, canonical}, {decl, fact}}); } } } } } void addUsingDirective( const clang::UsingDirectiveDecl *decl, Fact<Cxx::UsingDirective> fact) { if (auto context = getCanonicalDeclContext(decl->getDeclContext())) { if (auto dir_context = getCanonicalDeclContext( clang::dyn_cast<clang::DeclContext>( decl->getNominatedNamespace()))) { forwards[context].push_back( Forward{decl->getUsingLoc(), dir_context, fact}); } } } /// Given a NamedDecl and its XRefTarget, add XRefTarget.indirect if the xref /// goes through using declarations Cxx::XRefTarget retarget( const clang::Decl * FOLLY_NULLABLE base, Cxx::XRefTarget target) { if (base) { if (const auto decl = clang::dyn_cast_or_null<clang::NamedDecl>( base->getCanonicalDecl())) { const auto declContext = getCanonicalDeclContext(decl->getDeclContext()); if (declContext != currentContext) { const clang::DeclContext *parentContext = nullptr; // For non-scoped enumerators, we need to take into account that // using the enclosing namespace of the enum type also brings the // enumerators into scope: // // namespace N1 { enum E { A } }; // namespace N2 { using namespace N1; /* A is in scope */ } // namespace N3 { using N1::E; /* A isn't in scope */ } // // So we have to look for the DeclContext of the enum (but not for // the enum itself). if (auto enm = clang::dyn_cast_or_null<clang::EnumDecl>(declContext)) { if (!enm->isScoped()) { parentContext = getCanonicalDeclContext( enm->getCanonicalDecl()->getDeclContext()); } } if (parentContext != currentContext) { LookupState state{decl, declContext, parentContext, {}, {}}; lookup(currentContext, state); for (const auto& via : state.via) { target = Cxx::XRefTarget::indirect( db.fact<Cxx::XRefIndirectTarget>(via, target)); } } } } } return target; } private: struct LookupState { /// The canonical decl we're looking for const clang::NamedDecl *decl; /// The canonical context of the decl const clang::DeclContext *context; /// The context of the parent enum decl if we are looking for an enumerator const clang::DeclContext * FOLLY_NULLABLE parentContext; /// List of XRefVia populated by the lookup std::list<Cxx::XRefVia> via; /// Visited contexts populated by the lookup folly::F14FastSet<const clang::DeclContext *> visited; }; folly::Optional<const clang::DeclContext *> lookupIn( const clang::DeclContext * context, LookupState& s) { if (s.visited.find(context) == s.visited.end()) { s.visited.insert(context); if (context == s.context || context == s.parentContext) { return nullptr; } else if (auto r = folly::get_optional(usingDecls, {context, s.decl})) { s.via.push_front(Cxx::XRefVia::usingDeclaration(r->second)); // Follow chains of using decls - for instance: // // namespace foo { using std::vector; } // namespace bar { using foo::vector; } // bar::vector // // TODO: We should probably cache this rather than recomputing every // time. return getSpecifierContext(r->first->getQualifier()); } else { auto p = forwards.find(context); if (p != forwards.end()) { // If we find a decl through a using directive we'll have to place it // in the right spot in the via list so remember the current position. auto pos = s.via.begin(); for (auto i = p->second.rbegin(); i != p->second.rend(); ++i) { if (auto ctx = lookupIn(i->context, s)) { if (i->fact) { s.via.insert( pos, Cxx::XRefVia::usingDirective(i->fact.value())); } return ctx; } } } return folly::none; } } else { return folly::none; } } void lookup(const clang::DeclContext *context, LookupState& s) { while (context) { if (s.visited.find(context) != s.visited.end()) { // We might have visited this context via a using directive so we still // need to go up. Example: // // namespace parent { // namespace child { // using namespace parent; // } // } context = context->getLookupParent(); } else if (auto ctx = lookupIn(context, s)) { context = ctx.value(); } else { context = context->getLookupParent(); } if (context) { context = getCanonicalDeclContext(context); } } } public: // Execute f in a new DeclContext template<typename F> inline auto inContext(const clang::DeclContext * FOLLY_NULLABLE context, F&& f) { context = getCanonicalDeclContext(context); if (context) { std::swap(context, currentContext); } SCOPE_EXIT { if (context) { currentContext = context; } }; return f(); } // Execute f in the context of a NestedNameSpecifier. Consider: // // namespace foo { struct T { typedef int U; }; } // namespace bar { using foo::T; } // bar::T::U x; // // Here, the xref to U won't go through a using declaration but the xref to T // will so we need to make sure the xref is computed in the DeclContext of // bar. template<typename F> inline auto inNameContext( const clang::NestedNameSpecifier * FOLLY_NULLABLE spec, F&& f) { // Consider: // // namespace foo { struct T { typedef int U; }; } // namespace bar { using foo::T; T::U x; } // // Here, we need to make sure that the xref to T is computed in the // enclosing DeclContext but we can't get that from the NestedNameSpecifier. // So when we enter a NestedNameSpecified (the first call to inNameContext, // savedContext == nullptr), we store the enclosingContext in savedContext. // Then, when NestedNameSpecifier is null (i.e., we're about to visit the // left-most name), we grab the original context from savedContext. // // TODO: This is very hacky, make it better. // auto saved = savedContext; SCOPE_EXIT { savedContext = saved; }; if (savedContext == nullptr) { savedContext = currentContext; } return inContext(spec ? getSpecifierContext(spec) : savedContext, f); } // We can't traverse the entire declaration in the context of the // function as this is the wrong context for the return type and for the // function's qualified name. So just remember the current function // and change the context when traversing the body (via maybeBody). template<typename F> inline auto inFunction(const clang::FunctionDecl *fun, F&& f) { auto saved = currentFunction; currentFunction = fun; SCOPE_EXIT { currentFunction = saved; }; return f(); } // Traverse function bodies in the context of the function. // // Note that the body isn't necessarily the first statement we see after // the function decl: // // Foo::Foo() : bar([] { ... }) { ... } template<typename F> inline auto maybeBody(const clang::Stmt *body, F&& f) { if (currentFunction && body == currentFunction->getBody()) { return inContext(currentFunction, std::forward<F>(f)); } else { return f(); } } const clang::DeclContext * FOLLY_NULLABLE getSpecifierContext( const clang::NestedNameSpecifier * FOLLY_NULLABLE spec) { if (spec) { if (auto ns = spec->getAsNamespace()) { return ns; } else if (auto rec = spec->getAsRecordDecl()) { return rec; } else if (spec->getKind() == clang::NestedNameSpecifier::SpecifierKind::Global) { return globalContext; } } return nullptr; } private: static const clang::DeclContext * FOLLY_NULLABLE getCanonicalDeclContext( const clang::DeclContext * FOLLY_NULLABLE ctx) { if (ctx) { return clang::dyn_cast<clang::DeclContext>( clang::dyn_cast<clang::Decl>(ctx)->getCanonicalDecl()); } else { return nullptr; } } const clang::DeclContext * const globalContext; const clang::DeclContext *currentContext; const clang::DeclContext * FOLLY_NULLABLE savedContext = nullptr; const clang::FunctionDecl * FOLLY_NULLABLE currentFunction = nullptr; ClangDB& db; folly::F14FastMap< std::pair<const clang::DeclContext *, const clang::NamedDecl *>, std::pair<const clang::UsingDecl *, Fact<Cxx::UsingDeclaration>>> usingDecls; struct Forward { const clang::SourceLocation loc; const clang::DeclContext *context; folly::Optional<Fact<Cxx::UsingDirective>> fact; }; folly::F14FastMap<const clang::DeclContext *, std::vector<Forward>> forwards; }; struct ASTVisitor : public clang::RecursiveASTVisitor<ASTVisitor> { using Base = clang::RecursiveASTVisitor<ASTVisitor>; // Memoization of a function template< typename Key, typename Value, Value (ASTVisitor::*Compute)(Key), Key (*Transform)(Key) = &identity<Key>> struct Memo { using key_type = Key; using value_type = Value; explicit Memo(ASTVisitor& v) : visitor(v) {} Value operator()(Key key) { auto real_key = Transform(key); if (auto r = folly::get_optional(items, real_key)) { return r.value(); } else { auto value = (visitor.*Compute)(real_key); items.emplace(real_key, value); return value; } } folly::F14FastMap<Key, Value> items; ASTVisitor& visitor; }; // Memoization of a function which can fail to produce a result template<typename Key, typename Value> struct MemoOptional { using key_type = Key; using value_type = Value; explicit MemoOptional(const std::string& t, ASTVisitor& v) : visitor(v), tag(t) {} template<typename K> folly::Optional<Value> operator()(K key) { if (auto r = folly::get_optional(items, key)) { auto s = r.value(); if (s) { return s.value(); } else { LOG(FATAL) << "MemoOptional (" << tag << "): infinite loop"; } } else { items.insert({key, folly::none}); auto v = Value::compute(visitor, key); if (v) { items[key] = v; } else { items.erase(key); } return v; } } folly::F14FastMap<Key, folly::Optional<Value>> items; ASTVisitor& visitor; const std::string tag; }; // Obtain the FunctionName for a decl unless we choose to ignore it folly::Optional<Fact<Cxx::FunctionName>> functionName( const clang::NamedDecl *decl) { auto name = decl->getDeclName(); switch (name.getNameKind()) { case clang::DeclarationName::Identifier: if (auto ide = name.getAsIdentifierInfo()) { return db.fact<Cxx::FunctionName>( alt<0>(db.name(ide->getName()))); } else { return folly::none; } case clang::DeclarationName::ObjCZeroArgSelector: return folly::none; case clang::DeclarationName::ObjCOneArgSelector: return folly::none; case clang::DeclarationName::ObjCMultiArgSelector: return folly::none; case clang::DeclarationName::CXXOperatorName: return db.fact<Cxx::FunctionName>( alt<1>(name.getAsString())); case clang::DeclarationName::CXXLiteralOperatorName: return db.fact<Cxx::FunctionName>( alt<2>(name.getAsString())); case clang::DeclarationName::CXXConstructorName: return db.fact<Cxx::FunctionName>( alt<3>(std::make_tuple())); case clang::DeclarationName::CXXDestructorName: return db.fact<Cxx::FunctionName>( alt<4>(std::make_tuple())); case clang::DeclarationName::CXXConversionFunctionName: return db.fact<Cxx::FunctionName>( alt<5>(type(name.getCXXNameType()))); case clang::DeclarationName::CXXDeductionGuideName: return folly::none; case clang::DeclarationName::CXXUsingDirective: return folly::none; } } /********** * Scopes * **********/ // Scopes: global, namespace, class + access struct GlobalScope {}; struct NamespaceScope { const clang::NamespaceDecl *decl; Fact<Cxx::NamespaceQName> fact; }; struct ClassScope { const clang::RecordDecl *decl; Fact<Cxx::QName> fact; }; struct LocalScope { const clang::FunctionDecl *decl; Fact<Cxx::FunctionQName> fact; }; using Scope = boost::variant< GlobalScope, NamespaceScope, ClassScope, LocalScope>; // Translate clang::AccessSpecifier to cxx.Access static Cxx::Access access(clang::AccessSpecifier spec) { switch (spec) { case clang::AS_public: return Cxx::Access::Public; case clang::AS_protected: return Cxx::Access::Protected; case clang::AS_private: return Cxx::Access::Private; default: // TODO: is this always public? return Cxx::Access::Public; } } // Obtain the scope for a DeclContext Scope defineScope(const clang::DeclContext *ctx) { while (ctx) { if (clang::isa<clang::TranslationUnitDecl>(ctx)) { return GlobalScope{}; } else if (auto x = clang::dyn_cast<clang::NamespaceDecl>(ctx)) { if (auto r = namespaces(x)) { return NamespaceScope{x, r->qname}; } } else if (auto x = clang::dyn_cast<clang::CXXRecordDecl>(ctx)) { if (auto r = classDecls(x)) { return ClassScope{x, r->qname}; } } else if (auto x = clang::dyn_cast<clang::FunctionDecl>(ctx)) { if (auto r = funDecls(x)) { return LocalScope{x, r->qname}; } } ctx = ctx->getParent(); } return GlobalScope{}; } // Obtain the parent scope of a Decl. Scope parentScope(const clang::Decl *decl) { return scopes(decl->getDeclContext()); } Cxx::Scope scopeRepr(const Scope& scope, clang::AccessSpecifier acs) { struct GetScope : boost::static_visitor<Cxx::Scope> { const clang::AccessSpecifier access_; explicit GetScope(clang::AccessSpecifier a) : access_(a) {} result_type operator()(const GlobalScope&) const { return Cxx::Scope::global_(); } result_type operator()(const NamespaceScope& ns) const { return Cxx::Scope::namespace_(ns.fact); } result_type operator()(const ClassScope& cls) const { return Cxx::Scope::recordWithAccess(cls.fact, access(access_)); } result_type operator()(const LocalScope& fun) const { return Cxx::Scope::local(fun.fact); } }; return boost::apply_visitor(GetScope(acs), scope); } // Obtain the cxx.Scope of a Decl and translate it to clang.Scope Cxx::Scope parentScopeRepr(const clang::Decl *decl) { return scopeRepr(parentScope(decl), decl->getAccess()); } struct DeclTraits { template<typename T> static bool isDefinition(const T* decl) { return DeclTraits::getDefinition(decl) == decl; } static bool isDefinition(const clang::ObjCMethodDecl *decl) { return decl->isThisDeclarationADefinition(); } template<typename T> static const T *getDefinition(const T *decl) { return decl->getDefinition(); } static const clang::NamespaceDecl * FOLLY_NULLABLE getDefinition( const clang::NamespaceDecl *) { return nullptr; } static const clang::TypedefNameDecl * FOLLY_NULLABLE getDefinition( const clang::TypedefNameDecl *) { return nullptr; } static const clang::FieldDecl * FOLLY_NULLABLE getDefinition( const clang::FieldDecl *) { return nullptr; } static const clang::ObjCCategoryDecl *getDefinition( const clang::ObjCCategoryDecl *decl) { return decl; // TODO: is this right? } static const clang::ObjCImplementationDecl *getDefinition( const clang::ObjCImplementationDecl *decl) { return decl; // TODO: is this right? } static const clang::ObjCCategoryImplDecl *getDefinition( const clang::ObjCCategoryImplDecl *decl) { return decl; // TODO: is this right? } static const clang::ObjCMethodDecl * FOLLY_NULLABLE getDefinition( const clang::ObjCMethodDecl *) { return nullptr; } static const clang::ObjCPropertyDecl * FOLLY_NULLABLE getDefinition( const clang::ObjCPropertyDecl *) { return nullptr; } template<typename T> static const T *getCanonicalDecl(const T *decl) { return decl->getCanonicalDecl(); } static const clang::ObjCCategoryDecl *getCanonicalDecl( const clang::ObjCCategoryDecl *decl) { return decl; // TODO: is this right? } static const clang::ObjCImplementationDecl *getCanonicalDecl( const clang::ObjCImplementationDecl *decl) { return decl; // TODO: is this right? } static const clang::ObjCCategoryImplDecl *getCanonicalDecl( const clang::ObjCCategoryImplDecl *decl) { return decl; // TODO: is this right? } static const clang::ObjCPropertyDecl *getCanonicalDecl( const clang::ObjCPropertyDecl *decl) { return decl; // TODO: is this right? } template<typename T> static constexpr bool canHaveComments(const T *) { return true; } static constexpr bool canHaveComments(const clang::NamespaceDecl *) { // Clang assigns comments at the top level of a module to the first // namespace decls. Comments on namespaces probably aren't interesting, // anyway. return false; } }; template<typename Memo, typename Decl> folly::Optional<typename Memo::value_type> representative( Memo& memo, const Decl *decl, folly::Optional<typename Memo::value_type> me) { auto defn = DeclTraits::getDefinition(decl); if (defn == decl) { return me; } else if (defn != nullptr) { if (auto r = memo(defn)) { return r.value(); } } auto can = DeclTraits::getCanonicalDecl(decl); if (can != nullptr) { if (auto r = memo(can)) { return r.value(); } } return me; } template<typename Decl> struct Declare { template<typename ClangDecl> static folly::Optional<Decl> compute( ASTVisitor& visitor, const ClangDecl *decl) { auto range = visitor.db.srcRange(decl->getSourceRange()); folly::Optional<Decl> result = Decl::declare( visitor, decl, visitor.parentScopeRepr(decl), range.range); if (result) { visitor.db.declaration(range, result->declaration()); // The name location retrieval logic should be consistent with ClangD: // https://github.com/llvm/llvm-project/blob/a3a2239aaaf6860eaee591c70a016b7c5984edde/clang-tools-extra/clangd/AST.cpp#L167-L172 auto nameRange = visitor.db.srcRange(visitor.db.rangeOfToken(decl->getLocation())); visitor.db.fact<Cxx::DeclarationNameSpan>( result->declaration(), nameRange.range.file, nameRange.span); if (DeclTraits::canHaveComments(decl)){ if (auto comment = decl->getASTContext().getRawCommentForDeclNoCache(decl)) { auto crange = visitor.db.srcRange(comment->getSourceRange()); visitor.db.fact<Cxx::DeclarationComment>( result->declaration(), crange.range.file, // might be "<builtin>" crange.span); } } } return result; } }; template<typename Memo, typename Decl> void visitDeclaration(Memo& memo, const Decl *decl) { if (auto cdecl = memo(decl)) { if (DeclTraits::isDefinition(decl)) { cdecl->define(*this, decl); } auto same = representative(memo, decl, cdecl.value()); if (same) { const auto this_decl = cdecl->declaration(); const auto other_decl = same->declaration(); if (this_decl != other_decl) { db.fact<Cxx::Same>(this_decl, other_decl); } } } } /************** * Namespaces * **************/ // Obtain the parent namespace of a Decl, if any folly::Optional<Fact<Cxx::NamespaceQName>> parentNamespace( const clang::Decl* decl) { struct GetNamespace : boost::static_visitor<folly::Optional<Fact<Cxx::NamespaceQName>>> { result_type operator()(const GlobalScope&) const { return folly::none; } result_type operator()(const NamespaceScope& ns) const { return ns.fact; } result_type operator()(const ClassScope&) const { LOG(ERROR) << "Inner scope is a class, should have been a namespace"; return folly::none; } result_type operator()(const LocalScope&) const { LOG(ERROR) << "Inner scope is a function, should have been a namespace"; return folly::none; } }; return boost::apply_visitor(GetNamespace(), parentScope(decl)); } struct NamespaceDecl : Declare<NamespaceDecl> { Fact<Cxx::NamespaceQName> qname; Fact<Cxx::NamespaceDeclaration> decl; Cxx::Declaration declaration() const { return Cxx::Declaration::namespace_(decl); } static folly::Optional<NamespaceDecl> declare( ASTVisitor& visitor, const clang::NamespaceDecl *decl, Cxx::Scope, Src::Range range) { folly::Optional<Fact<Cxx::Name>> name; if (!decl->isAnonymousNamespace()) { name = visitor.db.name(decl->getName()); } auto qname = visitor.db.fact<Cxx::NamespaceQName>( maybe(name), maybe(visitor.parentNamespace(decl))); return NamespaceDecl { {} , qname , visitor.db.fact<Cxx::NamespaceDeclaration>(qname, range) }; } }; // Clang namespace visitor bool VisitNamespaceDecl(clang::NamespaceDecl *decl) { if (auto r = namespaces(decl)) { // FIXME: complete db.fact<Cxx::NamespaceDefinition>( r->decl, db.fact<Cxx::Declarations>( std::vector<Cxx::Declaration>())); } usingTracker.addNamespace(decl); return true; } /********************** * Using declarations * **********************/ bool VisitUsingDecl(const clang::UsingDecl *decl) { if (auto name = functionName(decl)) { if (auto context = usingTracker.getSpecifierContext(decl->getQualifier())) { auto range = db.srcRange(decl->getSourceRange()); auto fact = db.fact<Cxx::UsingDeclaration>( db.fact<Cxx::FunctionQName>( name.value(), scopeRepr(scopes(context), decl->getAccess())), range.range); db.declaration(range, Cxx::Declaration::usingDeclaration(fact)); usingTracker.addUsingDecl(decl, fact); } } return true; } /******************** * Using directives * ********************/ bool VisitUsingDirectiveDecl(const clang::UsingDirectiveDecl *decl) { if (auto nominated = decl->getNominatedNamespace()) { usingTracker.inNameContext(decl->getQualifier(), [&] { xrefTarget( db.rangeOfToken(decl->getIdentLocation()), XRef::toDecl(namespaces, nominated)); }); } if (auto ns = decl->getNominatedNamespaceAsWritten()) { auto range = db.srcRange(decl->getSourceRange()); auto fact = db.fact<Cxx::UsingDirective>( db.fact<Cxx::QName>( db.name(ns->getName()), parentScopeRepr(ns)), range.range); db.declaration(range, Cxx::Declaration::usingDirective(fact)); usingTracker.addUsingDirective(decl, fact); } return true; } /*********** * Enums * ***********/ Fact<Cxx::Enumerator> enumerator( Fact<Cxx::EnumDeclaration> type, const clang::EnumConstantDecl *decl) { return db.fact<Cxx::Enumerator>( db.name(decl->getName()), type, db.srcRange(decl->getSourceRange()).range); } struct EnumDecl : Declare<EnumDecl> { Fact<Cxx::EnumDeclaration> decl; Cxx::Declaration declaration() const { return Cxx::Declaration::enum_(decl); } static folly::Optional<EnumDecl> declare( ASTVisitor& visitor, const clang::EnumDecl *decl, Cxx::Scope scope, Src::Range range) { auto qname = visitor.db.fact<Cxx::QName>( visitor.db.name(decl->getName()), scope); folly::Optional<Fact<Cxx::Type>> underlying; if (auto ty = decl->getIntegerTypeSourceInfo()) { underlying = visitor.type(ty->getType()); } return EnumDecl { {} , visitor.db.fact<Cxx::EnumDeclaration>( qname, decl->isScoped(), maybe(underlying), range) }; } void define(ASTVisitor& visitor, const clang::EnumDecl *d) const { std::vector<Fact<Cxx::Enumerator>> enumerators; for (const auto& e : d->enumerators()) { enumerators.push_back(visitor.enumerator(decl, e)); } visitor.db.fact<Cxx::EnumDefinition>(decl, enumerators); } }; struct EnumeratorDecl { Fact<Cxx::Enumerator> fact; static folly::Optional<EnumeratorDecl> compute( ASTVisitor& visitor, const clang::EnumConstantDecl *decl) { if (auto ty = clang::dyn_cast<clang::EnumDecl>(decl->getDeclContext())) { if (auto enm = visitor.enumDecls(ty)) { return EnumeratorDecl{ visitor.enumerator(enm->decl, decl) }; } } return folly::none; } }; bool VisitEnumDecl(const clang::EnumDecl *decl) { visitDeclaration(enumDecls, decl); return true; } /**************** * Type aliases * ****************/ struct TypeAliasDecl : Declare<TypeAliasDecl> { Fact<Cxx::TypeAliasDeclaration> decl; Cxx::Declaration declaration() const { return Cxx::Declaration::typeAlias(decl); } static folly::Optional<TypeAliasDecl> declare( ASTVisitor& visitor, const clang::TypedefNameDecl *decl, Cxx::Scope scope, Src::Range range) { folly::Optional<Cxx::TypeAliasKind> kind; if (clang::isa<clang::TypeAliasDecl>(decl)) { kind = Cxx::TypeAliasKind::Using; } else if (clang::isa<clang::TypedefDecl>(decl)) { kind = Cxx::TypeAliasKind::Typedef; } if (kind) { auto qname = visitor.db.fact<Cxx::QName>( visitor.db.name(decl->getName()), scope); auto type = visitor.type(decl->getUnderlyingType()); return TypeAliasDecl { {} , visitor.db.fact<Cxx::TypeAliasDeclaration>( qname, type, kind.value(), range) }; } else { return folly::none; } } void define(ASTVisitor&, const clang::TypedefNameDecl *) const {} }; bool VisitTypedefNameDecl(const clang::TypedefNameDecl *decl) { visitDeclaration(typeAliasDecls, decl); return true; } /*********** * Classes * ***********/ struct ClassDecl : Declare<ClassDecl> { Fact<Cxx::QName> qname; Fact<Cxx::RecordDeclaration> decl; Cxx::Declaration declaration() const { return Cxx::Declaration::record_(decl); } static folly::Optional<ClassDecl> declare( ASTVisitor& visitor, const clang::CXXRecordDecl *decl, Cxx::Scope scope, Src::Range range) { if (decl->isInjectedClassName()) { return folly::none; } folly::Optional<Cxx::RecordKind> kind; switch (decl->getTagKind()) { case clang::TTK_Struct: kind = Cxx::RecordKind::struct_(); break; case clang::TTK_Class: kind = Cxx::RecordKind::class_(); break; case clang::TTK_Union: kind = Cxx::RecordKind::union_(); break; default: break; } if (kind) { auto qname = visitor.db.fact<Cxx::QName>( visitor.db.name(decl->getName()), scope); return ClassDecl { {} , qname , visitor.db.fact<Cxx::RecordDeclaration>( qname, kind.value(), range) }; } else { return folly::none; } } void define(ASTVisitor& visitor, const clang::CXXRecordDecl *d) const { std::vector<Cxx::RecordBase> bases; for (const auto& base : d->bases()) { if (auto ty = base.getType().getTypePtrOrNull()) { if (auto record = ty->getAsCXXRecordDecl()) { if (auto other = visitor.classDecls(record)) { bases.push_back(Cxx::RecordBase{ other->decl, // should this be base.representative? visitor.access(base.getAccessSpecifier()), base.isVirtual() }); } } } } std::vector<Cxx::Declaration> members; for (const auto& mem : d->decls()) { if (auto record = clang::dyn_cast<clang::CXXRecordDecl>(mem)) { if (!record->isInjectedClassName()) { if (auto m = visitor.classDecls(record)) { members.push_back(m->declaration()); } } } else if (auto fun = clang::dyn_cast<clang::FunctionDecl>(mem)) { // Skip implicit constructors/destructors if (fun->isImplicit()) continue; if (auto m = visitor.funDecls(fun)) { members.push_back(Cxx::Declaration::function_(m->decl)); } } else if (auto ed = clang::dyn_cast<clang::EnumDecl>(mem)) { if (auto m = visitor.enumDecls(ed)) { members.push_back(Cxx::Declaration::enum_(m->decl)); } } else if (auto vd = clang::dyn_cast<clang::VarDecl>(mem)) { if (auto m = visitor.varDecls(vd)) { members.push_back(Cxx::Declaration::variable(m->decl)); } } else if (auto tad = clang::dyn_cast<clang::TypeAliasDecl>(mem)) { if (auto m = visitor.typeAliasDecls(tad)) { members.push_back(Cxx::Declaration::typeAlias(m->decl)); } } } visitor.db.fact<Cxx::RecordDefinition>( decl, bases, visitor.db.fact<Cxx::Declarations>(members)); } static const clang::CXXRecordDecl * FOLLY_NULLABLE getInstantiatedMember( const clang::CXXRecordDecl *decl) { return decl->getInstantiatedFromMemberClass(); } static const clang::CXXRecordDecl * FOLLY_NULLABLE getSpecializedDecl( const clang::CXXRecordDecl *decl) { if (auto spec = clang::dyn_cast<clang::ClassTemplatePartialSpecializationDecl>( decl)) { auto tpl = spec->getSpecializedTemplateOrPartial(); if (auto tdecl = tpl.dyn_cast<clang::ClassTemplateDecl*>()) { return tdecl->getTemplatedDecl(); } else if (auto tspec = tpl.dyn_cast<clang::ClassTemplatePartialSpecializationDecl*>()) { return tspec; } } return nullptr; } }; // Clang record visitor bool VisitCXXRecordDecl(const clang::CXXRecordDecl *decl) { visitDeclaration(classDecls, decl); return true; } /************* * Functions * *************/ Fact<Cxx::Type> type(const clang::QualType& ty) { return db.fact<Cxx::Type>(ty.getAsString(astContext.getPrintingPolicy())); } Fact<Cxx::Signature> signature( const clang::QualType& result, clang::ArrayRef<clang::ParmVarDecl *> parameters) { std::vector<Cxx::Parameter> params; for (auto parm : parameters) { params.push_back({db.name(parm->getName()), type(parm->getType())}); } return db.fact<Cxx::Signature>(type(result), params); } static Cxx::RefQualifier refQualifier(clang::RefQualifierKind rq) { switch (rq) { case clang::RQ_None: return Cxx::RefQualifier::None_; case clang::RQ_LValue: return Cxx::RefQualifier::LValue; case clang::RQ_RValue: return Cxx::RefQualifier::RValue; default: LOG(ERROR) << "unknown clang::RefQualifiedKind"; return Cxx::RefQualifier::None_; } } struct FunDecl : Declare<FunDecl> { Fact<Cxx::FunctionQName> qname; Fact<Cxx::FunctionDeclaration> decl; bool method; Cxx::Declaration declaration() const { return Cxx::Declaration::function_(decl); } static folly::Optional<FunDecl> declare( ASTVisitor& visitor, const clang::FunctionDecl *decl, Cxx::Scope scope, Src::Range range) { // TODO: should we ignore deleted functions or have some info about them? if (decl->isDeleted() || decl->getTemplateSpecializationKind() == clang::TSK_ImplicitInstantiation) { return folly::none; } if (auto name = visitor.functionName(decl)) { folly::Optional<Cxx::MethodSignature> method; if (auto mtd = clang::dyn_cast<clang::CXXMethodDecl>(decl)) { if (mtd->isInstance()) { method = Cxx::MethodSignature{ mtd->isVirtual(), mtd->isConst(), mtd->isVolatile(), visitor.refQualifier(mtd->getRefQualifier()) }; } } auto qname = visitor.db.fact<Cxx::FunctionQName>(name.value(), scope); auto decl_fact = visitor.db.fact<Cxx::FunctionDeclaration>( qname, visitor.signature(decl->getReturnType(), decl->parameters()), maybe(method), range ); if (decl->hasAttrs()) { for (const auto attr : decl->getAttrs()) { visitor.db.fact<Cxx::FunctionAttribute>( visitor.db.fact<Cxx::Attribute>(visitor.db.srcText(attr->getRange()).str()), decl_fact ); } } return FunDecl{ {}, qname, decl_fact, method.has_value()}; } else { return folly::none; } } void define(ASTVisitor& visitor, const clang::FunctionDecl *d) const { visitor.db.fact<Cxx::FunctionDefinition>( decl, d->isInlineSpecified()); if (method) { if (auto mtd = clang::dyn_cast<clang::CXXMethodDecl>(d)) { for (const auto *base : mtd->overridden_methods()) { if (auto cbase = visitor.funDecls(base)) { visitor.db.fact<Cxx::MethodOverrides>(decl, cbase->decl); } } } } } static const clang::FunctionDecl * FOLLY_NULLABLE getInstantiatedMember( const clang::FunctionDecl *decl) { return decl->getInstantiatedFromMemberFunction(); } static const clang::FunctionDecl * FOLLY_NULLABLE getSpecializedDecl( const clang::FunctionDecl *decl) { if (auto spec = decl->getTemplateSpecializationInfo()) { return spec->getTemplate()->getTemplatedDecl(); } else { return nullptr; } } }; bool VisitFunctionDecl(const clang::FunctionDecl *decl) { visitDeclaration(funDecls, decl); return true; } /************* * Variables * *************/ struct VarDecl : Declare<VarDecl> { Fact<Cxx::QName> qname; Fact<Cxx::VariableDeclaration> decl; Cxx::Declaration declaration() const { return Cxx::Declaration::variable(decl); } static folly::Optional<Cxx::GlobalVariableKind> globalKind( const clang::VarDecl *decl) { if (decl->isLocalVarDeclOrParm()) { return folly::none; } if (decl->isStaticDataMember()) { return Cxx::GlobalVariableKind::StaticMember; } else { switch (decl->getStorageClass()) { case clang::SC_None: return Cxx::GlobalVariableKind::SimpleVariable; case clang::SC_Extern: return Cxx::GlobalVariableKind::SimpleVariable; case clang::SC_Static: return Cxx::GlobalVariableKind::StaticVariable; case clang::SC_PrivateExtern: return folly::none; case clang::SC_Auto: return folly::none; case clang::SC_Register: return folly::none; } } } static Cxx::GlobalVariableAttribute globalAttribute( const clang::VarDecl *decl) { if (decl->isConstexpr()) { return Cxx::GlobalVariableAttribute::Constexpr; } else if (decl->isInline()) { return Cxx::GlobalVariableAttribute::Inline; } else { return Cxx::GlobalVariableAttribute::Plain; } } static folly::Optional<VarDecl> declare( ASTVisitor& visitor, const clang::VarDecl *decl, Cxx::Scope scope, Src::Range range) { if (auto kind = globalKind(decl)) { auto qname = visitor.db.fact<Cxx::QName>( visitor.db.name(decl->getName()), scope); return VarDecl { {} , qname , visitor.db.fact<Cxx::VariableDeclaration>( qname, visitor.type(decl->getType()), Cxx::VariableKind::global_( Cxx::GlobalVariable{ kind.value(), globalAttribute(decl), decl->isThisDeclarationADefinition() == clang::VarDecl::Definition }), range) }; } else { return folly::none; } } static Cxx::VariableKind fieldKind( ASTVisitor& visitor, const clang::FieldDecl *decl) { folly::Optional<uint64_t> bitsize; if (auto size_expr = decl->getBitWidth()) { // Consider the following code: // // template<class T> class U { unsigned i : sizeof(T); }; // // Here, i is a bit field but it doesn't have a fixed bit size. In fact, // Clang segfaults if we call getBitWidthValue on it. So let's give it // size 0 for now - we should probably extend the schema eventually. bitsize = size_expr->isValueDependent() ? 0 : decl->getBitWidthValue(visitor.astContext); } if (auto ivar = clang::dyn_cast<clang::ObjCIvarDecl>(decl)) { return Cxx::VariableKind::ivar( Cxx::ObjcIVar{ivar->getSynthesize(), maybe(bitsize)} ); } else { return Cxx::VariableKind::field( Cxx::Field{decl->isMutable(), maybe(bitsize)} ); } } static folly::Optional<VarDecl> declare( ASTVisitor& visitor, const clang::FieldDecl *decl, Cxx::Scope scope, Src::Range range) { auto qname = visitor.db.fact<Cxx::QName>( visitor.db.name(decl->getName()), scope); return VarDecl { {} , qname , visitor.db.fact<Cxx::VariableDeclaration>( qname, visitor.type(decl->getType()), fieldKind(visitor, decl), range) }; } void define(ASTVisitor&, const clang::VarDecl *) const {} void define(ASTVisitor&, const clang::FieldDecl *) const {} static const clang::VarDecl * FOLLY_NULLABLE getInstantiatedMember( const clang::VarDecl *decl) { return decl->getInstantiatedFromStaticDataMember(); } static const clang::VarDecl * FOLLY_NULLABLE getSpecializedDecl( const clang::VarDecl *decl) { if (auto spec = clang::dyn_cast<clang::VarTemplateSpecializationDecl>(decl)) { auto tpl = spec->getSpecializedTemplateOrPartial(); if (auto tdecl = tpl.dyn_cast<clang::VarTemplateDecl*>()) { return tdecl->getTemplatedDecl(); } else if (auto tspec = tpl.dyn_cast<clang::VarTemplatePartialSpecializationDecl*>()) { return tspec; } } return nullptr; } }; bool VisitVarDecl(const clang::VarDecl *decl) { visitDeclaration(varDecls, decl); return true; } bool VisitFieldDecl(const clang::FieldDecl *decl) { visitDeclaration(varDecls, decl); return true; } /******************* * ObjC containers * *******************/ std::vector<Fact<Cxx::ObjcContainerDeclaration>> objcContainerProtocols( const clang::ObjCContainerDecl *decl) { const clang::ObjCProtocolList *list; if (auto prot = clang::dyn_cast<clang::ObjCProtocolDecl>(decl)) { list = &prot->getReferencedProtocols(); } else if (auto iface = clang::dyn_cast<clang::ObjCInterfaceDecl>(decl)) { list = &iface->getReferencedProtocols(); } else if (auto cat = clang::dyn_cast<clang::ObjCCategoryDecl>(decl)) { list = &cat->getReferencedProtocols(); } else { list = nullptr; } std::vector<Fact<Cxx::ObjcContainerDeclaration>> protocols; if (list) { if (auto loc = list->loc_begin()) { for (auto prot : *list) { // NOTE: Protocols don't seem to be visited anywhere so record xrefs // here. xrefObjCProtocolDecl(*loc, prot); ++loc; if (auto p = objcContainerDecls(prot)) { protocols.push_back(p->decl); } } } } return protocols; } Fact<Cxx::Declarations> objcContainerMembers( const clang::ObjCContainerDecl *decl) { std::vector<Cxx::Declaration> members; for (auto member : decl->decls()) { if (auto method = clang::dyn_cast<clang::ObjCMethodDecl>(member)) { if (auto d = objcMethodDecls(method)) { members.push_back(Cxx::Declaration::objcMethod(d->decl)); } } if (auto property = clang::dyn_cast<clang::ObjCPropertyDecl>(member)) { if (auto d = objcPropertyDecls(property)) { members.push_back(Cxx::Declaration::objcProperty(d->decl)); } } if (auto ivar = clang::dyn_cast<clang::ObjCIvarDecl>(member)) { if (auto d = varDecls(ivar)) { members.push_back(Cxx::Declaration::variable(d->decl)); } } } return db.fact<Cxx::Declarations>(std::move(members)); } struct ObjcContainerDecl : Declare<ObjcContainerDecl> { Cxx::ObjcContainerId id; Fact<Cxx::ObjcContainerDeclaration> decl; Cxx::Declaration declaration() const { return Cxx::Declaration::objcContainer(decl); } template<typename Decl, typename... Decls, typename ClangDecl> static folly::Optional<ObjcContainerDecl> findIn( ASTVisitor& visitor, const ClangDecl *decl) { if (auto p = clang::dyn_cast<Decl>(decl)) { return visitor.objcContainerDecls(p); } else { return findIn<Decls...>(visitor, decl); } } template<typename ClangDecl> static folly::Optional<ObjcContainerDecl> findIn( ASTVisitor&, const ClangDecl *) { return folly::none; } template<typename ClangDecl> static folly::Optional<ObjcContainerDecl> find( ASTVisitor& visitor, const ClangDecl * FOLLY_NULLABLE decl) { if (decl) { return findIn< clang::ObjCProtocolDecl, clang::ObjCInterfaceDecl, clang::ObjCCategoryDecl, clang::ObjCImplementationDecl, clang::ObjCCategoryImplDecl>(visitor, decl); } else { return folly::none; } } template<typename Decl> static folly::Optional<Cxx::ObjcCategoryId> categoryId( ASTVisitor& visitor, const Decl *decl) { if (auto iface = decl->getClassInterface()) { return Cxx::ObjcCategoryId{ visitor.db.name(iface->getName()), visitor.db.name(decl->getName()) }; } else { return folly::none; } } static folly::Optional<Cxx::ObjcContainerId> containerId( ASTVisitor& visitor, const clang::ObjCProtocolDecl *decl) { return Cxx::ObjcContainerId::protocol(visitor.db.name(decl->getName())); } static folly::Optional<Cxx::ObjcContainerId> containerId( ASTVisitor& visitor, const clang::ObjCInterfaceDecl *decl) { return Cxx::ObjcContainerId::interface_( visitor.db.name(decl->getName())); } static folly::Optional<Cxx::ObjcContainerId> containerId( ASTVisitor& visitor, const clang::ObjCCategoryDecl *decl) { if (decl->IsClassExtension()) { if (auto iface = decl->getClassInterface()) { return Cxx::ObjcContainerId::extensionInterface( visitor.db.name(iface->getName())); } else { return folly::none; } } else if (auto id = categoryId(visitor, decl)) { return Cxx::ObjcContainerId::categoryInterface(id.value()); } else { return folly::none; } } static folly::Optional<Cxx::ObjcContainerId> containerId( ASTVisitor& visitor, const clang::ObjCImplementationDecl *decl) { if (auto iface = decl->getClassInterface()) { return Cxx::ObjcContainerId::implementation( visitor.db.name(iface->getName())); } else { return folly::none; } } static folly::Optional<Cxx::ObjcContainerId> containerId( ASTVisitor& visitor, const clang::ObjCCategoryImplDecl *decl) { if (auto id = categoryId(visitor, decl)) { return Cxx::ObjcContainerId::categoryImplementation(id.value()); } else { return folly::none; } } template<typename Decl> static folly::Optional<ObjcContainerDecl> declare( ASTVisitor& visitor, const Decl *decl, Cxx::Scope, Src::Range range) { if (folly::Optional<Cxx::ObjcContainerId> id = ObjcContainerDecl::containerId(visitor, decl)) { return ObjcContainerDecl { {} , id.value() , visitor.db.fact<Cxx::ObjcContainerDeclaration>(id.value(), range) }; } else { return folly::none; } } static std::vector<ObjcContainerDecl> implements( ASTVisitor&, const clang::ObjCProtocolDecl *) { return {}; } static std::vector<ObjcContainerDecl> implements( ASTVisitor&, const clang::ObjCInterfaceDecl *) { return {}; } static std::vector<ObjcContainerDecl> implements( ASTVisitor&, const clang::ObjCCategoryDecl *) { return {}; } static std::vector<ObjcContainerDecl> implements( ASTVisitor& visitor, const clang::ObjCImplementationDecl *decl) { std::vector<ObjcContainerDecl> decls; if (auto iface = decl->getClassInterface()) { if (auto r = visitor.objcContainerDecls(iface)) { decls.push_back(r.value()); } for (auto cat : iface->known_extensions()) { if (auto r = visitor.objcContainerDecls(cat)) { decls.push_back(r.value()); } } } return decls; } static std::vector<ObjcContainerDecl> implements( ASTVisitor& visitor, const clang::ObjCCategoryImplDecl *decl) { std::vector<ObjcContainerDecl> decls; if (auto cat = decl->getCategoryDecl()) { if (auto r = visitor.objcContainerDecls(cat)) { decls.push_back(r.value()); } } return decls; } static folly::Optional<ObjcContainerDecl> base( ASTVisitor&, const clang::ObjCImplementationDecl *) { return folly::none; } static folly::Optional<ObjcContainerDecl> base( ASTVisitor&, const clang::ObjCCategoryDecl*) { return folly::none; } static folly::Optional<ObjcContainerDecl> base( ASTVisitor&, const clang::ObjCProtocolDecl*) { return folly::none; } static folly::Optional<ObjcContainerDecl> base( ASTVisitor& visitor, const clang::ObjCInterfaceDecl* decl) { if (auto sclass = decl->getSuperClass()) { if (auto r = visitor.objcContainerDecls(sclass)) { return r.value(); } } return folly::none; } static folly::Optional<ObjcContainerDecl> base( ASTVisitor&, const clang::ObjCCategoryImplDecl*) { return folly::none; } template<typename Decl> void define(ASTVisitor& visitor, const Decl *d) { visitor.db.fact<Cxx::ObjcContainerDefinition>( decl, visitor.objcContainerProtocols(d), visitor.objcContainerMembers(d)); auto xs = implements(visitor, d); for (auto x : xs) { visitor.db.fact<Cxx::ObjcImplements>(decl, x.decl); } if(auto bs = base(visitor, d)) { visitor.db.fact<Cxx::ObjcContainerBase>(decl, bs->decl); } } }; bool VisitObjCProtocolDecl(const clang::ObjCProtocolDecl *decl) { visitDeclaration(objcContainerDecls, decl); return true; } bool VisitObjCInterfaceDecl(const clang::ObjCInterfaceDecl *decl) { visitDeclaration(objcContainerDecls, decl); return true; } bool VisitObjCCategoryDecl(const clang::ObjCCategoryDecl *decl) { visitDeclaration(objcContainerDecls, decl); return true; } bool VisitObjCImplementationDecl(const clang::ObjCImplementationDecl *decl) { visitDeclaration(objcContainerDecls, decl); return true; } bool VisitObjCCategoryImplDecl(const clang::ObjCCategoryImplDecl *decl) { visitDeclaration(objcContainerDecls, decl); return true; } /**************** * ObjC methods * ****************/ Fact<Cxx::ObjcSelector> objcSelector(const clang::Selector& sel) { std::vector<std::string> sels; if (sel.isUnarySelector()) { // It seems that for unary selectors (i.e., selectors which are just a // name with no arguments), getNameForSlot(0) returns "". sels.push_back(sel.getAsString()); } else { const size_t n = sel.getNumArgs(); for (size_t i = 0; i < n; ++i) { sels.push_back(static_cast<std::string>(sel.getNameForSlot(i))); } } return db.fact<Cxx::ObjcSelector>(sels); } struct ObjcMethodDecl : Declare<ObjcMethodDecl> { Fact<Cxx::ObjcMethodDeclaration> decl; Cxx::Declaration declaration() const { return Cxx::Declaration::objcMethod(decl); } static folly::Optional<ObjcMethodDecl> declare( ASTVisitor& visitor, const clang::ObjCMethodDecl *d, Cxx::Scope, Src::Range range) { if (auto container = ObjcContainerDecl::find(visitor, d->getDeclContext())) { return ObjcMethodDecl { {} , visitor.db.fact<Cxx::ObjcMethodDeclaration>( visitor.objcSelector(d->getSelector()), container->id, visitor.signature(d->getReturnType(), d->parameters()), d->isInstanceMethod(), d->isOptional(), d->isPropertyAccessor(), range) }; } else { return folly::none; } } void define(ASTVisitor& visitor, const clang::ObjCMethodDecl *) const { visitor.db.fact<Cxx::ObjcMethodDefinition>(decl); } }; bool VisitObjCMethodDecl(const clang::ObjCMethodDecl *decl) { visitDeclaration(objcMethodDecls, decl); return true; } /******************* * ObjC properties * *******************/ struct ObjcPropertyDecl : Declare<ObjcPropertyDecl> { Fact<Cxx::ObjcPropertyDeclaration> decl; Cxx::Declaration declaration() const { return Cxx::Declaration::objcProperty(decl); } static folly::Optional<ObjcPropertyDecl> declare( ASTVisitor& visitor, const clang::ObjCPropertyDecl *d, Cxx::Scope, Src::Range range) { if (auto container = ObjcContainerDecl::find(visitor, d->getDeclContext())) { return ObjcPropertyDecl { {} , visitor.db.fact<Cxx::ObjcPropertyDeclaration>( visitor.db.name(d->getName()), container->id, visitor.type(d->getType()), d->isInstanceProperty(), d->isOptional(), d->isReadOnly(), d->isAtomic(), range) }; } else { return folly::none; } } void define(ASTVisitor&, const clang::ObjCPropertyDecl *) const { // TODO: complete } }; bool VisitObjCPropertyDecl(const clang::ObjCPropertyDecl *decl) { visitDeclaration(objcPropertyDecls, decl); return true; } bool VisitObjCPropertyImplDecl(const clang::ObjCPropertyImplDecl *decl) { if (decl->getSourceRange().isValid()) { if (auto r = objcPropertyDecls(decl->getPropertyDecl())) { // TODO: remove? folly::Optional<Fact<Cxx::Name>> name; if (decl->isIvarNameSpecified()) { name = db.name(decl->getPropertyIvarDecl()->getName()); } if (auto ivar = decl->getPropertyIvarDecl()) { if (auto s = varDecls(ivar)) { db.fact<Cxx::ObjcPropertyIVar>(r->decl, s->decl); } } db.fact<Cxx::ObjcPropertyImplementation>( r->decl, decl->getPropertyImplementation() == clang::ObjCPropertyImplDecl::Synthesize ? Cxx::ObjcPropertyKind::Synthesize : Cxx::ObjcPropertyKind::Dynamic, maybe(name), db.srcRange(decl->getSourceRange()).range); } } return true; } struct XRef { const clang::Decl *primary; const clang::Decl *decl; folly::Optional<Cxx::XRefTarget> target; static XRef unknown(const clang::Decl *d) { return XRef{d,d,folly::none}; } static XRef to(const clang::Decl *d, folly::Optional<Cxx::XRefTarget> t) { return XRef{d,d,t}; } template<typename Memo, typename Decl> static XRef toDecl(Memo& memo, const Decl *decl) { XRef xref{decl, decl}; if (auto b = memo(decl)) { xref.target = Cxx::XRefTarget::declaration(b->declaration()); } return xref; } template<typename Memo, typename Decl> void suggest(Memo& memo, const Decl *d) { if (!target) { if (auto b = memo(d)) { target = Cxx::XRefTarget::declaration(b->declaration()); decl = d; } } } template<typename Memo, typename Decl> static XRef toTemplatableDecl(Memo& memo, const Decl *decl) { XRef xref = toDecl(memo, decl); if (auto mem = Memo::value_type::getInstantiatedMember(decl)) { decl = mem; xref.suggest(memo, decl); } else { while (auto tpl = Memo::value_type::getSpecializedDecl(decl)) { decl = tpl; xref.suggest(memo, decl); } } xref.primary = decl; return xref; } }; void xrefTarget(clang::SourceRange range, XRef xref) { const auto raw = xref.target ? xref.target.value() : unknownTarget(xref.decl); const auto wrapped = usingTracker.retarget(xref.primary, raw); folly::Optional<clang::SourceLocation> loc; if (raw == wrapped) { loc = xref.decl->getBeginLoc(); } db.xref(range, loc, wrapped); } Cxx::XRefTarget unknownTarget(const clang::Decl* decl) { return Cxx::XRefTarget::unknown(db.srcLoc(decl->getBeginLoc())); } void xrefObjCProtocolDecl( clang::SourceLocation loc, const clang::ObjCProtocolDecl *decl) { if (loc.isValid()) { xrefTarget( {loc, loc.getLocWithOffset(decl->getIdentifier()->getLength()-1)}, XRef::toDecl(objcContainerDecls, decl)); } } /***************** * Type visitors * *****************/ bool VisitTagTypeLoc(clang::TagTypeLoc tloc) { if (auto decl = tloc.getDecl()) { const auto range = tloc.getSourceRange(); XRef xref; if (auto record = clang::dyn_cast<clang::CXXRecordDecl>(decl)) { xref = XRef::toTemplatableDecl(classDecls, record); } else if (auto enm = clang::dyn_cast<clang::EnumDecl>(decl)) { xref = XRef::toDecl(enumDecls, enm); } else { xref = XRef::unknown(decl); } xrefTarget(range, xref); } return true; } bool VisitTypedefTypeLoc(clang::TypedefTypeLoc tloc) { if (auto decl = tloc.getTypedefNameDecl()) { xrefTarget( tloc.getSourceRange(), XRef::toDecl(typeAliasDecls, decl)); } return true; } void xrefTemplateName(clang::SourceLocation loc, clang::TemplateName name) { if (auto decl = name.getAsTemplateDecl()) { XRef xref; if (auto cls = clang::dyn_cast<clang::ClassTemplateDecl>(decl)) { xref = XRef::toTemplatableDecl(classDecls, cls->getTemplatedDecl()); } else if (auto alias = clang::dyn_cast<clang::TypeAliasTemplateDecl>( decl)) { xref = XRef::toDecl(typeAliasDecls, alias->getTemplatedDecl()); } else { // We don't want to xref template template parameters and I assume we // can't get functions or variables here. return; } xrefTarget(db.rangeOfToken(loc), xref); } } bool VisitTemplateSpecializationTypeLoc( clang::TemplateSpecializationTypeLoc tloc) { xrefTemplateName( tloc.getTemplateNameLoc(), tloc.getTypePtr()->getTemplateName()); return true; } bool VisitObjCObjectTypeLoc(clang::ObjCObjectTypeLoc tloc) { const auto n = tloc.getNumProtocols(); const auto locs = tloc.getProtocolLocs(); for (size_t i = 0; i < n; ++i) { xrefObjCProtocolDecl(locs[i], tloc.getProtocol(i)); } return true; } bool VisitObjCInterfaceTypeLoc(clang::ObjCInterfaceTypeLoc tloc) { xrefTarget( tloc.getLocalSourceRange(), XRef::toDecl(objcContainerDecls, tloc.getIFaceDecl())); return true; } bool TraverseElaboratedTypeLoc(clang::ElaboratedTypeLoc tloc) { return usingTracker.inNameContext(tloc.getTypePtr()->getQualifier(), [&] { return Base::TraverseElaboratedTypeLoc(tloc); }); } bool TraverseTemplateArgumentLoc(const clang::TemplateArgumentLoc& arg) { // X::T<U> is an ElaboratedType and so we traverse the T<U> bit in the // context of X. This is correct for T but wrong for U so we have to reset // the context when traversing template arguments. return usingTracker.inNameContext(nullptr, [&] { switch (arg.getArgument().getKind()) { case clang::TemplateArgument::Template: case clang::TemplateArgument::TemplateExpansion: // This handles cases like: // // template<typename T> A {}; // template<template<typename> typename T> B {}; // // B<A> x; // // The template argument A isn't a proper type here and hence isn't // traversed by the usual machinery as such. It is a TemplateName but // those don't have source locations so we have to do it in the parent // nodes. usingTracker.inNameContext( arg.getTemplateQualifierLoc().getNestedNameSpecifier(), [&] { xrefTemplateName( arg.getTemplateNameLoc(), arg.getArgument().getAsTemplateOrTemplatePattern()); }); break; default: break; } return Base::TraverseTemplateArgumentLoc(arg); }); } // TODO: It's not clear if/when this is used instead of // TraverseTemplateArgumentLoc bool TraverseTemplateArguments( const clang::TemplateArgument *args, unsigned num) { return usingTracker.inNameContext(nullptr, [&] { return Base::TraverseTemplateArguments(args, num); }); } bool TraverseDecl(clang::Decl *decl) { clang::DeclContext *context = nullptr; if (decl) { if (auto tunit = clang::dyn_cast<clang::TranslationUnitDecl>(decl)) { context = tunit; } else if (auto ns = clang::dyn_cast<clang::NamespaceDecl>(decl)) { // FIXME: This isn't quite right, the qualified name should be // traversed in the enclosing context. context = ns; } else if (auto tag = clang::dyn_cast<clang::TagDecl>(decl)) { // FIXME: This isn't quite right, the qualified name should be // traversed in the enclosing context. Not sure about base classes. context = tag; } else if (auto fun = clang::dyn_cast<clang::FunctionDecl>(decl)) { return usingTracker.inFunction( fun, [&] { return Base::TraverseDecl(decl); }); } } return usingTracker.inContext( context, [&] { return Base::TraverseDecl(decl); }); } bool TraverseCompoundStmt(clang::CompoundStmt *stmt) { return usingTracker.maybeBody( stmt, [&] { return Base::TraverseCompoundStmt(stmt) ; }); } bool TraverseCoroutineBodyStmt(clang::CoroutineBodyStmt *stmt) { return usingTracker.maybeBody( stmt, [&] { return Base::TraverseCoroutineBodyStmt(stmt) ; }); } bool TraverseLambdaExpr(clang::LambdaExpr *lambda) { return usingTracker.inFunction( lambda->getCallOperator(), [&] { return Base::TraverseLambdaExpr(lambda); }); } bool TraverseParmVarDecl(clang::ParmVarDecl *decl) { // Thankfully, ParmVarDecls seem to have the right context if (auto *context = decl->getDeclContext()) { if (clang::isa<clang::FunctionDecl>(context)) { return usingTracker.inContext( context, [&] { return Base::TraverseParmVarDecl(decl); }); } } return Base::TraverseParmVarDecl(decl); } // TODO: set the right context for constructor initialisers and exception // specs // NOTE: For some reason, RecursiveASTVisitor doesn't seem to call // VisitNestedNameSpecifierLoc bool TraverseNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc) { auto spec = loc.getNestedNameSpecifier(); return usingTracker.inNameContext(spec ? spec->getPrefix() : nullptr, [&] { if (spec) { if (auto ns = spec->getAsNamespace()) { xrefTarget( db.rangeOfToken(loc.getLocalSourceRange()), XRef::toDecl(namespaces, ns)); } } return Base::TraverseNestedNameSpecifierLoc(loc); }); } void xrefExpr( const clang::Decl * FOLLY_NULLABLE decl, const clang::NestedNameSpecifier * FOLLY_NULLABLE qualifier, clang::SourceRange range) { if (decl) { // TODO: We aren't cross-referencing local variables for now but should // eventually. if (!clang::isa<clang::VarDecl>(decl) || decl->getParentFunctionOrMethod() == nullptr) { auto xref = XRef::unknown(decl); if (auto fun = clang::dyn_cast<clang::FunctionDecl>(decl)) { xref = XRef::toTemplatableDecl(funDecls, fun); } else if (auto var = clang::dyn_cast<clang::VarDecl>(decl)) { xref = XRef::toTemplatableDecl(varDecls, var); } else if (auto field = clang::dyn_cast<clang::FieldDecl>(decl)) { // TODO: can this ever happen? or will it always be a MemberExpr? xref = XRef::toDecl(varDecls, field); } else if (auto e = clang::dyn_cast<clang::EnumConstantDecl>(decl)) { if (auto r = enumeratorDecls(e)) { xref = XRef::to(e, Cxx::XRefTarget::enumerator(r->fact)); } } usingTracker.inNameContext(qualifier, [&]{ xrefTarget(range, xref); }); } } } // When using macros, the SourceLocation refers to the post expansion location. // This is not useful to users who are looking at the pre-expansion location in // their code. clang::SourceLocation fixMacroLocation(clang::SourceLocation loc) { const auto& srcMgr = db.sourceManager(); if (!srcMgr.isMacroArgExpansion(loc)) { return srcMgr.getExpansionLoc(loc); } // If a macro arg is a result of a paste, its spelling is in scratch space. // // #define PASTE(x,y) x##y // #define MACRO(x) x // // MACRO(PASTE(foo,bar)) // // If this is the case, we walk up the parent context // since there can be several level of token pasting. if (srcMgr.isWrittenInScratchSpace(srcMgr.getSpellingLoc(loc))) { do { loc = srcMgr.getImmediateMacroCallerLoc(loc); } while (srcMgr.isWrittenInScratchSpace(srcMgr.getSpellingLoc(loc))); return srcMgr.getExpansionLoc(loc); } return srcMgr.getSpellingLoc(loc); } bool VisitDeclRefExpr(const clang::DeclRefExpr* expr) { auto const beginLoc = expr->getNameInfo().getSourceRange().getBegin(); auto const endLoc = expr->getNameInfo().getSourceRange().getEnd(); xrefExpr( expr->getDecl(), expr->getQualifier(), { fixMacroLocation(beginLoc), fixMacroLocation(endLoc)}); return true; } bool VisitOverloadExpr(const clang::OverloadExpr *expr) { const auto qualifier = expr->getQualifier(); const auto range = expr->getNameInfo().getSourceRange(); // For now, we xref all functions in the overload set: // // void f(int); // A // void f(bool); // B // // template<class T> void g(T x) { f(x); } // will xref both A and B // // This is especially important for using decls. for (const auto *decl : expr->decls()) { if (auto shadow = clang::dyn_cast_or_null<clang::UsingShadowDecl>(decl)) { decl = shadow->getTargetDecl(); } if (auto tpl = clang::dyn_cast_or_null<clang::TemplateDecl>(decl)) { decl = tpl->getTemplatedDecl(); } xrefExpr(decl, qualifier, range); } return true; } bool VisitMemberExpr(const clang::MemberExpr *expr) { if (const auto *decl = expr->getMemberDecl()) { XRef xref; folly::Optional<Cxx::XRefTarget> target; if (auto fun = clang::dyn_cast<clang::FunctionDecl>(decl)) { xref = XRef::toTemplatableDecl(funDecls, fun); } else if (auto field = clang::dyn_cast<clang::FieldDecl>(decl)) { xref = XRef::toDecl(varDecls, field); } else { xref = XRef::unknown(decl); } // getMemberLoc can return an invalid location if, say, an implicit // conversion operator is applied to the member. This seems to be a bug // either in Clang proper or at least in the docs. auto begin = expr->getMemberLoc(); if (!begin.isValid()) { begin = expr->getBeginLoc(); } xrefTarget(clang::SourceRange(begin, expr->getEndLoc()), xref); } return true; } bool VisitCXXDependentScopeMemberExpr( const clang::CXXDependentScopeMemberExpr *expr) { if (const auto *decl = expr->getFirstQualifierFoundInScope()) { xrefTarget( clang::SourceRange( expr->getMemberLoc(), expr-> #if LLVM_VERSION_MAJOR >= 8 getEndLoc() #else getLocEnd() #endif ), XRef::unknown(decl)); } return true; } bool VisitObjCMessageExpr(const clang::ObjCMessageExpr *expr) { if (!expr->isImplicit()) { if (const auto *decl = expr->getMethodDecl()) { folly::Optional<Cxx::XRefTarget> target; if (auto r = objcMethodDecls(decl)) { target = Cxx::XRefTarget::declaration( Cxx::Declaration::objcMethod(r->decl)); } const auto sel = expr->getSelector(); const auto nsels = expr->getNumSelectorLocs(); for (unsigned int i = 0; i < nsels; ++i) { const auto start = expr->getSelectorLoc(i); if (start.isValid()) { // NOTE: Arguments don't necessarily have names, e.g. // foo:(float)x :(float)y :(float)z // which would then be called as // [o foo:1 :2 :3] // // There will be at least one word (the method name) so we'll // definitely get at least one xref. if (auto ident = sel.getIdentifierInfoForSlot(i)) { const auto end = start.getLocWithOffset( ident->getLength()-1); xrefTarget( clang::SourceRange(start,end), XRef::to(decl, target)); } } } } } return true; } bool VisitObjCPropertyRefExpr(const clang::ObjCPropertyRefExpr *expr) { std::vector<XRef> xrefs; if (expr->isImplicitProperty()) { // Note things like x.foo += 5 generate xrefs to both getter and setter. if (expr->isMessagingGetter()) { if (auto getter = expr->getImplicitPropertyGetter()) { xrefs.push_back(XRef::toDecl(objcMethodDecls, getter)); } if (expr->isClassReceiver()) { xrefTarget( expr->getReceiverLocation(), XRef::toDecl(objcContainerDecls, expr->getClassReceiver()) ); } } if (expr->isMessagingSetter()) { if (auto setter = expr->getImplicitPropertySetter()) { xrefs.push_back(XRef::toDecl(objcMethodDecls, setter)); } } } else if (auto prop = expr->getExplicitProperty()) { xrefs.push_back(XRef::toDecl(objcPropertyDecls, prop)); } if (!xrefs.empty()) { const auto start = expr->getLocation(); const auto range = db.rangeOfToken(start); for (const auto &xref : xrefs) { xrefTarget(range, xref); } } return true; } bool VisitObjCIvarRefExpr(const clang::ObjCIvarRefExpr *expr) { const clang::ObjCIvarDecl *decl = expr->getDecl(); xrefTarget(db.rangeOfToken(expr->getLocation()), XRef::toDecl(varDecls, decl)); return true; } bool VisitObjCSelectorExpr(const clang::ObjCSelectorExpr *expr) { db.xref( clang::SourceRange(expr->getBeginLoc(), expr->getEndLoc()), folly::none, Cxx::XRefTarget::objcSelector(objcSelector(expr->getSelector())) ); return true; } bool VisitObjCProtocolExpr(const clang::ObjCProtocolExpr *expr) { xrefObjCProtocolDecl(expr->getBeginLoc(), expr->getProtocol()); return true; } bool shouldVisitTemplateInstantiations() const { return false; } void finish() { db.finish(); } ASTVisitor(ClangDB* d, clang::ASTContext& ctx) : db(*d) , astContext(ctx) , usingTracker(ctx.getTranslationUnitDecl(), *d) , scopes(*this) , namespaces("namespaces", *this) , classDecls("classDecls", *this) , enumDecls("enumDecls", *this) , enumeratorDecls("enumeratorDecls", *this) , typeAliasDecls("typeAliasDecls", *this) , funDecls("funDecls", *this) , varDecls("varDecls", *this) , objcContainerDecls("objcContainerDecls", *this) , objcMethodDecls("objcMethodDecls", *this) , objcPropertyDecls("objcPropertyDecls", *this) {} ClangDB& db; clang::ASTContext &astContext; UsingTracker usingTracker; Memo<const clang::DeclContext *, Scope, &ASTVisitor::defineScope> scopes; MemoOptional< const clang::NamespaceDecl *, NamespaceDecl> namespaces; MemoOptional< const clang::CXXRecordDecl *, ClassDecl> classDecls; MemoOptional< const clang::EnumDecl *, EnumDecl> enumDecls; MemoOptional< const clang::EnumConstantDecl *, EnumeratorDecl> enumeratorDecls; MemoOptional< const clang::TypedefNameDecl *, TypeAliasDecl> typeAliasDecls; MemoOptional< const clang::FunctionDecl *, FunDecl> funDecls; MemoOptional< const clang::DeclaratorDecl *, VarDecl> varDecls; MemoOptional< const clang::ObjCContainerDecl *, ObjcContainerDecl> objcContainerDecls; MemoOptional< const clang::ObjCMethodDecl *, ObjcMethodDecl> objcMethodDecls; MemoOptional< const clang::ObjCPropertyDecl *, ObjcPropertyDecl> objcPropertyDecls; }; // ASTConsumer uses ASTVisitor to traverse the Clang AST // struct ASTConsumer : public clang::ASTConsumer { explicit ASTConsumer(ClangDB* d) : db(d) {} void HandleTranslationUnit (clang::ASTContext &ctx) override { // Clang is sometimes generating a bogus AST when there are // compilation errors (even for parts of the AST that should be // unrelated to the error) so this is a workaround. if (ctx.getDiagnostics().hasErrorOccurred() && !FLAGS_index_on_error) { db->IndexFailure(ctx); return; } ASTVisitor visitor(db, ctx); VLOG(1) << "traversing"; visitor.TraverseDecl(ctx.getTranslationUnitDecl()); visitor.finish(); } ClangDB* db; }; } namespace facebook { namespace glean { namespace clangx { std::unique_ptr<clang::ASTConsumer> newASTConsumer(ClangDB* db) { return std::make_unique<ASTConsumer>(db); } } } }