| //===- CIndexHigh.cpp - Higher level API functions ------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "CursorVisitor.h" |
| #include "CLog.h" |
| #include "CXCursor.h" |
| #include "CXSourceLocation.h" |
| #include "CXTranslationUnit.h" |
| #include "clang/AST/DeclObjC.h" |
| #include "clang/Frontend/ASTUnit.h" |
| #include "llvm/Support/Compiler.h" |
| |
| using namespace clang; |
| using namespace cxcursor; |
| using namespace cxindex; |
| |
| static void getTopOverriddenMethods(CXTranslationUnit TU, |
| const Decl *D, |
| SmallVectorImpl<const Decl *> &Methods) { |
| if (!D) |
| return; |
| if (!isa<ObjCMethodDecl>(D) && !isa<CXXMethodDecl>(D)) |
| return; |
| |
| SmallVector<CXCursor, 8> Overridden; |
| cxcursor::getOverriddenCursors(cxcursor::MakeCXCursor(D, TU), Overridden); |
| |
| if (Overridden.empty()) { |
| Methods.push_back(D->getCanonicalDecl()); |
| return; |
| } |
| |
| for (SmallVectorImpl<CXCursor>::iterator |
| I = Overridden.begin(), E = Overridden.end(); I != E; ++I) |
| getTopOverriddenMethods(TU, cxcursor::getCursorDecl(*I), Methods); |
| } |
| |
| namespace { |
| |
| struct FindFileIdRefVisitData { |
| CXTranslationUnit TU; |
| FileID FID; |
| const Decl *Dcl; |
| int SelectorIdIdx; |
| CXCursorAndRangeVisitor visitor; |
| |
| typedef SmallVector<const Decl *, 8> TopMethodsTy; |
| TopMethodsTy TopMethods; |
| |
| FindFileIdRefVisitData(CXTranslationUnit TU, FileID FID, |
| const Decl *D, int selectorIdIdx, |
| CXCursorAndRangeVisitor visitor) |
| : TU(TU), FID(FID), SelectorIdIdx(selectorIdIdx), visitor(visitor) { |
| Dcl = getCanonical(D); |
| getTopOverriddenMethods(TU, Dcl, TopMethods); |
| } |
| |
| ASTContext &getASTContext() const { |
| return cxtu::getASTUnit(TU)->getASTContext(); |
| } |
| |
| /// We are looking to find all semantically relevant identifiers, |
| /// so the definition of "canonical" here is different than in the AST, e.g. |
| /// |
| /// \code |
| /// class C { |
| /// C() {} |
| /// }; |
| /// \endcode |
| /// |
| /// we consider the canonical decl of the constructor decl to be the class |
| /// itself, so both 'C' can be highlighted. |
| const Decl *getCanonical(const Decl *D) const { |
| if (!D) |
| return nullptr; |
| |
| D = D->getCanonicalDecl(); |
| |
| if (const ObjCImplDecl *ImplD = dyn_cast<ObjCImplDecl>(D)) { |
| if (ImplD->getClassInterface()) |
| return getCanonical(ImplD->getClassInterface()); |
| |
| } else if (const CXXConstructorDecl *CXXCtorD = |
| dyn_cast<CXXConstructorDecl>(D)) { |
| return getCanonical(CXXCtorD->getParent()); |
| } |
| |
| return D; |
| } |
| |
| bool isHit(const Decl *D) const { |
| if (!D) |
| return false; |
| |
| D = getCanonical(D); |
| if (D == Dcl) |
| return true; |
| |
| if (isa<ObjCMethodDecl>(D) || isa<CXXMethodDecl>(D)) |
| return isOverriddingMethod(D); |
| |
| return false; |
| } |
| |
| private: |
| bool isOverriddingMethod(const Decl *D) const { |
| if (llvm::is_contained(TopMethods, D)) |
| return true; |
| |
| TopMethodsTy methods; |
| getTopOverriddenMethods(TU, D, methods); |
| for (TopMethodsTy::iterator |
| I = methods.begin(), E = methods.end(); I != E; ++I) { |
| if (llvm::is_contained(TopMethods, *I)) |
| return true; |
| } |
| |
| return false; |
| } |
| }; |
| |
| } // end anonymous namespace. |
| |
| /// For a macro \arg Loc, returns the file spelling location and sets |
| /// to \arg isMacroArg whether the spelling resides inside a macro definition or |
| /// a macro argument. |
| static SourceLocation getFileSpellingLoc(SourceManager &SM, |
| SourceLocation Loc, |
| bool &isMacroArg) { |
| assert(Loc.isMacroID()); |
| SourceLocation SpellLoc = SM.getImmediateSpellingLoc(Loc); |
| if (SpellLoc.isMacroID()) |
| return getFileSpellingLoc(SM, SpellLoc, isMacroArg); |
| |
| isMacroArg = SM.isMacroArgExpansion(Loc); |
| return SpellLoc; |
| } |
| |
| static enum CXChildVisitResult findFileIdRefVisit(CXCursor cursor, |
| CXCursor parent, |
| CXClientData client_data) { |
| CXCursor declCursor = clang_getCursorReferenced(cursor); |
| if (!clang_isDeclaration(declCursor.kind)) |
| return CXChildVisit_Recurse; |
| |
| const Decl *D = cxcursor::getCursorDecl(declCursor); |
| if (!D) |
| return CXChildVisit_Continue; |
| |
| FindFileIdRefVisitData *data = (FindFileIdRefVisitData *)client_data; |
| if (data->isHit(D)) { |
| cursor = cxcursor::getSelectorIdentifierCursor(data->SelectorIdIdx, cursor); |
| |
| // We are looking for identifiers to highlight so for objc methods (and |
| // not a parameter) we can only highlight the selector identifiers. |
| if ((cursor.kind == CXCursor_ObjCClassMethodDecl || |
| cursor.kind == CXCursor_ObjCInstanceMethodDecl) && |
| cxcursor::getSelectorIdentifierIndex(cursor) == -1) |
| return CXChildVisit_Recurse; |
| |
| if (clang_isExpression(cursor.kind)) { |
| if (cursor.kind == CXCursor_DeclRefExpr || |
| cursor.kind == CXCursor_MemberRefExpr) { |
| // continue.. |
| |
| } else if (cursor.kind == CXCursor_ObjCMessageExpr && |
| cxcursor::getSelectorIdentifierIndex(cursor) != -1) { |
| // continue.. |
| |
| } else |
| return CXChildVisit_Recurse; |
| } |
| |
| SourceLocation |
| Loc = cxloc::translateSourceLocation(clang_getCursorLocation(cursor)); |
| SourceLocation SelIdLoc = cxcursor::getSelectorIdentifierLoc(cursor); |
| if (SelIdLoc.isValid()) |
| Loc = SelIdLoc; |
| |
| ASTContext &Ctx = data->getASTContext(); |
| SourceManager &SM = Ctx.getSourceManager(); |
| bool isInMacroDef = false; |
| if (Loc.isMacroID()) { |
| bool isMacroArg; |
| Loc = getFileSpellingLoc(SM, Loc, isMacroArg); |
| isInMacroDef = !isMacroArg; |
| } |
| |
| // We are looking for identifiers in a specific file. |
| std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); |
| if (LocInfo.first != data->FID) |
| return CXChildVisit_Recurse; |
| |
| if (isInMacroDef) { |
| // FIXME: For a macro definition make sure that all expansions |
| // of it expand to the same reference before allowing to point to it. |
| return CXChildVisit_Recurse; |
| } |
| |
| if (data->visitor.visit(data->visitor.context, cursor, |
| cxloc::translateSourceRange(Ctx, Loc)) == CXVisit_Break) |
| return CXChildVisit_Break; |
| } |
| return CXChildVisit_Recurse; |
| } |
| |
| static bool findIdRefsInFile(CXTranslationUnit TU, CXCursor declCursor, |
| const FileEntry *File, |
| CXCursorAndRangeVisitor Visitor) { |
| assert(clang_isDeclaration(declCursor.kind)); |
| SourceManager &SM = cxtu::getASTUnit(TU)->getSourceManager(); |
| |
| FileID FID = SM.translateFile(File); |
| const Decl *Dcl = cxcursor::getCursorDecl(declCursor); |
| if (!Dcl) |
| return false; |
| |
| FindFileIdRefVisitData data(TU, FID, Dcl, |
| cxcursor::getSelectorIdentifierIndex(declCursor), |
| Visitor); |
| |
| if (const DeclContext *DC = Dcl->getParentFunctionOrMethod()) { |
| return clang_visitChildren(cxcursor::MakeCXCursor(cast<Decl>(DC), TU), |
| findFileIdRefVisit, &data); |
| } |
| |
| SourceRange Range(SM.getLocForStartOfFile(FID), SM.getLocForEndOfFile(FID)); |
| CursorVisitor FindIdRefsVisitor(TU, |
| findFileIdRefVisit, &data, |
| /*VisitPreprocessorLast=*/true, |
| /*VisitIncludedEntities=*/false, |
| Range, |
| /*VisitDeclsOnly=*/true); |
| return FindIdRefsVisitor.visitFileRegion(); |
| } |
| |
| namespace { |
| |
| struct FindFileMacroRefVisitData { |
| ASTUnit &Unit; |
| const FileEntry *File; |
| const IdentifierInfo *Macro; |
| CXCursorAndRangeVisitor visitor; |
| |
| FindFileMacroRefVisitData(ASTUnit &Unit, const FileEntry *File, |
| const IdentifierInfo *Macro, |
| CXCursorAndRangeVisitor visitor) |
| : Unit(Unit), File(File), Macro(Macro), visitor(visitor) { } |
| |
| ASTContext &getASTContext() const { |
| return Unit.getASTContext(); |
| } |
| }; |
| |
| } // anonymous namespace |
| |
| static enum CXChildVisitResult findFileMacroRefVisit(CXCursor cursor, |
| CXCursor parent, |
| CXClientData client_data) { |
| const IdentifierInfo *Macro = nullptr; |
| if (cursor.kind == CXCursor_MacroDefinition) |
| Macro = getCursorMacroDefinition(cursor)->getName(); |
| else if (cursor.kind == CXCursor_MacroExpansion) |
| Macro = getCursorMacroExpansion(cursor).getName(); |
| if (!Macro) |
| return CXChildVisit_Continue; |
| |
| FindFileMacroRefVisitData *data = (FindFileMacroRefVisitData *)client_data; |
| if (data->Macro != Macro) |
| return CXChildVisit_Continue; |
| |
| SourceLocation |
| Loc = cxloc::translateSourceLocation(clang_getCursorLocation(cursor)); |
| |
| ASTContext &Ctx = data->getASTContext(); |
| SourceManager &SM = Ctx.getSourceManager(); |
| bool isInMacroDef = false; |
| if (Loc.isMacroID()) { |
| bool isMacroArg; |
| Loc = getFileSpellingLoc(SM, Loc, isMacroArg); |
| isInMacroDef = !isMacroArg; |
| } |
| |
| // We are looking for identifiers in a specific file. |
| std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); |
| if (SM.getFileEntryForID(LocInfo.first) != data->File) |
| return CXChildVisit_Continue; |
| |
| if (isInMacroDef) { |
| // FIXME: For a macro definition make sure that all expansions |
| // of it expand to the same reference before allowing to point to it. |
| return CXChildVisit_Continue; |
| } |
| |
| if (data->visitor.visit(data->visitor.context, cursor, |
| cxloc::translateSourceRange(Ctx, Loc)) == CXVisit_Break) |
| return CXChildVisit_Break; |
| return CXChildVisit_Continue; |
| } |
| |
| static bool findMacroRefsInFile(CXTranslationUnit TU, CXCursor Cursor, |
| const FileEntry *File, |
| CXCursorAndRangeVisitor Visitor) { |
| if (Cursor.kind != CXCursor_MacroDefinition && |
| Cursor.kind != CXCursor_MacroExpansion) |
| return false; |
| |
| ASTUnit *Unit = cxtu::getASTUnit(TU); |
| SourceManager &SM = Unit->getSourceManager(); |
| |
| FileID FID = SM.translateFile(File); |
| const IdentifierInfo *Macro = nullptr; |
| if (Cursor.kind == CXCursor_MacroDefinition) |
| Macro = getCursorMacroDefinition(Cursor)->getName(); |
| else |
| Macro = getCursorMacroExpansion(Cursor).getName(); |
| if (!Macro) |
| return false; |
| |
| FindFileMacroRefVisitData data(*Unit, File, Macro, Visitor); |
| |
| SourceRange Range(SM.getLocForStartOfFile(FID), SM.getLocForEndOfFile(FID)); |
| CursorVisitor FindMacroRefsVisitor(TU, |
| findFileMacroRefVisit, &data, |
| /*VisitPreprocessorLast=*/false, |
| /*VisitIncludedEntities=*/false, |
| Range); |
| return FindMacroRefsVisitor.visitPreprocessedEntitiesInRegion(); |
| } |
| |
| namespace { |
| |
| struct FindFileIncludesVisitor { |
| ASTUnit &Unit; |
| const FileEntry *File; |
| CXCursorAndRangeVisitor visitor; |
| |
| FindFileIncludesVisitor(ASTUnit &Unit, const FileEntry *File, |
| CXCursorAndRangeVisitor visitor) |
| : Unit(Unit), File(File), visitor(visitor) { } |
| |
| ASTContext &getASTContext() const { |
| return Unit.getASTContext(); |
| } |
| |
| enum CXChildVisitResult visit(CXCursor cursor, CXCursor parent) { |
| if (cursor.kind != CXCursor_InclusionDirective) |
| return CXChildVisit_Continue; |
| |
| SourceLocation |
| Loc = cxloc::translateSourceLocation(clang_getCursorLocation(cursor)); |
| |
| ASTContext &Ctx = getASTContext(); |
| SourceManager &SM = Ctx.getSourceManager(); |
| |
| // We are looking for includes in a specific file. |
| std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); |
| if (SM.getFileEntryForID(LocInfo.first) != File) |
| return CXChildVisit_Continue; |
| |
| if (visitor.visit(visitor.context, cursor, |
| cxloc::translateSourceRange(Ctx, Loc)) == CXVisit_Break) |
| return CXChildVisit_Break; |
| return CXChildVisit_Continue; |
| } |
| |
| static enum CXChildVisitResult visit(CXCursor cursor, CXCursor parent, |
| CXClientData client_data) { |
| return static_cast<FindFileIncludesVisitor*>(client_data)-> |
| visit(cursor, parent); |
| } |
| }; |
| |
| } // anonymous namespace |
| |
| static bool findIncludesInFile(CXTranslationUnit TU, const FileEntry *File, |
| CXCursorAndRangeVisitor Visitor) { |
| assert(TU && File && Visitor.visit); |
| |
| ASTUnit *Unit = cxtu::getASTUnit(TU); |
| SourceManager &SM = Unit->getSourceManager(); |
| |
| FileID FID = SM.translateFile(File); |
| |
| FindFileIncludesVisitor IncludesVisitor(*Unit, File, Visitor); |
| |
| SourceRange Range(SM.getLocForStartOfFile(FID), SM.getLocForEndOfFile(FID)); |
| CursorVisitor InclusionCursorsVisitor(TU, |
| FindFileIncludesVisitor::visit, |
| &IncludesVisitor, |
| /*VisitPreprocessorLast=*/false, |
| /*VisitIncludedEntities=*/false, |
| Range); |
| return InclusionCursorsVisitor.visitPreprocessedEntitiesInRegion(); |
| } |
| |
| |
| //===----------------------------------------------------------------------===// |
| // libclang public APIs. |
| //===----------------------------------------------------------------------===// |
| |
| extern "C" { |
| |
| CXResult clang_findReferencesInFile(CXCursor cursor, CXFile file, |
| CXCursorAndRangeVisitor visitor) { |
| LogRef Log = Logger::make(__func__); |
| |
| if (clang_Cursor_isNull(cursor)) { |
| if (Log) |
| *Log << "Null cursor"; |
| return CXResult_Invalid; |
| } |
| if (cursor.kind == CXCursor_NoDeclFound) { |
| if (Log) |
| *Log << "Got CXCursor_NoDeclFound"; |
| return CXResult_Invalid; |
| } |
| if (!file) { |
| if (Log) |
| *Log << "Null file"; |
| return CXResult_Invalid; |
| } |
| if (!visitor.visit) { |
| if (Log) |
| *Log << "Null visitor"; |
| return CXResult_Invalid; |
| } |
| |
| if (Log) |
| *Log << cursor << " @" << static_cast<const FileEntry *>(file); |
| |
| ASTUnit *CXXUnit = cxcursor::getCursorASTUnit(cursor); |
| if (!CXXUnit) |
| return CXResult_Invalid; |
| |
| ASTUnit::ConcurrencyCheck Check(*CXXUnit); |
| |
| if (cursor.kind == CXCursor_MacroDefinition || |
| cursor.kind == CXCursor_MacroExpansion) { |
| if (findMacroRefsInFile(cxcursor::getCursorTU(cursor), |
| cursor, |
| static_cast<const FileEntry *>(file), |
| visitor)) |
| return CXResult_VisitBreak; |
| return CXResult_Success; |
| } |
| |
| // We are interested in semantics of identifiers so for C++ constructor exprs |
| // prefer type references, e.g.: |
| // |
| // return MyStruct(); |
| // |
| // for 'MyStruct' we'll have a cursor pointing at the constructor decl but |
| // we are actually interested in the type declaration. |
| cursor = cxcursor::getTypeRefCursor(cursor); |
| |
| CXCursor refCursor = clang_getCursorReferenced(cursor); |
| |
| if (!clang_isDeclaration(refCursor.kind)) { |
| if (Log) |
| *Log << "cursor is not referencing a declaration"; |
| return CXResult_Invalid; |
| } |
| |
| if (findIdRefsInFile(cxcursor::getCursorTU(cursor), |
| refCursor, |
| static_cast<const FileEntry *>(file), |
| visitor)) |
| return CXResult_VisitBreak; |
| return CXResult_Success; |
| } |
| |
| CXResult clang_findIncludesInFile(CXTranslationUnit TU, CXFile file, |
| CXCursorAndRangeVisitor visitor) { |
| if (cxtu::isNotUsableTU(TU)) { |
| LOG_BAD_TU(TU); |
| return CXResult_Invalid; |
| } |
| |
| LogRef Log = Logger::make(__func__); |
| if (!file) { |
| if (Log) |
| *Log << "Null file"; |
| return CXResult_Invalid; |
| } |
| if (!visitor.visit) { |
| if (Log) |
| *Log << "Null visitor"; |
| return CXResult_Invalid; |
| } |
| |
| if (Log) |
| *Log << TU << " @" << static_cast<const FileEntry *>(file); |
| |
| ASTUnit *CXXUnit = cxtu::getASTUnit(TU); |
| if (!CXXUnit) |
| return CXResult_Invalid; |
| |
| ASTUnit::ConcurrencyCheck Check(*CXXUnit); |
| |
| if (findIncludesInFile(TU, static_cast<const FileEntry *>(file), visitor)) |
| return CXResult_VisitBreak; |
| return CXResult_Success; |
| } |
| |
| static enum CXVisitorResult _visitCursorAndRange(void *context, |
| CXCursor cursor, |
| CXSourceRange range) { |
| CXCursorAndRangeVisitorBlock block = (CXCursorAndRangeVisitorBlock)context; |
| return INVOKE_BLOCK2(block, cursor, range); |
| } |
| |
| CXResult clang_findReferencesInFileWithBlock(CXCursor cursor, |
| CXFile file, |
| CXCursorAndRangeVisitorBlock block) { |
| CXCursorAndRangeVisitor visitor = { block, |
| block ? _visitCursorAndRange : nullptr }; |
| return clang_findReferencesInFile(cursor, file, visitor); |
| } |
| |
| CXResult clang_findIncludesInFileWithBlock(CXTranslationUnit TU, |
| CXFile file, |
| CXCursorAndRangeVisitorBlock block) { |
| CXCursorAndRangeVisitor visitor = { block, |
| block ? _visitCursorAndRange : nullptr }; |
| return clang_findIncludesInFile(TU, file, visitor); |
| } |
| |
| } // end: extern "C" |