From: Argyrios Kyrtzidis Date: Thu, 30 Jul 2009 00:03:55 +0000 (+0000) Subject: Add support for ObjC message expressions, in the Analyzer: X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=19b732a5461f5023d2335773584ac9aaeb0af514;p=clang Add support for ObjC message expressions, in the Analyzer: -Accept an ObjC method and find all message expressions that this method may respond to. -Accept an ObjC message expression and find all methods that may respond to it. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@77551 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Index/Analyzer.h b/include/clang/Index/Analyzer.h index 90118b5173..f6b5465148 100644 --- a/include/clang/Index/Analyzer.h +++ b/include/clang/Index/Analyzer.h @@ -16,6 +16,7 @@ namespace clang { class Decl; + class ObjCMessageExpr; namespace idx { class Program; @@ -42,6 +43,10 @@ public: /// \brief Find all TULocations for references of the given Decl and pass /// them to Handler. void FindReferences(Decl *D, TULocationHandler &Handler); + + /// \brief Find methods that may respond to the given message and pass them + /// to Handler. + void FindObjCMethods(ObjCMessageExpr *MsgE, TULocationHandler &Handler); }; } // namespace idx diff --git a/lib/Index/Analyzer.cpp b/lib/Index/Analyzer.cpp index bd1d62c668..1f37a06642 100644 --- a/lib/Index/Analyzer.cpp +++ b/lib/Index/Analyzer.cpp @@ -16,9 +16,13 @@ #include "clang/Index/TranslationUnit.h" #include "clang/Index/Handlers.h" #include "clang/Index/ASTLocation.h" +#include "clang/Index/GlobalSelector.h" #include "clang/Index/DeclReferenceMap.h" +#include "clang/Index/SelectorMap.h" #include "clang/Index/IndexProvider.h" -#include "clang/AST/Decl.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/ExprObjC.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/Support/Compiler.h" using namespace clang; using namespace idx; @@ -73,7 +77,321 @@ public: DeclReferenceMap &RefMap = TU->getDeclReferenceMap(); for (DeclReferenceMap::astlocation_iterator I = RefMap.refs_begin(ND), E = RefMap.refs_end(ND); I != E; ++I) - TULocHandler.Handle(TULocation(TU, ASTLocation(*I))); + TULocHandler.Handle(TULocation(TU, *I)); + } +}; + +//===----------------------------------------------------------------------===// +// RefSelectorAnalyzer Implementation +//===----------------------------------------------------------------------===// + +/// \brief Accepts an ObjC method and finds all message expressions that this +/// method may respond to. +class VISIBILITY_HIDDEN RefSelectorAnalyzer : public TranslationUnitHandler { + Program &Prog; + TULocationHandler &TULocHandler; + + // The original ObjCInterface associated with the method. + Entity IFaceEnt; + GlobalSelector GlobSel; + bool IsInstanceMethod; + + /// \brief Super classes of the ObjCInterface. + typedef llvm::SmallSet EntitiesSetTy; + EntitiesSetTy HierarchyEntities; + +public: + RefSelectorAnalyzer(ObjCMethodDecl *MD, + Program &prog, TULocationHandler &handler) + : Prog(prog), TULocHandler(handler) { + assert(MD); + + // FIXME: Protocol methods. + assert(!isa(MD->getDeclContext()) && + "Protocol methods not supported yet"); + + ObjCInterfaceDecl *IFD = MD->getClassInterface(); + assert(IFD); + IFaceEnt = Entity::get(IFD, Prog); + GlobSel = GlobalSelector::get(MD->getSelector(), Prog); + IsInstanceMethod = MD->isInstanceMethod(); + + for (ObjCInterfaceDecl *Cls = IFD->getSuperClass(); + Cls; Cls = Cls->getSuperClass()) + HierarchyEntities.insert(Entity::get(Cls, Prog)); + } + + virtual void Handle(TranslationUnit *TU) { + assert(TU && "Passed null translation unit"); + + ASTContext &Ctx = TU->getASTContext(); + // Null means it doesn't exist in this translation unit. + ObjCInterfaceDecl *IFace = + cast_or_null(IFaceEnt.getDecl(Ctx)); + Selector Sel = GlobSel.getSelector(Ctx); + + SelectorMap &SelMap = TU->getSelectorMap(); + for (SelectorMap::astlocation_iterator + I = SelMap.refs_begin(Sel), E = SelMap.refs_end(Sel); I != E; ++I) { + if (ValidReference(*I, IFace)) + TULocHandler.Handle(TULocation(TU, *I)); + } + } + + /// \brief Determines whether the given message expression is likely to end + /// up at the given interface decl. + /// + /// It returns true "eagerly", meaning it will return false only if it can + /// "prove" statically that the interface cannot accept this message. + bool ValidReference(ASTLocation ASTLoc, ObjCInterfaceDecl *IFace) { + assert(ASTLoc.isValid()); + assert(ASTLoc.isStmt()); + + // FIXME: Finding @selector references should be through another Analyzer + // method, like FindSelectors. + if (isa(ASTLoc.getStmt())) + return false; + + ObjCInterfaceDecl *MsgD = 0; + ObjCMessageExpr *Msg = cast(ASTLoc.getStmt()); + + if (Msg->getReceiver()) { + const ObjCObjectPointerType *OPT = + Msg->getReceiver()->getType()->getAsObjCInterfacePointerType(); + + // Can be anything! Accept it as a possibility.. + if (!OPT || OPT->isObjCIdType() || OPT->isObjCQualifiedIdType()) + return true; + + // Expecting class method. + if (OPT->isObjCClassType() || OPT->isObjCQualifiedClassType()) + return !IsInstanceMethod; + + MsgD = OPT->getInterfaceDecl(); + assert(MsgD); + + // Should be an instance method. + if (!IsInstanceMethod) + return false; + + } else { + // Expecting class method. + if (IsInstanceMethod) + return false; + + MsgD = Msg->getClassInfo().first; + // FIXME: Case when we only have an identifier. + assert(MsgD && "Identifier only"); + } + + assert(MsgD); + + // Same interface ? We have a winner! + if (MsgD == IFace) + return true; + + // If the message interface is a superclass of the original interface, + // accept this message as a possibility. + if (HierarchyEntities.count(Entity::get(MsgD, Prog))) + return true; + + // If the message interface is a subclass of the original interface, accept + // the message unless there is a subclass in the hierarchy that will + // "steal" the message (thus the message "will go" to the subclass and not + /// the original interface). + if (IFace) { + Selector Sel = Msg->getSelector(); + for (ObjCInterfaceDecl *Cls = MsgD; Cls; Cls = Cls->getSuperClass()) { + if (Cls == IFace) + return true; + if (Cls->getMethod(Sel, IsInstanceMethod)) + return false; + } + } + + // The interfaces are unrelated, don't accept the message. + return false; + } +}; + +//===----------------------------------------------------------------------===// +// MessageAnalyzer Implementation +//===----------------------------------------------------------------------===// + +/// \brief Accepts an ObjC message expression and finds all methods that may +/// respond to it. +class VISIBILITY_HIDDEN MessageAnalyzer : public TranslationUnitHandler { + Program &Prog; + TULocationHandler &TULocHandler; + + // The ObjCInterface associated with the message. Can be null/invalid. + Entity MsgIFaceEnt; + GlobalSelector GlobSel; + bool CanBeInstanceMethod; + bool CanBeClassMethod; + + /// \brief Super classes of the ObjCInterface. + typedef llvm::SmallSet EntitiesSetTy; + EntitiesSetTy HierarchyEntities; + + /// \brief The interface in the message interface hierarchy that "intercepts" + /// the selector. + Entity ReceiverIFaceEnt; + +public: + MessageAnalyzer(ObjCMessageExpr *Msg, + Program &prog, TULocationHandler &handler) + : Prog(prog), TULocHandler(handler), + CanBeInstanceMethod(false), + CanBeClassMethod(false) { + + assert(Msg); + + ObjCInterfaceDecl *MsgD = 0; + + while (true) { + if (Msg->getReceiver() == 0) { + CanBeClassMethod = true; + MsgD = Msg->getClassInfo().first; + // FIXME: Case when we only have an identifier. + assert(MsgD && "Identifier only"); + break; + } + + const ObjCObjectPointerType *OPT = + Msg->getReceiver()->getType()->getAsObjCInterfacePointerType(); + + if (!OPT || OPT->isObjCIdType() || OPT->isObjCQualifiedIdType()) { + CanBeInstanceMethod = CanBeClassMethod = true; + break; + } + + if (OPT->isObjCClassType() || OPT->isObjCQualifiedClassType()) { + CanBeClassMethod = true; + break; + } + + MsgD = OPT->getInterfaceDecl(); + assert(MsgD); + CanBeInstanceMethod = true; + break; + } + + assert(CanBeInstanceMethod || CanBeClassMethod); + + Selector sel = Msg->getSelector(); + assert(!sel.isNull()); + + MsgIFaceEnt = Entity::get(MsgD, Prog); + GlobSel = GlobalSelector::get(sel, Prog); + + if (MsgD) { + for (ObjCInterfaceDecl *Cls = MsgD->getSuperClass(); + Cls; Cls = Cls->getSuperClass()) + HierarchyEntities.insert(Entity::get(Cls, Prog)); + + // Find the interface in the hierarchy that "receives" the message. + for (ObjCInterfaceDecl *Cls = MsgD; Cls; Cls = Cls->getSuperClass()) { + bool isReceiver = false; + + ObjCInterfaceDecl::lookup_const_iterator Meth, MethEnd; + for (llvm::tie(Meth, MethEnd) = Cls->lookup(sel); + Meth != MethEnd; ++Meth) { + if (ObjCMethodDecl *MD = dyn_cast(*Meth)) + if ((MD->isInstanceMethod() && CanBeInstanceMethod) || + (MD->isClassMethod() && CanBeClassMethod)) { + isReceiver = true; + break; + } + } + + if (isReceiver) { + ReceiverIFaceEnt = Entity::get(Cls, Prog); + break; + } + } + } + } + + virtual void Handle(TranslationUnit *TU) { + assert(TU && "Passed null translation unit"); + ASTContext &Ctx = TU->getASTContext(); + + // Null means it doesn't exist in this translation unit or there was no + // interface that was determined to receive the original message. + ObjCInterfaceDecl *ReceiverIFace = + cast_or_null(ReceiverIFaceEnt.getDecl(Ctx)); + + // No subclass for the original receiver interface, so it remains the + // receiver. + if (ReceiverIFaceEnt.isValid() && ReceiverIFace == 0) + return; + + // Null means it doesn't exist in this translation unit or there was no + // interface associated with the message in the first place. + ObjCInterfaceDecl *MsgIFace = + cast_or_null(MsgIFaceEnt.getDecl(Ctx)); + + Selector Sel = GlobSel.getSelector(Ctx); + SelectorMap &SelMap = TU->getSelectorMap(); + for (SelectorMap::method_iterator + I = SelMap.methods_begin(Sel), E = SelMap.methods_end(Sel); + I != E; ++I) { + ObjCMethodDecl *D = *I; + if (ValidMethod(D, MsgIFace, ReceiverIFace)) { + for (ObjCMethodDecl::redecl_iterator + RI = D->redecls_begin(), RE = D->redecls_end(); RI != RE; ++RI) + TULocHandler.Handle(TULocation(TU, ASTLocation(*RI))); + } + } + } + + /// \brief Determines whether the given method is likely to accept the + /// original message. + /// + /// It returns true "eagerly", meaning it will return false only if it can + /// "prove" statically that the method cannot accept the original message. + bool ValidMethod(ObjCMethodDecl *D, ObjCInterfaceDecl *MsgIFace, + ObjCInterfaceDecl *ReceiverIFace) { + assert(D); + + // FIXME: Protocol methods ? + if (isa(D->getDeclContext())) + return false; + + // No specific interface associated with the message. Can be anything. + if (MsgIFaceEnt.isInvalid()) + return true; + + if (!CanBeInstanceMethod && D->isInstanceMethod() || + !CanBeClassMethod && D->isClassMethod()) + return false; + + ObjCInterfaceDecl *IFace = D->getClassInterface(); + assert(IFace); + + // If the original message interface is the same or a superclass of the + // given interface, accept the method as a possibility. + if (MsgIFace && MsgIFace->isSuperClassOf(IFace)) + return true; + + if (ReceiverIFace) { + // The given interface, "overrides" the receiver. + if (ReceiverIFace->isSuperClassOf(IFace)) + return true; + } else { + // No receiver was found for the original message. + assert(ReceiverIFaceEnt.isInvalid()); + + // If the original message interface is a subclass of the given interface, + // accept the message. + if (HierarchyEntities.count(Entity::get(IFace, Prog))) + return true; + } + + // The interfaces are unrelated, or the receiver interface wasn't + // "overriden". + return false; } }; @@ -95,6 +413,13 @@ void Analyzer::FindDeclarations(Decl *D, TULocationHandler &Handler) { void Analyzer::FindReferences(Decl *D, TULocationHandler &Handler) { assert(D && "Passed null declaration"); + if (ObjCMethodDecl *MD = dyn_cast(D)) { + RefSelectorAnalyzer RSA(MD, Prog, Handler); + GlobalSelector Sel = GlobalSelector::get(MD->getSelector(), Prog); + Idxer.GetTranslationUnitsFor(Sel, RSA); + return; + } + Entity Ent = Entity::get(D, Prog); if (Ent.isInvalid()) return; @@ -102,3 +427,13 @@ void Analyzer::FindReferences(Decl *D, TULocationHandler &Handler) { RefEntityAnalyzer REA(Ent, Handler); Idxer.GetTranslationUnitsFor(Ent, REA); } + +/// \brief Find methods that may respond to the given message and pass them +/// to Handler. +void Analyzer::FindObjCMethods(ObjCMessageExpr *Msg, + TULocationHandler &Handler) { + assert(Msg); + MessageAnalyzer MsgAnalyz(Msg, Prog, Handler); + GlobalSelector GlobSel = GlobalSelector::get(Msg->getSelector(), Prog); + Idxer.GetTranslationUnitsFor(GlobSel, MsgAnalyz); +} diff --git a/test/Index/objc-decls.m b/test/Index/objc-decls.m new file mode 100644 index 0000000000..1a8ab4b557 --- /dev/null +++ b/test/Index/objc-decls.m @@ -0,0 +1,16 @@ +// RUN: clang-cc -emit-pch %S/t1.m -o %t1.m.ast && +// RUN: clang-cc -emit-pch %S/t2.m -o %t2.m.ast && + +// RUN: index-test %t1.m.ast %t2.m.ast -point-at %S/t1.m:12:12 -print-decls > %t && +// RUN: cat %t | count 2 && +// RUN: grep 'objc.h:2:9,' %t | count 2 && + +// RUN: index-test %t1.m.ast %t2.m.ast -point-at %S/objc.h:5:13 -print-decls > %t && +// RUN: cat %t | count 3 && +// RUN: grep 'objc.h:5:1,' %t | count 2 && +// RUN: grep 't1.m:15:1,' %t | count 1 && + +// RUN: index-test %t1.m.ast %t2.m.ast -point-at %S/objc.h:10:13 -print-decls > %t && +// RUN: cat %t | count 3 && +// RUN: grep 'objc.h:10:1,' %t | count 2 && +// RUN: grep 't2.m:11:1,' %t | count 1 diff --git a/test/Index/objc-message.m b/test/Index/objc-message.m new file mode 100644 index 0000000000..45ce83876c --- /dev/null +++ b/test/Index/objc-message.m @@ -0,0 +1,38 @@ +// RUN: clang-cc -emit-pch %S/t1.m -o %t1.m.ast && +// RUN: clang-cc -emit-pch %S/t2.m -o %t2.m.ast && + +// RUN: index-test %t1.m.ast %t2.m.ast -point-at %S/objc.h:5:13 -print-refs > %t && +// RUN: cat %t | count 1 && +// RUN: grep 't1.m:6:3,' %t && + +// RUN: index-test %t1.m.ast %t2.m.ast -point-at %S/objc.h:6:13 -print-refs > %t && +// RUN: cat %t | count 2 && +// RUN: grep 't1.m:7:3,' %t && +// RUN: grep 't2.m:7:3,' %t && + +// RUN: index-test %t1.m.ast %t2.m.ast -point-at %S/objc.h:10:13 -print-refs > %t && +// RUN: cat %t | count 2 && +// RUN: grep 't1.m:6:3,' %t && +// RUN: grep 't2.m:6:3,' %t && + +// RUN: index-test %t1.m.ast %t2.m.ast -point-at %S/t1.m:6:15 -print-decls > %t && +// RUN: cat %t | count 6 && +// RUN: grep 'objc.h:5:1,' %t | count 2 && +// RUN: grep 'objc.h:10:1,' %t | count 2 && +// RUN: grep 't1.m:15:1,' %t && +// RUN: grep 't2.m:11:1,' %t && + +// RUN: index-test %t1.m.ast %t2.m.ast -point-at %S/t1.m:7:15 -print-decls > %t && +// RUN: cat %t | count 3 && +// RUN: grep 'objc.h:6:1,' %t | count 2 && +// RUN: grep 't1.m:18:1,' %t && + +// RUN: index-test %t2.m.ast %t1.m.ast -point-at %S/t2.m:6:15 -print-decls > %t && +// RUN: cat %t | count 3 && +// RUN: grep 'objc.h:10:1,' %t | count 2 && +// RUN: grep 't2.m:11:1,' %t && + +// RUN: index-test %t2.m.ast %t1.m.ast -point-at %S/t2.m:7:15 -print-decls > %t && +// RUN: cat %t | count 3 && +// RUN: grep 'objc.h:6:1,' %t | count 2 && +// RUN: grep 't1.m:18:1,' %t diff --git a/test/Index/objc.h b/test/Index/objc.h new file mode 100644 index 0000000000..c671addde5 --- /dev/null +++ b/test/Index/objc.h @@ -0,0 +1,11 @@ +@interface Base { + int my_var; +} +-(int) my_var; +-(void) my_method: (int)param; ++(void) my_method: (int)param; +@end + +@interface Sub : Base +-(void) my_method: (int)param; +@end diff --git a/test/Index/t1.m b/test/Index/t1.m new file mode 100644 index 0000000000..b2a7a3726d --- /dev/null +++ b/test/Index/t1.m @@ -0,0 +1,23 @@ +#include "objc.h" + +static void foo() { + Base *base; + int x = [base my_var]; + [base my_method:x]; + [Base my_method:x]; +} + +@implementation Base +-(int) my_var { + return my_var; +} + +-(void) my_method: (int)param { +} + ++(void) my_method: (int)param { +} +@end + +// Suppress 'no run line' failure. +// RUN: true diff --git a/test/Index/t2.m b/test/Index/t2.m new file mode 100644 index 0000000000..00d2f1d92b --- /dev/null +++ b/test/Index/t2.m @@ -0,0 +1,16 @@ +#include "objc.h" + +static void foo() { + Sub *sub; + int x = [sub my_var]; + [sub my_method:x]; + [Sub my_method:x]; +} + +@implementation Sub +-(void) my_method: (int)param { +} +@end + +// Suppress 'no run line' failure. +// RUN: true diff --git a/tools/index-test/index-test.cpp b/tools/index-test/index-test.cpp index fd522d54d0..02971ed782 100644 --- a/tools/index-test/index-test.cpp +++ b/tools/index-test/index-test.cpp @@ -44,8 +44,8 @@ #include "clang/Index/Utils.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CommandLineSourceLoc.h" -#include "clang/AST/Decl.h" -#include "clang/AST/Expr.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/ExprObjC.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "llvm/Support/CommandLine.h" @@ -106,8 +106,48 @@ DisableFree("disable-free", static bool HadErrors = false; +static void ProcessObjCMessage(ObjCMessageExpr *Msg, Indexer &Idxer) { + llvm::raw_ostream &OS = llvm::outs(); + typedef Storing ResultsTy; + ResultsTy Results; + + Analyzer Analyz(Idxer.getProgram(), Idxer); + + switch (ProgAction) { + default: assert(0); + case PrintRefs: + llvm::errs() << "Error: Cannot -print-refs on a ObjC message expression\n"; + HadErrors = true; + return; + + case PrintDecls: { + Analyz.FindObjCMethods(Msg, Results); + for (ResultsTy::iterator + I = Results.begin(), E = Results.end(); I != E; ++I) + I->print(OS); + break; + } + + case PrintDefs: { + Analyz.FindObjCMethods(Msg, Results); + for (ResultsTy::iterator + I = Results.begin(), E = Results.end(); I != E; ++I) { + const ObjCMethodDecl *D = cast(I->getDecl()); + if (D->isThisDeclarationADefinition()) + I->print(OS); + } + break; + } + + } +} + static void ProcessASTLocation(ASTLocation ASTLoc, Indexer &Idxer) { assert(ASTLoc.isValid()); + + if (ObjCMessageExpr *Msg = + dyn_cast_or_null(ASTLoc.getStmt())) + return ProcessObjCMessage(Msg, Idxer); Decl *D = ASTLoc.getReferencedDecl(); if (D == 0) { @@ -140,7 +180,7 @@ static void ProcessASTLocation(ASTLocation ASTLoc, Indexer &Idxer) { break; } - case PrintDefs:{ + case PrintDefs: { Analyz.FindDeclarations(D, Results); for (ResultsTy::iterator I = Results.begin(), E = Results.end(); I != E; ++I) { @@ -206,6 +246,10 @@ int main(int argc, char **argv) { if (!PointAtLocation.empty()) { const std::string &Filename = PointAtLocation[0].FileName; const FileEntry *File = FileMgr.getFile(Filename); + if (File == 0) { + llvm::errs() << "File '" << Filename << "' does not exist\n"; + return 1; + } // Safety check. Using an out-of-date AST file will only lead to crashes // or incorrect results. @@ -218,10 +262,6 @@ int main(int argc, char **argv) { return 1; } - if (File == 0) { - llvm::errs() << "File '" << Filename << "' does not exist\n"; - return 1; - } unsigned Line = PointAtLocation[0].Line; unsigned Col = PointAtLocation[0].Column;