blob: e2feb8012f3d1cce5dd68442243e6dba6edfe756 [file] [log] [blame]
/*
* Copyright 2014 The Kythe Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "IndexerASTHooks.h"
#include <algorithm>
#include <tuple>
#include "GraphObserver.h"
#include "absl/strings/str_cat.h"
#include "absl/types/optional.h"
#include "clang/AST/Attr.h"
#include "clang/AST/CommentLexer.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclFriend.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclOpenMP.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtObjC.h"
#include "clang/AST/StmtOpenMP.h"
#include "clang/AST/TemplateBase.h"
#include "clang/AST/TemplateName.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Index/USRGeneration.h"
#include "clang/Lex/Lexer.h"
#include "clang/Sema/Lookup.h"
#include "gflags/gflags.h"
#include "indexed_parent_iterator.h"
#include "kythe/cxx/common/scope_guard.h"
#include "kythe/cxx/indexer/cxx/clang_utils.h"
#include "kythe/cxx/indexer/cxx/marked_source.h"
#include "kythe/cxx/indexer/cxx/node_set.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
DEFINE_bool(experimental_alias_template_instantiations, false,
"Ignore template instantation information when generating IDs.");
DEFINE_bool(experimental_threaded_claiming, false,
"Defer answering claims and submit them in bulk when possible.");
DEFINE_bool(emit_anchors_on_builtins, true,
"Emit anchors on builtin types like int and float.");
namespace kythe {
using namespace clang;
using Claimability = GraphObserver::Claimability;
using NodeId = GraphObserver::NodeId;
void* NullGraphObserver::NullClaimToken::NullClaimTokenClass = nullptr;
namespace {
/// Decides whether `Tok` can be used to quote an identifier.
bool TokenQuotesIdentifier(const clang::SourceManager& SM,
const clang::Token& Tok) {
switch (Tok.getKind()) {
case tok::TokenKind::pipe:
return true;
case tok::TokenKind::unknown: {
bool Invalid = false;
if (const char* TokChar =
SM.getCharacterData(Tok.getLocation(), &Invalid)) {
if (!Invalid && Tok.getLength() > 0) {
return *TokChar == '`';
}
}
return false;
}
default:
return false;
}
}
/// \brief Finds the first CXXConstructExpr child of the given
/// CXXFunctionalCastExpr.
const clang::CXXConstructExpr* FindConstructExpr(
const clang::CXXFunctionalCastExpr* E) {
for (const auto* Child : E->children()) {
if (isa<clang::CXXConstructExpr>(Child)) {
return dyn_cast<clang::CXXConstructExpr>(Child);
}
}
return nullptr;
}
template <typename F>
void MapOverrideRoots(const clang::CXXMethodDecl* M, const F& Fn) {
if (M->size_overridden_methods() == 0) {
Fn(M);
} else {
for (const auto& PM : M->overridden_methods()) {
MapOverrideRoots(PM, Fn);
}
}
}
template <typename F>
void MapOverrideRoots(const clang::ObjCMethodDecl* M, const F& Fn) {
if (!M->isOverriding()) {
Fn(M);
} else {
SmallVector<const ObjCMethodDecl*, 4> overrides;
M->getOverriddenMethods(overrides);
for (const auto& PM : overrides) {
MapOverrideRoots(PM, Fn);
}
}
}
clang::QualType FollowAliasChain(const clang::TypedefNameDecl* TND) {
clang::Qualifiers Qs;
clang::QualType QT;
for (;;) {
// We'll assume that the alias chain stops as soon as we hit a non-alias.
// This does not attempt to dereference aliases in template parameters
// (or even aliases underneath pointers, etc).
QT = TND->getUnderlyingType();
Qs.addQualifiers(QT.getQualifiers());
if (auto* TTD = dyn_cast<TypedefType>(QT.getTypePtr())) {
TND = TTD->getDecl();
} else if (auto* ET = dyn_cast<ElaboratedType>(QT.getTypePtr())) {
if (auto* TDT = dyn_cast<TypedefType>(ET->getNamedType().getTypePtr())) {
TND = TDT->getDecl();
Qs.addQualifiers(ET->getNamedType().getQualifiers());
} else {
return QualType(QT.getTypePtr(), Qs.getFastQualifiers());
}
} else {
// We lose fidelity with getFastQualifiers.
return QualType(QT.getTypePtr(), Qs.getFastQualifiers());
}
}
}
/// \brief Attempt to find the template parameters bound immediately by `DC`.
/// \return null if no parameters could be found.
clang::TemplateParameterList* GetTypeParameters(const clang::DeclContext* DC) {
if (!DC) {
return nullptr;
}
if (const auto* TemplateContext = dyn_cast<clang::TemplateDecl>(DC)) {
return TemplateContext->getTemplateParameters();
} else if (const auto* CTPSD =
dyn_cast<ClassTemplatePartialSpecializationDecl>(DC)) {
return CTPSD->getTemplateParameters();
} else if (const auto* RD = dyn_cast<CXXRecordDecl>(DC)) {
if (const auto* TD = RD->getDescribedClassTemplate()) {
return TD->getTemplateParameters();
}
} else if (const auto* VTPSD =
dyn_cast<VarTemplatePartialSpecializationDecl>(DC)) {
return VTPSD->getTemplateParameters();
} else if (const auto* VD = dyn_cast<VarDecl>(DC)) {
if (const auto* TD = VD->getDescribedVarTemplate()) {
return TD->getTemplateParameters();
}
} else if (const auto* AD = dyn_cast<TypeAliasDecl>(DC)) {
if (const auto* TD = AD->getDescribedAliasTemplate()) {
return TD->getTemplateParameters();
}
}
return nullptr;
}
/// Make sure `DC` won't cause Sema::LookupQualifiedName to fail an assertion.
bool IsContextSafeForLookup(const clang::DeclContext* DC) {
if (const auto* TD = dyn_cast<clang::TagDecl>(DC)) {
return TD->isCompleteDefinition() || TD->isBeingDefined();
}
return DC->isDependentContext() || !isa<clang::LinkageSpecDecl>(DC);
}
/// \brief Returns whether `Ctor` would override the in-class initializer for
/// `Field`.
bool ConstructorOverridesInitializer(const clang::CXXConstructorDecl* Ctor,
const clang::FieldDecl* Field) {
const clang::FunctionDecl* CtorDefn = nullptr;
if (Ctor->isDefined(CtorDefn) &&
(Ctor = dyn_cast_or_null<CXXConstructorDecl>(CtorDefn))) {
for (const auto* Init : Ctor->inits()) {
if (Init->getMember() == Field && !Init->isInClassMemberInitializer()) {
return true;
}
}
}
return false;
}
/// \return true if `D` should not be visited because its name will never be
/// uttered due to aliasing rules.
bool SkipAliasedDecl(const clang::Decl* D) {
return FLAGS_experimental_alias_template_instantiations &&
(FindSpecializedTemplate(D) != D);
}
bool IsObjCForwardDecl(const clang::ObjCInterfaceDecl* decl) {
return !decl->isThisDeclarationADefinition();
}
const clang::Decl* FindImplicitDeclForStmt(
const IndexedParentMap* AllParents, const clang::Stmt* Stmt,
llvm::SmallVector<unsigned, 16>* StmtPath) {
for (const auto& Current : RootTraversal(AllParents, Stmt)) {
if (Current.decl && Current.decl->isImplicit() &&
!isa<VarDecl>(Current.decl) &&
!(isa<CXXRecordDecl>(Current.decl) &&
dyn_cast<CXXRecordDecl>(Current.decl)->isLambda())) {
// If this is an implicit variable declaration, we assume that it is one
// of the implicit declarations attached to a range for loop. We ignore
// its implicitness, which lets us associate a source location with the
// implicit references to 'begin', 'end' and operators. We also skip
// the implicit CXXRecordDecl that's created for lambda expressions,
// since it may include interesting non-implicit nodes.
return Current.decl;
}
if (Current.indexed_parent && StmtPath) {
StmtPath->push_back(Current.indexed_parent->index);
}
}
return nullptr;
}
// Calls tsi->overrideType(new_type) and restores the current type upon
// destruction.
class ScopedTypeOverride {
public:
explicit ScopedTypeOverride(clang::TypeSourceInfo* tsi,
clang::QualType new_type)
: tsi_(tsi), previous_type_(tsi_->getType()) {
tsi_->overrideType(new_type);
}
~ScopedTypeOverride() { tsi_->overrideType(previous_type_); }
// Neither copyable nor movable.
ScopedTypeOverride(const ScopedTypeOverride&) = delete;
ScopedTypeOverride& operator=(const ScopedTypeOverride) = delete;
private:
clang::TypeSourceInfo* tsi_;
clang::QualType previous_type_;
};
template <typename T>
std::string DumpString(const T& val) {
std::string s;
llvm::raw_string_ostream ss(s);
val.dump(ss);
return s;
}
} // anonymous namespace
bool IsClaimableForTraverse(const clang::Decl* decl) {
// Operationally, we'll define this as any decl that causes
// Job->UnderneathImplicitTemplateInstantiation to be set.
if (auto* VTSD = dyn_cast<const clang::VarTemplateSpecializationDecl>(decl)) {
return !VTSD->isExplicitInstantiationOrSpecialization();
}
if (auto* CTSD =
dyn_cast<const clang::ClassTemplateSpecializationDecl>(decl)) {
return !CTSD->isExplicitInstantiationOrSpecialization();
}
if (auto* FD = dyn_cast<const clang::FunctionDecl>(decl)) {
if (const auto* MSI = FD->getMemberSpecializationInfo()) {
// The definitions of class template member functions are not necessarily
// dominated by the class template definition.
if (!MSI->isExplicitSpecialization()) {
return true;
}
} else if (const auto* FSI = FD->getTemplateSpecializationInfo()) {
if (!FSI->isExplicitInstantiationOrSpecialization()) {
return true;
}
}
}
return false;
}
enum class Prunability {
kNone,
kImmediate,
kDeferIncompleteFunctions,
kDeferred
};
/// \brief RAII class used to pair claimImplicitNode/finishImplicitNode
/// when pruning AST traversal.
class PruneCheck {
public:
PruneCheck(IndexerASTVisitor* visitor, const clang::Decl* decl)
: visitor_(visitor) {
if (!visitor_->Job->UnderneathImplicitTemplateInstantiation &&
visitor_->declDominatesPrunableSubtree(decl)) {
// This node in the AST dominates a subtree that can be pruned.
if (!visitor_->Observer.claimLocation(decl->getLocation())) {
can_prune_ = Prunability::kImmediate;
}
return;
}
if (llvm::isa<clang::FunctionDecl>(decl)) {
// We make the assumption that we can stop traversal at function
// declarations, which means that we always expect the subtree rooted
// at a particular FunctionDecl under a particular type context will
// look the same. If this isn't a function declaration, the tree we
// see depends on external use sites. This assumption is reasonable
// for code that doesn't rely on making different sets of decls
// visible for ADL in different places (for example). (The core
// difference between a function body and, say, a class body is that
// function bodies are "closed*" and can't span files**, nor can they
// leak local template declarations in a way that would allow
// clients outside the function body to instantiate those declarations
// at different types.)
//
// * modulo ADL, dependent lookup, different overloads/specializations
// in context; code that makes these a problem is arguably not
// reasonable code
// ** modulo wacky macros
//
GenerateCleanupId(decl);
if (FLAGS_experimental_threaded_claiming ||
!visitor_->Observer.claimImplicitNode(cleanup_id_)) {
can_prune_ = Prunability::kDeferred;
}
} else if (llvm::isa<clang::ClassTemplateSpecializationDecl>(decl)) {
GenerateCleanupId(decl);
if (FLAGS_experimental_threaded_claiming ||
!visitor_->Observer.claimImplicitNode(cleanup_id_)) {
can_prune_ = Prunability::kDeferIncompleteFunctions;
}
}
}
~PruneCheck() {
if (!FLAGS_experimental_threaded_claiming && !cleanup_id_.empty()) {
visitor_->Observer.finishImplicitNode(cleanup_id_);
}
}
/// \return true if the decl supplied during construction doesn't need
/// traversed.
Prunability can_prune() const { return can_prune_; }
const std::string& cleanup_id() { return cleanup_id_; }
private:
void GenerateCleanupId(const clang::Decl* decl) {
// TODO(zarko): Check to see if non-function members of a class
// can be traversed once per argument set.
cleanup_id_ =
absl::StrCat(visitor_->BuildNodeIdForDecl(decl).getRawIdentity(), "#",
visitor_->getGraphObserver().getBuildConfig());
// It's critical that we distinguish between different argument lists
// here even if aliasing is turned on; otherwise we will drop data.
// If aliasing is off, the NodeId already contains this information.
if (FLAGS_experimental_alias_template_instantiations) {
llvm::raw_string_ostream ostream(cleanup_id_);
for (const auto& Current :
RootTraversal(visitor_->getAllParents(), decl)) {
if (!(Current.decl && Current.indexed_parent)) continue;
if (const auto* czdecl =
dyn_cast<ClassTemplateSpecializationDecl>(Current.decl)) {
ostream << "#"
<< HashToString(visitor_->Hash(
&czdecl->getTemplateInstantiationArgs()));
} else if (const auto* fdecl = dyn_cast<FunctionDecl>(Current.decl)) {
if (const auto* template_args =
fdecl->getTemplateSpecializationArgs()) {
ostream << "#" << HashToString(visitor_->Hash(template_args));
}
} else if (const auto* vdecl =
dyn_cast<VarTemplateSpecializationDecl>(Current.decl)) {
ostream << "#"
<< HashToString(visitor_->Hash(
&vdecl->getTemplateInstantiationArgs()));
}
}
}
cleanup_id_ = CompressString(cleanup_id_);
}
Prunability can_prune_ = Prunability::kNone;
std::string cleanup_id_;
IndexerASTVisitor* visitor_;
};
const IndexedParentMap* IndexerASTVisitor::getAllParents() {
if (!AllParents) {
// We always need to run over the whole translation unit, as
// hasAncestor can escape any subtree.
// TODO(zarko): Is this relavant for naming?
ProfileBlock block(Observer.getProfilingCallback(), "build_parent_map");
AllParents = absl::make_unique<IndexedParentMap>(
IndexedParentMap::Build(Context.getTranslationUnitDecl()));
}
return AllParents.get();
}
const IndexedParent* IndexerASTVisitor::getIndexedParent(
const ast_type_traits::DynTypedNode& Node) {
return getAllParents()->GetIndexedParent(Node);
}
bool IndexerASTVisitor::declDominatesPrunableSubtree(const clang::Decl* Decl) {
return getAllParents()->DeclDominatesPrunableSubtree(Decl);
}
bool IndexerASTVisitor::IsDefinition(const clang::VarDecl* VD) {
if (const auto* PVD = dyn_cast<ParmVarDecl>(VD)) {
// For parameters, we want to report them as definitions iff they're
// part of a function definition. Current (2013-02-14) Clang appears
// to report all function parameter declarations as definitions.
if (const auto* FD = dyn_cast<FunctionDecl>(PVD->getDeclContext())) {
return IsDefinition(FD);
}
}
// This one's a little quirky. It would actually work to just return
// implicit_cast<bool>(VD->isThisDeclarationADefinition()), because
// VarDecl::DeclarationOnly is zero, but this is more explicit.
return VD->isThisDeclarationADefinition() != VarDecl::DeclarationOnly;
}
SourceRange IndexerASTVisitor::ConsumeToken(
SourceLocation StartLocation, clang::tok::TokenKind ExpectedKind) const {
clang::Token Token;
if (getRawToken(StartLocation, Token) == LexerResult::Success) {
// We can't use findLocationAfterToken() as that also uses "raw" lexing,
// which does not respect |lang_opts_.CXXOperatorNames| and will happily
// give |tok::raw_identifier| for tokens such as "compl" and then decide
// that "compl" doesn't match tok::tilde. We want raw lexing so that
// macro expansion is suppressed, but then we need to manually re-map
// C++'s "alternative tokens" to the correct token kinds.
clang::tok::TokenKind ActualKind = Token.getKind();
if (Token.is(clang::tok::raw_identifier)) {
llvm::StringRef TokenSpelling(Token.getRawIdentifier());
const clang::IdentifierInfo& II =
Observer.getPreprocessor()->getIdentifierTable().get(TokenSpelling);
ActualKind = II.getTokenID();
}
if (ActualKind == ExpectedKind) {
const SourceLocation Begin = Token.getLocation();
return SourceRange(
Begin, GetLocForEndOfToken(*Observer.getSourceManager(),
*Observer.getLangOptions(), Begin));
}
}
return SourceRange(); // invalid location signals error/mismatch.
}
clang::SourceRange IndexerASTVisitor::RangeForNameOfDeclaration(
const clang::NamedDecl* Decl) const {
const SourceLocation StartLocation = Decl->getLocation();
if (StartLocation.isInvalid()) {
return SourceRange();
}
if (StartLocation.isFileID()) {
if (isa<clang::CXXDestructorDecl>(Decl)) {
// If the first token is "~" (or its alternate spelling, "compl") and
// the second is the name of class (rather than the name of a macro),
// then span two tokens. Otherwise span just one.
const SourceLocation NextLocation =
ConsumeToken(StartLocation, clang::tok::tilde).getEnd();
if (NextLocation.isValid()) {
// There was a tilde (or its alternate token, "compl", which is
// technically valid for a destructor even if it's awful style).
// The "name" of the destructor is "~Type" even if the source
// code says "compl Type".
clang::Token SecondToken;
if (getRawToken(NextLocation, SecondToken) == LexerResult::Success &&
SecondToken.is(clang::tok::raw_identifier) &&
("~" + std::string(SecondToken.getRawIdentifier())) ==
Decl->getNameAsString()) {
const SourceLocation EndLocation = GetLocForEndOfToken(
*Observer.getSourceManager(), *Observer.getLangOptions(),
SecondToken.getLocation());
return clang::SourceRange(StartLocation, EndLocation);
}
}
} else if (auto* M = dyn_cast<clang::ObjCMethodDecl>(Decl)) {
// Only take the first selector (if we have one). This simplifies what
// consumers of this data have to do but it is not correct.
if (M->getNumSelectorLocs() == 0) {
// Take the whole declaration. For decls this goes up to but does not
// include the ";". For definitions this goes up to but does not include
// the "{". This range will include other fields, such as the return
// type, parameter types, and parameter names.
auto S = M->getSelectorStartLoc();
auto E = M->getDeclaratorEndLoc();
return SourceRange(S, E);
}
// TODO(salguarnieri) Return multiple ranges, one for each portion of the
// selector.
const SourceLocation& Loc = M->getSelectorLoc(0);
if (Loc.isValid() && Loc.isFileID()) {
return RangeForSingleToken(Loc);
}
// If the selector location is not valid or is not a file, return the
// whole range of the selector and hope for the best.
LogErrorWithASTDump("Could not get source range", M);
return M->getSourceRange();
}
}
return RangeForASTEntity(StartLocation);
}
clang::SourceRange IndexerASTVisitor::RangeForASTEntity(
const clang::SourceLocation start_location) const {
return RangeForASTEntityFromSourceLocation(
*Observer.getSourceManager(), *Observer.getLangOptions(), start_location);
}
clang::SourceRange IndexerASTVisitor::RangeForSingleToken(
const clang::SourceLocation start_location) const {
return RangeForSingleTokenFromSourceLocation(
*Observer.getSourceManager(), *Observer.getLangOptions(), start_location);
}
void IndexerASTVisitor::MaybeRecordDefinitionRange(
const absl::optional<GraphObserver::Range>& R,
const GraphObserver::NodeId& Id,
const absl::optional<GraphObserver::NodeId>& DeclId) {
if (R) {
Observer.recordDefinitionBindingRange(R.value(), Id, DeclId);
}
}
void IndexerASTVisitor::MaybeRecordFullDefinitionRange(
const absl::optional<GraphObserver::Range>& R,
const GraphObserver::NodeId& Id,
const absl::optional<GraphObserver::NodeId>& DeclId) {
if (R) {
Observer.recordFullDefinitionRange(R.value(), Id, DeclId);
}
}
GraphObserver::Implicit IndexerASTVisitor::IsImplicit(
const GraphObserver::Range& Range) {
return (Job->UnderneathImplicitTemplateInstantiation ||
Range.Kind != GraphObserver::Range::RangeKind::Physical)
? GraphObserver::Implicit::Yes
: GraphObserver::Implicit::No;
}
void IndexerASTVisitor::RecordCallEdges(const GraphObserver::Range& Range,
const GraphObserver::NodeId& Callee) {
if (Job->BlameStack.empty()) {
if (auto FileId = Observer.recordFileInitializer(Range)) {
Observer.recordCallEdge(Range, FileId.value(), Callee, IsImplicit(Range));
}
} else {
for (const auto& Caller : Job->BlameStack.back()) {
Observer.recordCallEdge(Range, Caller, Callee, IsImplicit(Range));
}
}
}
/// An in-flight possible lookup result used to approximate qualified lookup.
struct PossibleLookup {
clang::LookupResult Result;
const clang::DeclContext* Context;
};
/// Greedily consumes tokens to try and find the longest qualified name that
/// results in a nonempty lookup result.
class PossibleLookups {
public:
/// \param RC The most specific context to start searching in.
/// \param TC The translation unit context.
PossibleLookups(clang::ASTContext& C, clang::Sema& S,
const clang::DeclContext* RC, const clang::DeclContext* TC)
: Context(C), Sema(S), RootContext(RC), TUContext(TC) {
StartIdentifier();
}
/// Describes what happened after we added the last token to the lookup state.
enum class LookupResult {
Done, ///< Lookup is terminated; don't add any more tokens.
Updated, ///< The last token narrowed down the lookup.
Progress ///< The last token didn't narrow anything down.
};
/// Try to use `Tok` to narrow down the lookup.
LookupResult AdvanceLookup(const clang::Token& Tok) {
if (Tok.is(clang::tok::coloncolon)) {
// This flag only matters if the possible lookup list is empty.
// Just drop ::s on the floor otherwise.
ForceTUContext = true;
return LookupResult::Progress;
} else if (Tok.is(clang::tok::raw_identifier)) {
bool Invalid = false;
const char* TokChar = Context.getSourceManager().getCharacterData(
Tok.getLocation(), &Invalid);
if (Invalid || !TokChar) {
return LookupResult::Done;
}
llvm::StringRef TokText(TokChar, Tok.getLength());
const auto& Id = Context.Idents.get(TokText);
clang::DeclarationNameInfo DeclName(
Context.DeclarationNames.getIdentifier(&Id), Tok.getLocation());
if (Lookups.empty()) {
return StartLookups(DeclName);
} else {
return AdvanceLookups(DeclName);
}
}
return LookupResult::Done;
}
/// Start a new identifier.
void StartIdentifier() {
Lookups.clear();
ForceTUContext = false;
}
/// Retrieve a vector of plausible lookup results with the most specific
/// ones first.
const std::vector<PossibleLookup>& LookupState() { return Lookups; }
private:
/// Start a new lookup from `DeclName`.
LookupResult StartLookups(const clang::DeclarationNameInfo& DeclName) {
CHECK(Lookups.empty());
const clang::DeclContext* Context =
ForceTUContext ? TUContext : RootContext;
do {
clang::LookupResult FirstLookup(
Sema, DeclName, clang::Sema::LookupNameKind::LookupAnyName);
FirstLookup.suppressDiagnostics();
if (IsContextSafeForLookup(Context) &&
Sema.LookupQualifiedName(
FirstLookup, const_cast<clang::DeclContext*>(Context), false)) {
Lookups.push_back({std::move(FirstLookup), Context});
} else {
CHECK(FirstLookup.empty());
// We could be looking at a (type) parameter here. Note that this
// may still be part of a qualified (dependent) name.
if (const auto* FunctionContext =
dyn_cast_or_null<clang::FunctionDecl>(Context)) {
for (const auto& Param : FunctionContext->parameters()) {
if (Param->getDeclName() == DeclName.getName()) {
clang::LookupResult DerivedResult(clang::LookupResult::Temporary,
FirstLookup);
DerivedResult.suppressDiagnostics();
DerivedResult.addDecl(Param);
Lookups.push_back({std::move(DerivedResult), Context});
}
}
} else if (const auto* TemplateParams = GetTypeParameters(Context)) {
for (const auto& TParam : *TemplateParams) {
if (TParam->getDeclName() == DeclName.getName()) {
clang::LookupResult DerivedResult(clang::LookupResult::Temporary,
FirstLookup);
DerivedResult.suppressDiagnostics();
DerivedResult.addDecl(TParam);
Lookups.push_back({std::move(DerivedResult), Context});
}
}
}
}
Context = Context->getParent();
} while (Context != nullptr);
return Lookups.empty() ? LookupResult::Done : LookupResult::Updated;
}
/// Continue each in-flight lookup using `DeclName`.
LookupResult AdvanceLookups(const clang::DeclarationNameInfo& DeclName) {
// Try to advance each lookup we have stored.
std::vector<PossibleLookup> ResultLookups;
for (auto& Lookup : Lookups) {
for (const clang::NamedDecl* Result : Lookup.Result) {
const auto* Context = dyn_cast<clang::DeclContext>(Result);
if (!Context) {
Context = Lookup.Context;
}
clang::LookupResult NextResult(
Sema, DeclName, clang::Sema::LookupNameKind::LookupAnyName);
NextResult.suppressDiagnostics();
if (IsContextSafeForLookup(Context) &&
Sema.LookupQualifiedName(
NextResult, const_cast<clang::DeclContext*>(Context), false)) {
ResultLookups.push_back({std::move(NextResult), Context});
}
}
}
if (!ResultLookups.empty()) {
Lookups = std::move(ResultLookups);
return LookupResult::Updated;
} else {
return LookupResult::Done;
}
}
/// All the plausible lookup results so far. This is sorted with the most
/// specific results first (i.e., the ones that came from the closest
/// DeclContext).
std::vector<PossibleLookup> Lookups;
/// The ASTContext we're under.
clang::ASTContext& Context;
/// The Sema instance we're using.
clang::Sema& Sema;
/// The nearest declaration context.
const clang::DeclContext* RootContext;
/// The translation unit's declaration context.
const clang::DeclContext* TUContext;
/// Set if the current lookup is unambiguously in TUContext.
bool ForceTUContext = false;
};
/// The state for heuristic parsing of identifiers in comment bodies.
enum class RefParserState {
WaitingForMark, ///< We're waiting for something that might denote an
///< embedded identifier.
SawCommand ///< We saw something Clang identifies as a comment command.
};
/// Adds markup to Text to define anchor locations and sorts Anchors
/// accordingly.
void InsertAnchorMarks(std::string& Text, std::vector<MiniAnchor>& Anchors) {
// Drop empty or negative-length anchors.
Anchors.erase(
std::remove_if(Anchors.begin(), Anchors.end(),
[](const MiniAnchor& A) { return A.Begin >= A.End; }),
Anchors.end());
std::sort(Anchors.begin(), Anchors.end(),
[](const MiniAnchor& A, const MiniAnchor& B) {
return std::tie(A.Begin, B.End) < std::tie(B.Begin, A.End);
});
std::string NewText;
NewText.reserve(Text.size() + Anchors.size() * 2);
auto NextAnchor = Anchors.begin();
std::multiset<size_t> EndLocations;
for (size_t C = 0; C < Text.size(); ++C) {
while (!EndLocations.empty() && *(EndLocations.begin()) == C) {
NewText.push_back(']');
EndLocations.erase(EndLocations.begin());
}
for (; NextAnchor != Anchors.end() && C == NextAnchor->Begin;
++NextAnchor) {
NewText.push_back('[');
EndLocations.insert(NextAnchor->End);
}
if (Text[C] == '[' || Text[C] == ']' || Text[C] == '\\') {
// Escape [, ], and \.
NewText.push_back('\\');
}
NewText.push_back(Text[C]);
}
NewText.resize(NewText.size() + EndLocations.size(), ']');
Text.swap(NewText);
}
void IndexerASTVisitor::HandleFileLevelComments(
clang::FileID Id, const GraphObserver::NodeId& FileNode) {
const auto& RCL = Context.getRawCommentList();
auto IdStart = Context.getSourceManager().getLocForStartOfFile(Id);
if (!IdStart.isFileID() || !IdStart.isValid()) {
return;
}
auto StartIdLoc = Context.getSourceManager().getDecomposedLoc(IdStart);
// Find the block of comments for the given file. This behavior is not well-
// defined by Clang, which commits only to the RawComments being
// "sorted in order of appearance in the translation unit".
if (RCL.getComments().empty()) {
return;
}
// Find the first RawComment whose start location is greater or equal to
// the start of the file whose FileID is Id.
auto C = std::lower_bound(
RCL.getComments().begin(), RCL.getComments().end(), StartIdLoc,
[&](clang::RawComment* const T1, const decltype(StartIdLoc)& T2) {
return Context.getSourceManager().getDecomposedLoc(T1->getBeginLoc()) <
T2;
});
// Walk through the comments in Id starting with the one at the top. If we
// ever leave Id, then we're done. (The first time around the loop, if C isn't
// already in Id, this check will immediately break;.)
if (C != RCL.getComments().end()) {
auto CommentIdLoc =
Context.getSourceManager().getDecomposedLoc((*C)->getBeginLoc());
if (CommentIdLoc.first != Id) {
return;
}
// Here's a simple heuristic: the first comment in a file is the file-level
// comment. This is bad for files with (e.g.) license blocks, but we can
// gradually refine as necessary.
if (VisitedComments.find(*C) == VisitedComments.end()) {
VisitComment(*C, Context.getTranslationUnitDecl(), FileNode);
}
return;
}
}
void IndexerASTVisitor::VisitComment(
const clang::RawComment* Comment, const clang::DeclContext* DC,
const GraphObserver::NodeId& DocumentedNode) {
VisitedComments.insert(Comment);
const auto& SM = *Observer.getSourceManager();
std::string Text = Comment->getRawText(SM).str();
std::string StrippedRawText;
std::map<clang::SourceLocation, size_t> OffsetsInStrippedRawText;
std::vector<MiniAnchor> StrippedRawTextAnchors;
clang::comments::Lexer Lexer(Context.getAllocator(), Context.getDiagnostics(),
Context.getCommentCommandTraits(),
Comment->getSourceRange().getBegin(),
Text.data(), Text.data() + Text.size());
clang::comments::Token Tok;
std::vector<clang::Token> IdentifierTokens;
PossibleLookups Lookups(Context, Sema, DC, Context.getTranslationUnitDecl());
auto RecordDocDeclUse = [&](const clang::SourceRange& Range,
const GraphObserver::NodeId& ResultId) {
if (auto RCC = ExplicitRangeInCurrentContext(Range)) {
Observer.recordDeclUseLocationInDocumentation(RCC.value(), ResultId);
}
auto RawLoc = OffsetsInStrippedRawText.lower_bound(Range.getBegin());
if (RawLoc != OffsetsInStrippedRawText.end()) {
// This is a single token (so we won't span multiple stripped ranges).
size_t StrippedBegin = RawLoc->second +
Range.getBegin().getRawEncoding() -
RawLoc->first.getRawEncoding();
size_t StrippedLength =
Range.getEnd().getRawEncoding() - Range.getBegin().getRawEncoding();
if (StrippedLength != 0) {
StrippedRawTextAnchors.emplace_back(MiniAnchor{
StrippedBegin, StrippedBegin + StrippedLength, ResultId});
}
}
};
// Attribute all tokens on [FirstToken,LastToken] to every possible lookup
// result inside PossibleLookups.
auto HandleLookupResult = [&](int FirstToken, int LastToken) {
for (const auto& Results : Lookups.LookupState()) {
for (auto Result : Results.Result) {
auto ResultId = BuildNodeIdForRefToDecl(Result);
for (int Token = FirstToken; Token <= LastToken; ++Token) {
RecordDocDeclUse(
clang::SourceRange(IdentifierTokens[Token].getLocation(),
IdentifierTokens[Token].getEndLoc()),
ResultId);
}
}
}
};
auto State = RefParserState::WaitingForMark;
// FoundEndingDelimiter will be called whenever we're sure that no more
// tokens can be added to IdentifierTokens that could be related to the same
// identifier. (IdentifierTokens might still contain multiple identifiers,
// as in the doc text "`foo`, `bar`, or `baz`".)
// TODO(zarko): Deal with template arguments (`foo<int>::bar`), balanced
// delimiters (`foo::bar(int, c<int>)`), and dtors (which are troubling
// because they have ~s out in front). The goal here is not to completely
// reimplement a C/C++ parser, but instead to build just enough to make good
// guesses.
auto FoundEndingDelimiter = [&]() {
int IdentifierStart = -1;
int CurrentIndex = 0;
if (State == RefParserState::SawCommand) {
// Start as though we've seen a quote and consume tokens until we're not
// making progress.
IdentifierStart = 0;
Lookups.StartIdentifier();
}
for (const auto& Tok : IdentifierTokens) {
if (IdentifierStart >= 0) {
if (Lookups.AdvanceLookup(Tok) == PossibleLookups::LookupResult::Done) {
// We can't match an identifier using this token. Either it's the
// matching close-quote or it's some other weirdness.
if (IdentifierStart != CurrentIndex) {
HandleLookupResult(IdentifierStart, CurrentIndex - 1);
}
IdentifierStart = -1;
}
} else if (TokenQuotesIdentifier(SM, Tok)) {
// We found a signal to start an identifier.
IdentifierStart = CurrentIndex + 1;
Lookups.StartIdentifier();
}
++CurrentIndex;
}
State = RefParserState::WaitingForMark;
IdentifierTokens.clear();
};
do {
Lexer.lex(Tok);
unsigned offset = Tok.getLocation().getRawEncoding() -
Comment->getSourceRange().getBegin().getRawEncoding();
OffsetsInStrippedRawText.insert(
std::make_pair(Tok.getLocation(), StrippedRawText.size()));
StrippedRawText.append(Text.substr(offset, Tok.getLength()));
switch (Tok.getKind()) {
case clang::comments::tok::text: {
auto TokText = Tok.getText().str();
clang::Lexer LookupLexer(Tok.getLocation(), *Observer.getLangOptions(),
TokText.data(), TokText.data(),
TokText.data() + TokText.size());
do {
IdentifierTokens.emplace_back();
LookupLexer.LexFromRawLexer(IdentifierTokens.back());
} while (!IdentifierTokens.back().is(clang::tok::eof));
IdentifierTokens.pop_back();
} break;
case clang::comments::tok::newline:
break;
case clang::comments::tok::unknown_command:
case clang::comments::tok::backslash_command:
case clang::comments::tok::at_command:
FoundEndingDelimiter();
State = RefParserState::SawCommand;
break;
case clang::comments::tok::verbatim_block_begin:
case clang::comments::tok::verbatim_block_line:
case clang::comments::tok::verbatim_block_end:
case clang::comments::tok::verbatim_line_name:
case clang::comments::tok::verbatim_line_text:
case clang::comments::tok::html_start_tag:
case clang::comments::tok::html_ident:
case clang::comments::tok::html_equals:
case clang::comments::tok::html_quoted_string:
case clang::comments::tok::html_greater:
case clang::comments::tok::html_slash_greater:
case clang::comments::tok::html_end_tag:
case clang::comments::tok::eof:
FoundEndingDelimiter();
break;
}
} while (Tok.getKind() != clang::comments::tok::eof);
InsertAnchorMarks(StrippedRawText, StrippedRawTextAnchors);
std::vector<GraphObserver::NodeId> LinkNodes;
LinkNodes.reserve(StrippedRawTextAnchors.size());
for (const auto& Anchor : StrippedRawTextAnchors) {
LinkNodes.push_back(Anchor.AnchoredTo);
}
// Attribute the raw comment text to its associated decl.
if (auto RCC = ExplicitRangeInCurrentContext(Comment->getSourceRange())) {
Observer.recordDocumentationRange(RCC.value(), DocumentedNode);
Observer.recordDocumentationText(DocumentedNode, StrippedRawText,
LinkNodes);
}
}
void IndexerASTVisitor::VisitAttributes(
const clang::Decl* Decl, const GraphObserver::NodeId& TargetNode) {
for (const auto& Attr : Decl->attrs()) {
if (const auto* DepAttr = clang::dyn_cast<clang::DeprecatedAttr>(Attr)) {
Observer.recordDeprecated(TargetNode, DepAttr->getMessage());
}
}
}
bool IndexerASTVisitor::VisitDecl(const clang::Decl* Decl) {
if ((Job->UnderneathImplicitTemplateInstantiation || Decl == nullptr) &&
!Decl->hasAttrs()) {
// Template instantiation can't add any documentation text.
return true;
}
const auto* CommentOrNull = Context.getRawCommentForDeclNoCache(Decl);
if (!CommentOrNull && !Decl->hasAttrs()) {
// Fast path: if there are no attached documentation comments or attributes,
// bail.
return true;
}
const auto* DCxt = dyn_cast<DeclContext>(Decl);
if (!DCxt) {
DCxt = Decl->getDeclContext();
if (!DCxt) {
DCxt = Context.getTranslationUnitDecl();
}
}
if (const auto* DC = dyn_cast_or_null<DeclContext>(Decl)) {
if (auto DCID = BuildNodeIdForDeclContext(DC)) {
VisitAttributes(Decl, DCID.value());
if (CommentOrNull == nullptr) {
return true;
}
if (const auto* IFaceDecl = dyn_cast_or_null<ObjCInterfaceDecl>(DC)) {
VisitObjCInterfaceDeclComment(IFaceDecl, CommentOrNull, DCxt, DCID);
} else if (const auto* R = dyn_cast_or_null<RecordDecl>(DC)) {
VisitRecordDeclComment(R, CommentOrNull, DCxt, DCID);
} else {
VisitComment(CommentOrNull, DCxt, DCID.value());
}
}
if (const auto* CTPSD =
dyn_cast_or_null<ClassTemplatePartialSpecializationDecl>(Decl)) {
auto NodeId = BuildNodeIdForDecl(CTPSD);
VisitAttributes(Decl, NodeId);
if (CommentOrNull != nullptr) VisitComment(CommentOrNull, DCxt, NodeId);
}
if (const auto* FD = dyn_cast_or_null<FunctionDecl>(Decl)) {
if (const auto* FTD = FD->getDescribedFunctionTemplate()) {
auto NodeId = BuildNodeIdForDecl(FTD);
VisitAttributes(Decl, NodeId);
if (CommentOrNull != nullptr) VisitComment(CommentOrNull, DCxt, NodeId);
}
}
} else {
if (const auto* VD = dyn_cast_or_null<VarDecl>(Decl)) {
if (const auto* VTD = VD->getDescribedVarTemplate()) {
auto NodeId = BuildNodeIdForDecl(VTD);
VisitAttributes(VTD, NodeId);
if (CommentOrNull != nullptr) VisitComment(CommentOrNull, DCxt, NodeId);
}
} else if (const auto* AD = dyn_cast_or_null<TypeAliasDecl>(Decl)) {
if (const auto* TATD = AD->getDescribedAliasTemplate()) {
auto NodeId = BuildNodeIdForDecl(TATD);
VisitAttributes(TATD, NodeId);
if (CommentOrNull != nullptr) VisitComment(CommentOrNull, DCxt, NodeId);
}
}
auto NodeId = BuildNodeIdForDecl(Decl);
VisitAttributes(Decl, NodeId);
if (CommentOrNull != nullptr) VisitComment(CommentOrNull, DCxt, NodeId);
}
return true;
}
void IndexerASTVisitor::VisitObjCInterfaceDeclComment(
const ObjCInterfaceDecl* Decl, const RawComment* Comment,
const DeclContext* DCxt, absl::optional<GraphObserver::NodeId> DCID) {
// Don't record comments for ObjC class forward declarations because
// their comments generally aren't useful documentation about the class,
// they are more likely to be about why a forward declaration was used
// or be totally unrelated to the class.
if (shouldEmitObjCForwardClassDeclDocumentation() ||
!IsObjCForwardDecl(Decl)) {
VisitComment(Comment, DCxt, DCID.value());
}
}
void IndexerASTVisitor::VisitRecordDeclComment(
const RecordDecl* Decl, const RawComment* Comment, const DeclContext* DCxt,
absl::optional<GraphObserver::NodeId> DCID) {
// Don't record comments for forward declarations because their comments
// generally aren't useful documentation about the class, they are more likely
// to be about why a forward declaration was used or be totally unrelated to
// the class.
if (shouldEmitCppForwardDeclDocumentation() ||
Decl->getDefinition() == Decl) {
VisitComment(Comment, DCxt, DCID.value());
}
}
bool IndexerASTVisitor::TraverseDecl(clang::Decl* Decl) {
if (ShouldStopIndexing()) {
return false;
}
if (Decl == nullptr) {
return true;
}
// This is a dirty, dirty hack to allow other parts of the code to more
// cleanly handle deduced types, but should *only* be done for deduced types.
// TODO(shahms): Remove this when it's fixed upstream.
// Or replace it with the properly deduced type.
absl::optional<ScopedTypeOverride> type_override;
if (auto* D = dyn_cast<clang::DeclaratorDecl>(Decl)) {
if (auto* TSI = D->getTypeSourceInfo()) {
if (TSI->getType() != D->getType() && TSI->getType()->isUndeducedType()) {
type_override.emplace(TSI, D->getType());
}
}
}
auto Scope = RestoreValue(Job->PruneIncompleteFunctions);
if (Job->PruneIncompleteFunctions) {
if (auto* FD = llvm::dyn_cast<clang::FunctionDecl>(Decl)) {
if (!FD->isThisDeclarationADefinition()) {
return true;
}
}
}
if (FLAGS_experimental_threaded_claiming) {
if (Decl != Job->Decl) {
PruneCheck Prune(this, Decl);
auto can_prune = Prune.can_prune();
if (can_prune == Prunability::kImmediate) {
return true;
} else if (can_prune != Prunability::kNone) {
Worklist->EnqueueJobForImplicitDecl(
Decl, can_prune == Prunability::kDeferIncompleteFunctions,
Prune.cleanup_id());
return true;
}
} else {
if (Job->SetPruneIncompleteFunctions) {
Job->PruneIncompleteFunctions = true;
}
}
} else {
PruneCheck Prune(this, Decl);
auto can_prune = Prune.can_prune();
if (can_prune == Prunability::kImmediate) {
return true;
} else if (can_prune == Prunability::kDeferIncompleteFunctions) {
Job->PruneIncompleteFunctions = true;
}
}
GraphObserver::Delimiter Del(Observer);
// For clang::FunctionDecl and all subclasses thereof push blame data.
if (auto* FD = dyn_cast_or_null<clang::FunctionDecl>(Decl)) {
if (unsigned BuiltinID = FD->getBuiltinID()) {
if (!Observer.getPreprocessor()->getBuiltinInfo().isPredefinedLibFunction(
BuiltinID)) {
// These builtins act weirdly (by, e.g., defining themselves inside the
// macro context where they first appeared, but then also in the global
// namespace). Don't traverse them.
return true;
}
}
auto Scope = std::make_tuple(RestoreStack(Job->BlameStack),
RestoreStack(Job->RangeContext));
if (FD->isTemplateInstantiation() &&
FD->getTemplateSpecializationKind() !=
clang::TSK_ExplicitSpecialization) {
// Explicit specializations have ranges.
if (const auto RangeId = BuildNodeIdForRefToDeclContext(FD)) {
Job->RangeContext.push_back(RangeId.value());
} else {
Job->RangeContext.push_back(BuildNodeIdForDecl(FD));
}
}
if (const auto BlameId = BuildNodeIdForDeclContext(FD)) {
Job->BlameStack.push_back(IndexJob::SomeNodes(1, BlameId.value()));
} else {
Job->BlameStack.push_back(
IndexJob::SomeNodes(1, BuildNodeIdForRefToDecl(FD)));
}
// Dispatch the remaining logic to the base class TraverseDecl() which will
// call TraverseX(X*) for the most-derived X.
return Base::TraverseDecl(FD);
} else if (auto* ID = dyn_cast_or_null<clang::FieldDecl>(Decl)) {
// This will also cover the case of clang::ObjCIVarDecl since it is a
// subclass.
if (ID->hasInClassInitializer()) {
// Blame calls from in-class initializers I on all ctors C of the
// containing class so long as C does not have its own initializer for
// I's field.
auto R = RestoreStack(Job->BlameStack);
if (auto* CR = dyn_cast_or_null<clang::CXXRecordDecl>(ID->getParent())) {
if ((CR = CR->getDefinition())) {
IndexJob::SomeNodes Ctors;
auto TryCtor = [&](const clang::CXXConstructorDecl* Ctor) {
if (!ConstructorOverridesInitializer(Ctor, ID)) {
if (const auto BlameId = BuildNodeIdForRefToDeclContext(Ctor)) {
Ctors.push_back(BlameId.value());
}
}
};
for (const auto* SubDecl : CR->decls()) {
if (const auto* Ctor =
dyn_cast_or_null<CXXConstructorDecl>(SubDecl)) {
TryCtor(Ctor);
} else if (const auto* Templ =
dyn_cast_or_null<FunctionTemplateDecl>(SubDecl)) {
if (const auto* Ctor = dyn_cast_or_null<CXXConstructorDecl>(
Templ->getTemplatedDecl())) {
TryCtor(Ctor);
}
for (const auto* Spec : Templ->specializations()) {
if (const auto* Ctor =
dyn_cast_or_null<CXXConstructorDecl>(Spec)) {
TryCtor(Ctor);
}
}
}
}
if (!Ctors.empty()) {
Job->BlameStack.push_back(Ctors);
}
}
}
return Base::TraverseDecl(ID);
}
} else if (auto* MD = dyn_cast_or_null<clang::ObjCMethodDecl>(Decl)) {
// These variables (R and S) clean up the stacks (Job->BlameStack and
// Job->RangeContext) when the local variables (R and S) are
// destructed.
auto Scope = std::make_tuple(RestoreStack(Job->BlameStack),
RestoreStack(Job->RangeContext));
if (const auto BlameId = BuildNodeIdForDeclContext(MD)) {
Job->BlameStack.push_back(IndexJob::SomeNodes(1, BlameId.value()));
} else {
Job->BlameStack.push_back(IndexJob::SomeNodes(1, BuildNodeIdForDecl(MD)));
}
// Dispatch the remaining logic to the base class TraverseDecl() which will
// call TraverseX(X*) for the most-derived X.
return Base::TraverseDecl(MD);
}
return Base::TraverseDecl(Decl);
}
bool IndexerASTVisitor::VisitCXXDependentScopeMemberExpr(
const clang::CXXDependentScopeMemberExpr* E) {
absl::optional<GraphObserver::NodeId> Root = absl::nullopt;
auto BaseType = E->getBaseType();
if (BaseType.getTypePtrOrNull()) {
auto* Builtin = dyn_cast<BuiltinType>(BaseType.getTypePtr());
// The "Dependent" builtin type is not useful, so we'll keep this lookup
// rootless. We could alternately invent a singleton type for the range of
// the lhs expression.
if (!Builtin || Builtin->getKind() != BuiltinType::Dependent) {
Root = BuildNodeIdForType(BaseType);
}
}
if (auto DepNodeId = RecordEdgesForDependentName(
E->getQualifierLoc(), E->getMember(), E->getMemberLoc(), Root)) {
if (auto RCC = ExplicitRangeInCurrentContext(
RangeForASTEntity(E->getMemberLoc()))) {
Observer.recordDeclUseLocation(*RCC, *DepNodeId,
GraphObserver::Claimability::Claimable,
IsImplicit(*RCC));
}
if (E->hasExplicitTemplateArgs()) {
if (auto ArgIds = BuildTemplateArgumentList(E->template_arguments())) {
auto TappNodeId = Observer.recordTappNode(*DepNodeId, *ArgIds);
auto StmtId = BuildNodeIdForImplicitStmt(E);
auto Range = clang::SourceRange(E->getMemberLoc(),
E->getEndLoc().getLocWithOffset(1));
if (auto RCC = RangeInCurrentContext(StmtId, Range)) {
Observer.recordDeclUseLocation(
RCC.value(), TappNodeId, GraphObserver::Claimability::Unclaimable,
IsImplicit(RCC.value()));
}
}
}
}
return true;
}
bool IndexerASTVisitor::VisitMemberExpr(const clang::MemberExpr* E) {
if (E->getMemberLoc().isInvalid()) {
return true;
}
for (const auto* Child : E->children()) {
if (const auto* DeclRef = dyn_cast<clang::DeclRefExpr>(Child)) {
if (isa<clang::DecompositionDecl>(DeclRef->getDecl())) {
// Ignore field references synthesized from structured bindings, as
// they are just a clang implementation detail. Skip the references.
return true;
}
}
}
if (const auto* FieldDecl = E->getMemberDecl()) {
auto Range = RangeForASTEntity(E->getMemberLoc());
auto StmtId = BuildNodeIdForImplicitStmt(E);
if (auto RCC = RangeInCurrentContext(StmtId, Range)) {
Observer.recordDeclUseLocation(
RCC.value(), BuildNodeIdForRefToDecl(FieldDecl),
GraphObserver::Claimability::Unclaimable, IsImplicit(RCC.value()));
if (E->hasExplicitTemplateArgs()) {
// We still want to link the template args.
BuildTemplateArgumentList(E->template_arguments());
}
}
}
return true;
}
bool IndexerASTVisitor::IndexConstructExpr(const clang::CXXConstructExpr* E,
const clang::TypeSourceInfo* TSI) {
if (const auto* Callee = E->getConstructor()) {
// Clang doesn't invoke VisitDeclRefExpr on constructors, so we
// must do so manually.
// TODO(zarko): What about static initializers? Do we blame these on the
// translation unit?
clang::SourceLocation RPL = E->getParenOrBraceRange().getEnd();
clang::SourceLocation RefLoc = E->getBeginLoc();
if (TSI != nullptr) {
if (const auto ETL =
TSI->getTypeLoc().getAs<clang::ElaboratedTypeLoc>()) {
RefLoc = ETL.getNamedTypeLoc().getBeginLoc();
}
}
// We assume that a constructor call was inserted by the compiler (or
// is otherwise implicit) if it's being provided with arguments but it has
// an invalid right-paren location, since such a call would be impossible
// to write down.
bool IsImplicit = !RPL.isValid() && E->getNumArgs() > 0;
VisitDeclRefOrIvarRefExpr(E, Callee, RefLoc, IsImplicit);
clang::SourceRange SR = E->getSourceRange();
if (RPL.isValid()) {
// This loses the right paren without the offset.
SR.setEnd(RPL.getLocWithOffset(1));
} else if (TSI != nullptr) {
SR.setEnd(TSI->getTypeLoc().getEndLoc().getLocWithOffset(1));
} else {
SR.setEnd(SR.getEnd().getLocWithOffset(1));
}
auto StmtId = BuildNodeIdForImplicitStmt(E);
if (auto RCC = RangeInCurrentContext(StmtId, SR)) {
RecordCallEdges(RCC.value(), BuildNodeIdForRefToDecl(Callee));
}
}
return true;
}
bool IndexerASTVisitor::TraverseConstructorInitializer(
clang::CXXCtorInitializer* Init) {
if (Init->isMemberInitializer()) {
return Base::TraverseConstructorInitializer(Init);
}
if (const auto* CE = dyn_cast<clang::CXXConstructExpr>(Init->getInit())) {
if (IndexConstructExpr(CE, Init->getTypeSourceInfo())) {
auto Scope = PushScope(Job->ConstructorStack, CE);
return Base::TraverseConstructorInitializer(Init);
}
return false;
}
return Base::TraverseConstructorInitializer(Init);
}
bool IndexerASTVisitor::VisitCXXConstructExpr(
const clang::CXXConstructExpr* E) {
// Skip Visiting CXXConstructExprs directly which were already
// visited by a parent.
auto iter = std::find(Job->ConstructorStack.crbegin(),
Job->ConstructorStack.crend(), E);
if (iter != Job->ConstructorStack.crend()) {
return true;
}
clang::TypeSourceInfo* TSI = nullptr;
if (const auto* TE = dyn_cast<clang::CXXTemporaryObjectExpr>(E)) {
TSI = TE->getTypeSourceInfo();
}
return IndexConstructExpr(E, TSI);
}
bool IndexerASTVisitor::VisitCXXDeleteExpr(const clang::CXXDeleteExpr* E) {
auto DTy = E->getDestroyedType();
if (DTy.isNull()) {
return true;
}
auto DTyNonRef = DTy.getNonReferenceType();
if (DTyNonRef.isNull()) {
return true;
}
auto BaseType = Context.getBaseElementType(DTyNonRef);
if (BaseType.isNull()) {
return true;
}
absl::optional<GraphObserver::NodeId> DDId;
if (const auto* CD = BaseType->getAsCXXRecordDecl()) {
if (const auto* DD = CD->getDestructor()) {
DDId = BuildNodeIdForRefToDecl(DD);
}
} else {
auto QTCan = BaseType.getCanonicalType();
if (QTCan.isNull()) {
return true;
}
auto TyId = BuildNodeIdForType(QTCan);
if (!TyId) {
return true;
}
auto DtorName = Context.DeclarationNames.getCXXDestructorName(
CanQualType::CreateUnsafe(QTCan));
DDId = RecordEdgesForDependentName(clang::NestedNameSpecifierLoc(),
DtorName, E->getBeginLoc(), TyId);
}
if (DDId) {
clang::SourceRange SR = E->getSourceRange();
SR.setEnd(SR.getEnd().getLocWithOffset(1));
auto StmtId = BuildNodeIdForImplicitStmt(E);
if (auto RCC = RangeInCurrentContext(StmtId, SR)) {
RecordCallEdges(RCC.value(), DDId.value());
}
}
return true;
}
bool IndexerASTVisitor::TraverseCXXFunctionalCastExpr(
clang::CXXFunctionalCastExpr* E) {
if (const auto* CE = FindConstructExpr(E)) {
if (IndexConstructExpr(CE, E->getTypeInfoAsWritten())) {
auto Scope = PushScope(Job->ConstructorStack, CE);
return Base::TraverseCXXFunctionalCastExpr(E);
}
return false;
}
return Base::TraverseCXXFunctionalCastExpr(E);
}
bool IndexerASTVisitor::TraverseCXXNewExpr(clang::CXXNewExpr* E) {
if (const auto* CE = E->getConstructExpr()) {
if (IndexConstructExpr(CE, E->getAllocatedTypeSourceInfo())) {
auto Scope = PushScope(Job->ConstructorStack, CE);
return Base::TraverseCXXNewExpr(E);
}
return false;
}
return Base::TraverseCXXNewExpr(E);
}
bool IndexerASTVisitor::VisitCXXNewExpr(const clang::CXXNewExpr* E) {
auto StmtId = BuildNodeIdForImplicitStmt(E);
if (FunctionDecl* New = E->getOperatorNew()) {
auto NewId = BuildNodeIdForRefToDecl(New);
clang::SourceLocation NewLoc = E->getBeginLoc();
if (NewLoc.isFileID()) {
clang::SourceRange NewRange(
NewLoc, GetLocForEndOfToken(*Observer.getSourceManager(),
*Observer.getLangOptions(), NewLoc));
if (auto RCC = RangeInCurrentContext(StmtId, NewRange)) {
Observer.recordDeclUseLocation(RCC.value(), NewId,
GraphObserver::Claimability::Unclaimable,
IsImplicit(RCC.value()));
}
}
}
return true;
}
bool IndexerASTVisitor::VisitCXXPseudoDestructorExpr(
const clang::CXXPseudoDestructorExpr* E) {
if (E->getDestroyedType().isNull()) {
return true;
}
auto DTCan = E->getDestroyedType().getCanonicalType();
if (DTCan.isNull() || !DTCan.isCanonical()) {
return true;
}
absl::optional<GraphObserver::NodeId> TyId;
clang::NestedNameSpecifierLoc NNSLoc;
if (E->getDestroyedTypeInfo() != nullptr) {
TyId = BuildNodeIdForType(E->getDestroyedTypeInfo()->getTypeLoc());
} else if (E->hasQualifier()) {
NNSLoc = E->getQualifierLoc();
}
auto DtorName = Context.DeclarationNames.getCXXDestructorName(
CanQualType::CreateUnsafe(DTCan));
if (auto DDId = RecordEdgesForDependentName(NNSLoc, DtorName,
E->getTildeLoc(), TyId)) {
if (auto RCC = ExplicitRangeInCurrentContext(
RangeForASTEntity(E->getTildeLoc()))) {
Observer.recordDeclUseLocation(*RCC, *DDId,
GraphObserver::Claimability::Claimable,
IsImplicit(*RCC));
}
clang::SourceRange SR = E->getSourceRange();
SR.setEnd(RangeForASTEntity(SR.getEnd()).getEnd());
auto StmtId = BuildNodeIdForImplicitStmt(E);
if (auto RCC = RangeInCurrentContext(StmtId, SR)) {
RecordCallEdges(RCC.value(), DDId.value());
}
}
return true;
}
bool IndexerASTVisitor::VisitCXXUnresolvedConstructExpr(
const clang::CXXUnresolvedConstructExpr* E) {
auto QTCan = E->getTypeAsWritten().getCanonicalType();
if (QTCan.isNull()) {
return true;
}
CHECK(E->getTypeSourceInfo() != nullptr);
auto TyId = BuildNodeIdForType(E->getTypeSourceInfo()->getTypeLoc());
if (!TyId) {
return true;
}
auto CtorName = Context.DeclarationNames.getCXXConstructorName(
CanQualType::CreateUnsafe(QTCan));
if (auto LookupId = RecordEdgesForDependentName(
clang::NestedNameSpecifierLoc(), CtorName, E->getBeginLoc(), TyId)) {
clang::SourceLocation RPL = E->getRParenLoc();
clang::SourceRange SR = E->getSourceRange();
// This loses the right paren without the offset.
if (RPL.isValid()) {
SR.setEnd(RPL.getLocWithOffset(1));
}
auto StmtId = BuildNodeIdForImplicitStmt(E);
if (auto RCC = RangeInCurrentContext(StmtId, SR)) {
RecordCallEdges(RCC.value(), LookupId.value());
}
}
return true;
}
bool IndexerASTVisitor::VisitCallExpr(const clang::CallExpr* E) {
clang::SourceLocation RPL = E->getRParenLoc();
clang::SourceRange SR = E->getSourceRange();
if (RPL.isValid()) {
// This loses the right paren without the offset.
SR.setEnd(RPL.getLocWithOffset(1));
}
auto StmtId = BuildNodeIdForImplicitStmt(E);
if (auto RCC = RangeInCurrentContext(StmtId, SR)) {
if (const auto* Callee = E->getCalleeDecl()) {
auto CalleeId = BuildNodeIdForRefToDecl(Callee);
RecordCallEdges(RCC.value(), CalleeId);
for (const auto& S : Supports) {
S->InspectCallExpr(*this, E, RCC.value(), CalleeId);
}
} else if (const auto* CE = E->getCallee()) {
if (auto CalleeId = BuildNodeIdForExpr(CE, EmitRanges::Yes)) {
RecordCallEdges(RCC.value(), CalleeId.value());
}
}
}
return true;
}
absl::optional<GraphObserver::NodeId>
IndexerASTVisitor::BuildNodeIdForImplicitTemplateInstantiation(
const clang::Decl* Decl) {
if (const auto* FD = dyn_cast<const clang::FunctionDecl>(Decl)) {
return BuildNodeIdForImplicitFunctionTemplateInstantiation(FD);
} else if (const auto* CD =
dyn_cast<const clang::ClassTemplateSpecializationDecl>(Decl)) {
return BuildNodeIdForImplicitClassTemplateInstantiation(CD);
}
// TODO(zarko): other template kinds as needed.
return absl::nullopt;
}
absl::optional<GraphObserver::NodeId>
IndexerASTVisitor::BuildNodeIdForImplicitClassTemplateInstantiation(
const clang::ClassTemplateSpecializationDecl* CTSD) {
if (CTSD->isExplicitInstantiationOrSpecialization()) {
return absl::nullopt;
}
const clang::ASTTemplateArgumentListInfo* ArgsAsWritten = nullptr;
if (const auto* CTPSD =
dyn_cast<const clang::ClassTemplatePartialSpecializationDecl>(CTSD)) {
ArgsAsWritten = CTPSD->getTemplateArgsAsWritten();
}
auto PrimaryOrPartial = CTSD->getSpecializedTemplateOrPartial();
if (auto NIDS =
(ArgsAsWritten ? BuildTemplateArgumentList(ArgsAsWritten->arguments())
: BuildTemplateArgumentList(
CTSD->getTemplateArgs().asArray()))) {
if (auto SpecializedNode = BuildNodeIdForTemplateName(
clang::TemplateName(CTSD->getSpecializedTemplate()))) {
if (PrimaryOrPartial.is<clang::ClassTemplateDecl*>() &&
!isa<const clang::ClassTemplatePartialSpecializationDecl>(CTSD)) {
// Point to a tapp of the primary template.
return Observer.recordTappNode(*SpecializedNode, *NIDS);
}
}
}
if (const auto* Partial =
PrimaryOrPartial
.dyn_cast<clang::ClassTemplatePartialSpecializationDecl*>()) {
if (auto NIDS = BuildTemplateArgumentList(
CTSD->getTemplateInstantiationArgs().asArray())) {
// Point to a tapp of the partial template specialization.
return Observer.recordTappNode(BuildNodeIdForDecl(Partial), *NIDS);
}
}
return absl::nullopt;
}
absl::optional<GraphObserver::NodeId>
IndexerASTVisitor::BuildNodeIdForImplicitFunctionTemplateInstantiation(
const clang::FunctionDecl* FD) {
std::vector<GraphObserver::NodeId> NIDS;
const clang::TemplateArgumentLoc* ArgsAsWritten = nullptr;
unsigned NumArgsAsWritten = 0;
const clang::TemplateArgumentList* Args = nullptr;
if (FD->getDescribedFunctionTemplate() != nullptr) {
// This is the body of a function template.
return absl::nullopt;
}
if (auto* MSI = FD->getMemberSpecializationInfo()) {
if (MSI->isExplicitSpecialization()) {
// Refer to explicit specializations directly.
return absl::nullopt;
}
// This is a member under a template instantiation. See #1879.
return Observer.recordTappNode(
BuildNodeIdForDecl(MSI->getInstantiatedFrom()), {}, 0);
}
std::vector<std::pair<clang::TemplateName, clang::SourceLocation>> TNs;
if (auto* FTSI = FD->getTemplateSpecializationInfo()) {
if (FTSI->isExplicitSpecialization()) {
// Refer to explicit specializations directly.
return absl::nullopt;
}
if (FTSI->TemplateArgumentsAsWritten) {
// We have source locations for the template arguments.
ArgsAsWritten = FTSI->TemplateArgumentsAsWritten->getTemplateArgs();
NumArgsAsWritten = FTSI->TemplateArgumentsAsWritten->NumTemplateArgs;
}
Args = FTSI->TemplateArguments;
TNs.emplace_back(TemplateName(FTSI->getTemplate()),
FTSI->getPointOfInstantiation());
} else if (auto* DFTSI = FD->getDependentSpecializationInfo()) {
ArgsAsWritten = DFTSI->getTemplateArgs();
NumArgsAsWritten = DFTSI->getNumTemplateArgs();
for (unsigned T = 0; T < DFTSI->getNumTemplates(); ++T) {
TNs.emplace_back(clang::TemplateName(DFTSI->getTemplate(T)),
FD->getLocation());
}
}
// We can't do anything useful if we don't have type arguments.
if (ArgsAsWritten || Args) {
bool CouldGetAllTypes = true;
if (ArgsAsWritten) {
// Prefer arguments as they were written in source files.
NIDS.reserve(NumArgsAsWritten);
for (unsigned I = 0; I < NumArgsAsWritten; ++I) {
if (auto ArgId = BuildNodeIdForTemplateArgument(ArgsAsWritten[I],
EmitRanges::Yes)) {
NIDS.push_back(ArgId.value());
} else {
CouldGetAllTypes = false;
break;
}
}
} else {
NIDS.reserve(Args->size());
for (unsigned I = 0; I < Args->size(); ++I) {
if (auto ArgId = BuildNodeIdForTemplateArgument(
Args->get(I), clang::SourceLocation())) {
NIDS.push_back(ArgId.value());
} else {
CouldGetAllTypes = false;
break;
}
}
}
if (CouldGetAllTypes) {
// If there's more than one possible template name (e.g., this is
// dependent), choose one arbitrarily.
for (const auto& TN : TNs) {
if (auto SpecializedNode = BuildNodeIdForTemplateName(TN.first)) {
return Observer.recordTappNode(SpecializedNode.value(), NIDS,
NumArgsAsWritten);
}
}
}
}
return absl::nullopt;
}
GraphObserver::NodeId IndexerASTVisitor::BuildNodeIdForRefToDecl(
const clang::Decl* Decl) {
if (auto TappId = BuildNodeIdForImplicitTemplateInstantiation(Decl)) {
return TappId.value();
}
return BuildNodeIdForDecl(Decl);
}
absl::optional<GraphObserver::NodeId>
IndexerASTVisitor::BuildNodeIdForRefToDeclContext(
const clang::DeclContext* DC) {
if (auto* DCDecl = llvm::dyn_cast<const clang::Decl>(DC)) {
if (auto TappId = BuildNodeIdForImplicitTemplateInstantiation(DCDecl)) {
return TappId;
}
return BuildNodeIdForDeclContext(DC);
}
return absl::nullopt;
}
absl::optional<GraphObserver::NodeId>
IndexerASTVisitor::BuildNodeIdForDeclContext(const clang::DeclContext* DC) {
if (auto* DCDecl = llvm::dyn_cast<const clang::Decl>(DC)) {
if (llvm::isa<TranslationUnitDecl>(DCDecl)) {
return absl::nullopt;
}
if (llvm::isa<ClassTemplatePartialSpecializationDecl>(DCDecl)) {
return BuildNodeIdForDecl(DCDecl, 0);
} else if (auto* CRD = dyn_cast<const clang::CXXRecordDecl>(DCDecl)) {
if (const auto* CTD = CRD->getDescribedClassTemplate()) {
return BuildNodeIdForDecl(DCDecl, 0);
}
} else if (auto* FD = dyn_cast<const clang::FunctionDecl>(DCDecl)) {
if (FD->getDescribedFunctionTemplate()) {
return BuildNodeIdForDecl(DCDecl, 0);
}
}
return BuildNodeIdForDecl(DCDecl);
}
return absl::nullopt;
}
void IndexerASTVisitor::AddChildOfEdgeToDeclContext(
const clang::Decl* Decl, const GraphObserver::NodeId& DeclNode) {
if (const DeclContext* DC = Decl->getDeclContext()) {
if (FLAGS_experimental_alias_template_instantiations) {
if (!Job->UnderneathImplicitTemplateInstantiation) {
if (auto ContextId = BuildNodeIdForRefToDeclContext(DC)) {
Observer.recordChildOfEdge(DeclNode, ContextId.value());
}
}
} else {
if (auto ContextId = BuildNodeIdForDeclContext(DC)) {
Observer.recordChildOfEdge(DeclNode, ContextId.value());
}
}
}
}
bool IndexerASTVisitor::VisitDeclRefExpr(const clang::DeclRefExpr* DRE) {
return VisitDeclRefOrIvarRefExpr(DRE, DRE->getDecl(), DRE->getLocation());
}
bool IndexerASTVisitor::VisitBuiltinTypeLoc(clang::BuiltinTypeLoc TL) {
if (FLAGS_emit_anchors_on_builtins) {
RecordTypeLocSpellingLocation(TL);
}
return true;
}
bool IndexerASTVisitor::VisitEnumTypeLoc(clang::EnumTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitRecordTypeLoc(clang::RecordTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitTemplateTypeParmTypeLoc(
clang::TemplateTypeParmTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitTemplateSpecializationTypeLoc(
clang::TemplateSpecializationTypeLoc TL) {
auto NameLocation = TL.getTemplateNameLoc();
if (NameLocation.isFileID()) {
if (auto RCC = ExpandedRangeInCurrentContext(NameLocation)) {
if (auto TemplateName =
BuildNodeIdForTemplateName(TL.getTypePtr()->getTemplateName())) {
NodeId DeclNode = [&] {
if (const auto* Decl = TL.getTypePtr()->getAsCXXRecordDecl()) {
return BuildNodeIdForDecl(Decl);
}
return TemplateName.value();
}();
Observer.recordDeclUseLocation(*RCC, DeclNode,
GraphObserver::Claimability::Claimable,
IsImplicit(*RCC));
}
}
}
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitDeducedTypeLoc(clang::DeducedTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitSubstTemplateTypeParmTypeLoc(
clang::SubstTemplateTypeParmTypeLoc TL) {
// TODO(zarko): Record both the replaced parameter and the replacement
// type.
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitDecltypeTypeLoc(clang::DecltypeTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitElaboratedTypeLoc(clang::ElaboratedTypeLoc TL) {
// This one wraps a qualified (via 'struct S' | 'N::M::type') type
// reference and we don't want to link 'struct'.
// TODO(zarko): Add an anchor for all the Elaborated type; otherwise decls
// like `typedef B::C tdef;` will only anchor `C` instead of `B::C`.
return true;
}
bool IndexerASTVisitor::VisitTypedefTypeLoc(clang::TypedefTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitInjectedClassNameTypeLoc(
clang::InjectedClassNameTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitDependentNameTypeLoc(
clang::DependentNameTypeLoc TL) {
if (auto Nodes = RecordTypeLocSpellingLocation(TL)) {
if (auto RCC =
ExplicitRangeInCurrentContext(RangeForASTEntity(TL.getNameLoc()))) {
Observer.recordDeclUseLocation(*RCC, Nodes.ForReference(),
GraphObserver::Claimability::Claimable,
IsImplicit(*RCC));
}
if (RecordParamEdgesForDependentName(Nodes.ForReference(),
TL.getQualifierLoc())) {
RecordLookupEdgeForDependentName(
Nodes.ForReference(),
clang::DeclarationName(TL.getTypePtr()->getIdentifier()));
}
}
return true;
}
bool IndexerASTVisitor::VisitPackExpansionTypeLoc(
clang::PackExpansionTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitObjCObjectTypeLoc(clang::ObjCObjectTypeLoc TL) {
for (unsigned i = 0; i < TL.getNumProtocols(); ++i) {
if (auto RCC = ExpandedRangeInCurrentContext(TL.getProtocolLoc(i))) {
Observer.recordDeclUseLocation(*RCC,
BuildNodeIdForDecl(TL.getProtocol(i)),
Claimability::Claimable, IsImplicit(*RCC));
}
}
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::VisitObjCTypeParamTypeLoc(
clang::ObjCTypeParamTypeLoc TL) {
RecordTypeLocSpellingLocation(TL);
return true;
}
bool IndexerASTVisitor::TraverseAttributedTypeLoc(clang::AttributedTypeLoc TL) {
// TODO(shahms): Emit a reference to the underlying TL covering the entire
// range of attributed TL. This was the desired behavior previously, but not
// implemented as such.
//
// If we have an attributed type, treat it as the raw type, but provide the
// source range of the attributed type. This allows us to connect a _Nonnull
// type back to the underlying type's definition. For example:
// `@property Data * _Nullable var;` should connect back to the type Data *,
// not Data * _Nullable;
return Base::TraverseAttributedTypeLoc(TL);
}
bool IndexerASTVisitor::TraverseDependentAddressSpaceTypeLoc(
clang::DependentAddressSpaceTypeLoc TL) {
// TODO(shahms): Emit a reference to the underlying TL covering the entire
// range of attributed TL. This was the desired behavior previously, but not
// implemented as such.
//
// If we have an attributed type, treat it as the raw type, but provide the
// source range of the attributed type. This allows us to connect a _Nonnull
// type back to the underlying type's definition. For example:
// `@property Data * _Nullable var;` should connect back to the type Data *,
// not Data * _Nullable;
return Base::TraverseDependentAddressSpaceTypeLoc(TL);
}
bool IndexerASTVisitor::TraverseMemberPointerTypeLoc(
clang::MemberPointerTypeLoc TL) {
// TODO(shahms): Fix this upstream. RecursiveASTVisitor calls:
// TraverseType(Class);
// TraverseTypeLoc(Pointee);
// Rather than TraverseTypeLoc on both.
if (auto* TSI = TL.getClassTInfo()) {
return TraverseTypeLoc(TSI->getTypeLoc()) &&
TraverseTypeLoc(TL.getPointeeLoc());
} else {
return Base::TraverseMemberPointerTypeLoc(TL);
}
}
bool IndexerASTVisitor::VisitDesignatedInitExpr(
const clang::DesignatedInitExpr* DIE) {
for (const auto& D : DIE->designators()) {
if (!D.isFieldDesignator()) continue;
if (const auto* F = D.getField()) {
if (!VisitDeclRefOrIvarRefExpr(DIE, F, D.getFieldLoc(), false, true)) {
return false;
}
}
}
return true;
}
NodeSet IndexerASTVisitor::RecordTypeLocSpellingLocation(clang::TypeLoc TL) {
if (auto RCC = ExpandedRangeInCurrentContext(TL.getSourceRange())) {
if (auto Nodes = BuildNodeSetForType(TL)) {
Observer.recordTypeSpellingLocation(
*RCC, Nodes.ForReference(), Nodes.claimability(), IsImplicit(*RCC));
return Nodes;
}
}
return NodeSet::Empty();
}
bool IndexerASTVisitor::TraverseDeclarationNameInfo(
clang::DeclarationNameInfo NameInfo) {
// For ConversionFunctions this leads to duplicate edges as the return value
// is visited both here and via TraverseFunctionProtoTypeLoc.
if (NameInfo.getName().getNameKind() ==
clang::DeclarationName::CXXConversionFunctionName) {
return true;
}
return Base::TraverseDeclarationNameInfo(NameInfo);
}
// Use FoundDecl to get to template defs; use getDecl to get to template
// instantiations.
bool IndexerASTVisitor::VisitDeclRefOrIvarRefExpr(
const clang::Expr* Expr, const NamedDecl* const FoundDecl,
SourceLocation SL, bool IsImplicit, bool IsInit) {
// TODO(zarko): check to see if this DeclRefExpr has already been indexed.
// (Use a simple N=1 cache.)
// TODO(zarko): Point at the capture as well as the thing being captured;
// port over RemapDeclIfCaptured.
// const NamedDecl* const TargetDecl = RemapDeclIfCaptured(FoundDecl);
const NamedDecl* TargetDecl = FoundDecl;
if (const auto* IFD = dyn_cast<clang::IndirectFieldDecl>(FoundDecl)) {
// An IndirectFieldDecl is just an alias; we want to record this as a
// reference to the underlying entity.
// TODO(jdennett): Would this be better done in BuildNodeIdForDecl?
TargetDecl = IFD->getAnonField();
}
if (isa<clang::VarDecl>(TargetDecl) && TargetDecl->isImplicit()) {
// Ignore variable declarations synthesized from for-range loops, as they
// are just a clang implementation detail.
return true;
}
if (SL.isValid()) {
SourceRange Range = RangeForASTEntity(SL);
if (IsImplicit) {
// Mark implicit ranges by making them zero-length.
Range.setEnd(Range.getBegin());
}
auto StmtId = BuildNodeIdForImplicitStmt(Expr);
if (auto RCC = RangeInCurrentContext(StmtId, Range)) {
GraphObserver::NodeId DeclId = BuildNodeIdForRefToDecl(TargetDecl);
if (IsInit) {
Observer.recordInitLocation(RCC.value(), DeclId,
GraphObserver::Claimability::Unclaimable,
this->IsImplicit(RCC.value()));
} else {
Observer.recordDeclUseLocation(RCC.value(), DeclId,
GraphObserver::Claimability::Unclaimable,
this->IsImplicit(RCC.value()));
}
for (const auto& S : Supports) {
S->InspectDeclRef(*this, SL, RCC.value(), DeclId, TargetDecl);
}
}
}
return true;
}
absl::optional<std::vector<GraphObserver::NodeId>>
IndexerASTVisitor::BuildTemplateArgumentList(ArrayRef<TemplateArgument> Args) {
std::vector<NodeId> result;
result.reserve(Args.size());
for (const auto& Arg : Args) {
if (auto ArgId =
BuildNodeIdForTemplateArgument(Arg, clang::SourceLocation())) {
result.push_back(*ArgId);
} else {
return absl::nullopt;
}
}
return result;
}
absl::optional<std::vector<GraphObserver::NodeId>>
IndexerASTVisitor::BuildTemplateArgumentList(
ArrayRef<TemplateArgumentLoc> Args) {
std::vector<NodeId> result;
result.reserve(Args.size());
for (const auto& ArgLoc : Args) {
if (auto ArgId = BuildNodeIdForTemplateArgument(ArgLoc, EmitRanges::Yes)) {
result.push_back(*ArgId);
} else {
return absl::nullopt;
}
}
return result;
}
bool IndexerASTVisitor::VisitVarDecl(const clang::VarDecl* Decl) {
if (SkipAliasedDecl(Decl)) {
return true;
}
if (isa<ParmVarDecl>(Decl)) {
// Ignore parameter types, those are added to the graph after processing
// the parent function or member.
return true;
}
if (Decl->isImplicit()) {
// Ignore variable declarations synthesized from for-range loops, as they
// are just a clang implementation detail.
return true;
}
auto Marks = MarkedSources.Generate(Decl);
SourceLocation DeclLoc = Decl->getLocation();
SourceRange NameRange = RangeForNameOfDeclaration(Decl);
GraphObserver::NodeId BodyDeclNode(Observer.getDefaultClaimToken(), "");
GraphObserver::NodeId DeclNode(Observer.getDefaultClaimToken(), "");
const clang::ASTTemplateArgumentListInfo* ArgsAsWritten = nullptr;
if (const auto* VTPSD =
dyn_cast<const clang::VarTemplatePartialSpecializationDecl>(Decl)) {
ArgsAsWritten = VTPSD->getTemplateArgsAsWritten();
BodyDeclNode = BuildNodeIdForDecl(Decl, 0);
DeclNode = RecordTemplate(VTPSD, BodyDeclNode);
} else if (const auto* VTD = Decl->getDescribedVarTemplate()) {
CHECK(!isa<clang::VarTemplateSpecializationDecl>(VTD));
BodyDeclNode = BuildNodeIdForDecl(Decl);
DeclNode = RecordTemplate(VTD, BodyDeclNode);
} else {
BodyDeclNode = BuildNodeIdForDecl(Decl);
DeclNode = BodyDeclNode;
}
// if this variable is a static member record it as static
if (Decl->isStaticDataMember()) {
Observer.recordStaticVariable(DeclNode);
}
if (auto* VTSD = dyn_cast<const clang::VarTemplateSpecializationDecl>(Decl)) {
Marks.set_implicit(Job->UnderneathImplicitTemplateInstantiation ||
!VTSD->isExplicitInstantiationOrSpecialization());
// If this is a partial specialization, we've already recorded the newly
// abstracted parameters above. We can now record the type arguments passed
// to the template we're specializing. Synthesize the type we need.
auto PrimaryOrPartial = VTSD->getSpecializedTemplateOrPartial();
if (auto NIDS = (ArgsAsWritten
? BuildTemplateArgumentList(ArgsAsWritten->arguments())
: BuildTemplateArgumentList(
VTSD->getTemplateArgs().asArray()))) {
if (auto SpecializedNode = BuildNodeIdForTemplateName(
clang::TemplateName(VTSD->getSpecializedTemplate()))) {
if (PrimaryOrPartial.is<clang::VarTemplateDecl*>() &&
!isa<const clang::VarTemplatePartialSpecializationDecl>(Decl)) {
// This is both an instance and a specialization of the primary
// template. We use the same arguments list for both.
Observer.recordInstEdge(
DeclNode, Observer.recordTappNode(SpecializedNode.value(), *NIDS),
GraphObserver::Confidence::NonSpeculative);
}
Observer.recordSpecEdge(
DeclNode, Observer.recordTappNode(SpecializedNode.value(), *NIDS),
GraphObserver::Confidence::NonSpeculative);
}
}
if (const auto* Partial =
PrimaryOrPartial
.dyn_cast<clang::VarTemplatePartialSpecializationDecl*>()) {
if (auto NIDS = BuildTemplateArgumentList(
VTSD->getTemplateInstantiationArgs().asArray())) {
Observer.recordInstEdge(
DeclNode,
Observer.recordTappNode(BuildNodeIdForDecl(Partial), *NIDS),
GraphObserver::Confidence::NonSpeculative);
}
}
}
MaybeRecordDefinitionRange(
RangeInCurrentContext(Decl->isImplicit(), DeclNode, NameRange), DeclNode,
BuildNodeIdForDefnOfDecl(Decl));
Marks.set_marked_source_end(GetLocForEndOfToken(
*Observer.getSourceManager(), *Observer.getLangOptions(),
Decl->getSourceRange().getEnd()));
Marks.set_name_range(NameRange);
if (const auto* TSI = Decl->getTypeSourceInfo()) {
// TODO(zarko): Storage classes.
AscribeSpelledType(TSI->getTypeLoc(), Decl->getType(), BodyDeclNode);
} else if (auto TyNodeId = BuildNodeIdForType(Decl->getType())) {
Observer.recordTypeEdge(BodyDeclNode, TyNodeId.value());
}
AddChildOfEdgeToDeclContext(Decl, DeclNode);
std::vector<LibrarySupport::Completion> Completions;
if (!IsDefinition(Decl)) {
AssignUSR(BodyDeclNode, Decl);
Observer.recordVariableNode(
BodyDeclNode, GraphObserver::Completeness::Incomplete,
GraphObserver::VariableSubkind::None, absl::nullopt);
Observer.recordMarkedSource(DeclNode, Marks.GenerateMarkedSource(DeclNode));
for (const auto& S : Supports) {
S->InspectVariable(*this, DeclNode, BodyDeclNode, Decl,
GraphObserver::Completeness::Incomplete, Completions);
}
return true;
}
FileID DeclFile = Observer.getSourceManager()->getFileID(Decl->getLocation());
auto NameRangeInContext =
RangeInCurrentContext(Decl->isImplicit(), BodyDeclNode, NameRange);
for (const auto* NextDecl : Decl->redecls()) {
const clang::Decl* OuterTemplate = nullptr;
// It's not useful to draw completion edges to implicit forward
// declarations, nor is it useful to declare that a definition completes
// itself.
if (NextDecl != Decl && !NextDecl->isImplicit()) {
if (auto* VD = dyn_cast<const clang::VarDecl>(NextDecl)) {
OuterTemplate = VD->getDescribedVarTemplate();
}
FileID NextDeclFile =
Observer.getSourceManager()->getFileID(NextDecl->getLocation());
// We should not point a completes edge from an abs node to a var node.
GraphObserver::NodeId TargetDecl =
BuildNodeIdForDecl(OuterTemplate ? OuterTemplate : NextDecl);
if (NameRangeInContext) {
Observer.recordCompletionRange(
NameRangeInContext.value(), TargetDecl,
NextDeclFile == DeclFile
? GraphObserver::Specificity::UniquelyCompletes
: GraphObserver::Specificity::Completes,
DeclNode);
}
Completions.push_back(LibrarySupport::Completion{NextDecl, TargetDecl});
}
}
AssignUSR(BodyDeclNode, Decl);
Observer.recordVariableNode(
BodyDeclNode, GraphObserver::Completeness::Definition,
GraphObserver::VariableSubkind::None, absl::nullopt);
Observer.recordMarkedSource(DeclNode, Marks.GenerateMarkedSource(DeclNode));
for (const auto& S : Supports) {
S->InspectVariable(*this, DeclNode, BodyDeclNode, Decl,
GraphObserver::Completeness::Definition, Completions);
}
return true;
}
bool IndexerASTVisitor::VisitNamespaceDecl(const clang::NamespaceDecl* Decl) {
auto Marks = MarkedSources.Generate(Decl);
GraphObserver::NodeId DeclNode(BuildNodeIdForDecl(Decl));
// Use the range covering `namespace` for anonymous namespaces.
SourceRange NameRange;
if (Decl->isAnonymousNamespace()) {
SourceLocation Loc = Decl->getBeginLoc();
if (Decl->isInline() && Loc.isValid() && Loc.isFileID()) {
// Skip the `inline` keyword.
Loc = RangeForSingleToken(Loc).getEnd();
if (Loc.isValid() && Loc.isFileID()) {
SkipWhitespace(*Observer.getSourceManager(), &Loc);
}
}
if (Loc.isValid() && Loc.isFileID()) {
NameRange = RangeForASTEntity(Loc);
}
} else {
NameRange = RangeForNameOfDeclaration(Decl);
}
// Namespaces are never defined; they are only invoked.
if (auto RCC =
RangeInCurrentContext(Decl->isImplicit(), DeclNode, NameRange)) {
Observer.recordDeclUseLocation(RCC.value(), DeclNode,
GraphObserver::Claimability::Unclaimable,
IsImplicit(RCC.value()));
}
Observer.recordNamespaceNode(DeclNode, Marks.GenerateMarkedSource(DeclNode));
AddChildOfEdgeToDeclContext(Decl, DeclNode);
return true;
}
bool IndexerASTVisitor::VisitBindingDecl(const clang::BindingDecl* Decl) {
SourceRange NameRange = RangeForNameOfDeclaration(Decl);
GraphObserver::NodeId DeclNode = BuildNodeIdForDecl(Decl);
if (auto RCC = ExplicitRangeInCurrentContext(NameRange)) {
Observer.recordDefinitionBindingRange(RCC.value(), DeclNode, absl::nullopt);
}
auto Marks = MarkedSources.Generate(Decl);
Marks.set_marked_source_end(Decl->getSourceRange().getEnd());
AddChildOfEdgeToDeclContext(Decl, DeclNode);
Observer.recordVariableNode(DeclNode, GraphObserver::Completeness::Definition,
GraphObserver::VariableSubkind::None,
Marks.GenerateMarkedSource(DeclNode));
return true;
}
bool IndexerASTVisitor::VisitFieldDecl(const clang::FieldDecl* Decl) {
auto Marks = MarkedSources.Generate(Decl);
GraphObserver::NodeId DeclNode(BuildNodeIdForDecl(Decl));
SourceRange NameRange = RangeForNameOfDeclaration(Decl);
MaybeRecordDefinitionRange(
RangeInCurrentContext(Decl->isImplicit(), DeclNode, NameRange), DeclNode,
absl::nullopt);
Marks.set_implicit(Job->UnderneathImplicitTemplateInstantiation);
Marks.set_marked_source_end(GetLocForEndOfToken(
*Observer.getSourceManager(), *Observer.getLangOptions(),
Decl->getSourceRange().getEnd()));
Marks.set_name_range(NameRange);
// TODO(zarko): Record completeness data. This is relevant for static fields,
// which may be declared along with a complete class definition but later
// defined in a separate translation unit.
Observer.recordVariableNode(DeclNode, GraphObserver::Completeness::Definition,
GraphObserver::VariableSubkind::Field,
Marks.GenerateMarkedSource(DeclNode));
AssignUSR(DeclNode, Decl);
if (const auto* TSI = Decl->getTypeSourceInfo()) {
// TODO(zarko): Record storage classes for fields.
AscribeSpelledType(TSI->getTypeLoc(), Decl->getType(), DeclNode);
} else if (auto TyNodeId = BuildNodeIdForType(Decl->getType())) {
Observer.recordTypeEdge(DeclNode, TyNodeId.value());
}
AddChildOfEdgeToDeclContext(Decl, DeclNode);
return true;
}
bool IndexerASTVisitor::VisitEnumConstantDecl(
const clang::EnumConstantDecl* Decl) {
auto Marks = MarkedSources.Generate(Decl);
Marks.set_implicit(Job->UnderneathImplicitTemplateInstantiation);
// We first build the NameId and NodeId for the enumerator.
GraphObserver::NodeId DeclNode(BuildNodeIdForDecl(Decl));
SourceLocation DeclLoc = Decl->getLocation();
SourceRange NameRange = RangeForNameOfDeclaration(Decl);
Marks.set_marked_source_end(GetLocForEndOfToken(
*Observer.getSourceManager(), *Observer.getLangOptions(),
Decl->getSourceRange().getEnd()));
Marks.set_name_range(NameRange);
MaybeRecordDefinitionRange(
RangeInCurrentContext(Decl->isImplicit(), DeclNode, NameRange), DeclNode,
absl::nullopt);
AssignUSR(DeclNode, Decl);
Observer.recordIntegerConstantNode(DeclNode, Decl->getInitVal());
AddChildOfEdgeToDeclContext(Decl, DeclNode);
Observer.recordMarkedSource(DeclNode, Marks.GenerateMarkedSource(DeclNode));
return true;
}
bool IndexerASTVisitor::VisitEnumDecl(const clang::EnumDecl* Decl) {
auto Marks = MarkedSources.Generate(Decl);
Marks.set_implicit(Job->UnderneathImplicitTemplateInstantiation);
GraphObserver::NodeId DeclNode(BuildNodeIdForDecl(Decl));
SourceLocation DeclLoc = Decl->getLocation();
SourceRange NameRange = RangeForNameOfDeclaration(Decl);
if (Decl->isThisDeclarationADefinition() && Decl->getBody() != nullptr) {
Marks.set_marked_source_end(Decl->getBody()->getSourceRange().getBegin());
} else {
Marks.set_marked_source_end(GetLocForEndOfToken(
*Observer.getSourceManager(), *Observer.getLangOptions(),
Decl->getSourceRange().getEnd()));
}
Marks.set_name_range(NameRange);
MaybeRecordDefinitionRange(
RangeInCurrentContext(Decl->isImplicit(), DeclNode, NameRange), DeclNode,
BuildNodeIdForDefnOfDecl(Decl));
bool HasSpecifiedStorageType = false;
if (const auto* TSI = Decl->getIntegerTypeSourceInfo()) {
HasSpecifiedStorageType = true;
AscribeSpelledType(TSI->getTypeLoc(), TSI->getType(), DeclNode);
}
AddChildOfEdgeToDeclContext(Decl, DeclNode);
// TODO(zarko): Would this be clearer as !Decl->isThisDeclarationADefinition
// or !Decl->isCompleteDefinition()? Do those calls have the same meaning
// as Decl->getDefinition() != Decl? The Clang documentation suggests that
// there is a subtle difference.
Observer.recordMarkedSource(DeclNode, Marks.GenerateMarkedSource(DeclNode));
// TODO(zarko): Add edges to previous decls.
if (Decl->getDefinition() != Decl) {
// TODO(jdennett): Should we use Type::isIncompleteType() instead of doing
// something enum-specific here?
AssignUSR(DeclNode, Decl);
Observer.recordEnumNode(
DeclNode,
HasSpecifiedStorageType ? GraphObserver::Completeness::Complete
: GraphObserver::Completeness::Incomplete,
Decl->isScoped() ? GraphObserver::EnumKind::Scoped
: GraphObserver::EnumKind::Unscoped);
return true;
}
FileID DeclFile = Observer.getSourceManager()->getFileID(Decl->getLocation());
for (const auto* NextDecl : Decl->redecls()) {
if (NextDecl != Decl) {
FileID NextDeclFile =
Observer.getSourceManager()->getFileID(NextDecl->getLocation());
if (auto RCC =
RangeInCurrentContext(Decl->isImplicit(), DeclNode, NameRange)) {
Observer.recordCompletionRange(
RCC.value(), BuildNodeIdForDecl(NextDecl),
NextDeclFile == DeclFile
? GraphObserver::Specificity::UniquelyCompletes
: GraphObserver::Specificity::Completes,
DeclNode);
}
}
}
AssignUSR(DeclNode, Decl);
Observer.recordEnumNode(DeclNode, GraphObserver::Completeness::Definition,
Decl->isScoped() ? GraphObserver::EnumKind::Scoped
: GraphObserver::EnumKind::Unscoped);
return true;
}
// TODO(zarko): In general, while we traverse a specialization we don't
// want to have the primary-template's type variables in context.
bool IndexerASTVisitor::TraverseClassTemplateDecl(
clang::ClassTemplateDecl* TD) {
auto Scope = PushScope(Job->TypeContext, TD->getTemplateParameters());
return Base::TraverseClassTemplateDecl(TD);
}
// NB: The Traverse* member that's called is based on the dynamic type of the
// AST node it's being called with (so only one of
// TraverseClassTemplate{Partial}SpecializationDecl will be called).
bool IndexerASTVisitor::TraverseClassTemplateSpecializationDecl(
clang::ClassTemplateSpecializationDecl* TD) {
auto Scope = std::make_tuple(
RestoreStack(Job->RangeContext),
RestoreValue(Job->UnderneathImplicitTemplateInstantiation));
// If this specialization was spelled out in the file, it has
// physical ranges.
if (TD->getTemplateSpecializationKind() !=
clang::TSK_ExplicitSpecialization) {
Job->RangeContext.push_back(BuildNodeIdForDecl(TD));
}
if (!TD->isExplicitInstantiationOrSpecialization()) {
Job->UnderneathImplicitTemplateInstantiation = true;
}
return Base::TraverseClassTemplateSpecializationDecl(TD);
}
bool IndexerASTVisitor::TraverseVarTemplateSpecializationDecl(
clang::VarTemplateSpecializationDecl* TD) {
if (TD->getTemplateSpecializationKind() == TSK_Undeclared ||
TD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) {