//===- 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 &Methods) { if (!D) return; if (!isa(D) && !isa(D)) return; SmallVector Overridden; cxcursor::getOverriddenCursors(cxcursor::MakeCXCursor(D, TU), Overridden); if (Overridden.empty()) { Methods.push_back(D->getCanonicalDecl()); return; } for (SmallVectorImpl::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 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(D)) { if (ImplD->getClassInterface()) return getCanonical(ImplD->getClassInterface()); } else if (const CXXConstructorDecl *CXXCtorD = dyn_cast(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(D) || isa(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 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(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 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 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(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(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(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(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(file); ASTUnit *CXXUnit = cxtu::getASTUnit(TU); if (!CXXUnit) return CXResult_Invalid; ASTUnit::ConcurrencyCheck Check(*CXXUnit); if (findIncludesInFile(TU, static_cast(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"