const Decl *getDecl() const;
/// \brief Returns the definition of the function or method that will be
- /// called. May be null.
- ///
- /// This is used when deciding how to inline the call.
- ///
- /// \param IsDynamicDispatch True if the definition returned may not be the
- /// definition that is actually invoked at runtime. Note that if we have
- /// sufficient type information to devirtualize a dynamic method call,
- /// we will (and \p IsDynamicDispatch will be set to \c false).
- const Decl *getDefinition(bool &IsDynamicDispatch) const;
+ /// called. Returns NULL if the definition cannot be found; ex: due to
+ /// dynamic dispatch in ObjC methods.
+ const Decl *getRuntimeDefinition() const;
/// \brief Returns the expression whose value will be the result of this call.
/// May be null.
return cast_or_null<FunctionDecl>(CallEvent::getDecl());
}
- const Decl *getDefinition(bool &IsDynamicDispatch) const {
- IsDynamicDispatch = false;
+ const Decl *getRuntimeDefinition() const {
const FunctionDecl *FD = getDecl();
// Note that hasBody() will fill FD with the definition FunctionDecl.
if (FD && FD->hasBody(FD))
: SimpleCall(CE, St, LCtx, K) {}
public:
- const Decl *getDefinition(bool &IsDynamicDispatch) const;
+ const Decl *getRuntimeDefinition() const;
static bool classof(const CallEvent *CA) {
return CA->getKind() >= CE_BEG_CXX_INSTANCE_CALLS &&
return BR->getDecl();
}
- const Decl *getDefinition(bool &IsDynamicDispatch) const {
- IsDynamicDispatch = false;
+ const Decl *getRuntimeDefinition() const {
return getBlockDecl();
}
unsigned getNumArgs() const { return 0; }
SVal getCXXThisVal() const;
- const Decl *getDefinition(bool &IsDynamicDispatch) const;
+ const Decl *getRuntimeDefinition() const;
static bool classof(const CallEvent *CA) {
return CA->getKind() == CE_CXXDestructor;
void getExtraInvalidatedRegions(RegionList &Regions) const;
QualType getDeclaredResultType() const;
+ ObjCMethodDecl *LookupClassMethodDefinition(Selector Sel,
+ ObjCInterfaceDecl *ClassDecl) const;
public:
ObjCMethodCall(const ObjCMessageExpr *Msg, ProgramStateRef St,
llvm_unreachable("Unknown message kind");
}
- const Decl *getDefinition(bool &IsDynamicDispatch) const {
- IsDynamicDispatch = true;
-
- const ObjCMethodDecl *MD = getDecl();
- if (!MD)
- return 0;
+ // TODO: We might want to only compute this once (or change the API for
+ // getting the parameters). Currently, this gets called 3 times during
+ // inlining.
+ const Decl *getRuntimeDefinition() const {
- for (Decl::redecl_iterator I = MD->redecls_begin(), E = MD->redecls_end();
- I != E; ++I) {
- if (cast<ObjCMethodDecl>(*I)->isThisDeclarationADefinition())
- return *I;
+ const ObjCMessageExpr *E = getOriginExpr();
+ if (E->isInstanceMessage()) {
+ return 0;
+ } else {
+ // This is a calss method.
+ // If we have type info for the receiver class, we are calling via
+ // class name.
+ if (ObjCInterfaceDecl *IDecl = E->getReceiverInterface()) {
+ return LookupClassMethodDefinition(E->getSelector(), IDecl);
+ }
}
+
return 0;
}
DISPATCH(getDecl);
}
-inline const Decl *CallEvent::getDefinition(bool &IsDynamicDispatch) const {
- DISPATCH_ARG(getDefinition, IsDynamicDispatch);
+inline const Decl *CallEvent::getRuntimeDefinition() const {
+ DISPATCH(getRuntimeDefinition);
}
inline unsigned CallEvent::getNumArgs() const {
void evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
const SimpleCall &Call);
- /// \bried Default implementation of call evaluation.
+ /// \brief Default implementation of call evaluation.
void defaultEvalCall(NodeBuilder &B, ExplodedNode *Pred,
const CallEvent &Call);
private:
CallEvent::param_iterator
AnyFunctionCall::param_begin(bool UseDefinitionParams) const {
- bool IgnoredDynamicDispatch;
- const Decl *D = UseDefinitionParams ? getDefinition(IgnoredDynamicDispatch)
+ const Decl *D = UseDefinitionParams ? getRuntimeDefinition()
: getDecl();
if (!D)
return 0;
CallEvent::param_iterator
AnyFunctionCall::param_end(bool UseDefinitionParams) const {
- bool IgnoredDynamicDispatch;
- const Decl *D = UseDefinitionParams ? getDefinition(IgnoredDynamicDispatch)
+ const Decl *D = UseDefinitionParams ? getRuntimeDefinition()
: getDecl();
if (!D)
return 0;
}
-const Decl *CXXInstanceCall::getDefinition(bool &IsDynamicDispatch) const {
- const Decl *D = SimpleCall::getDefinition(IsDynamicDispatch);
+const Decl *CXXInstanceCall::getRuntimeDefinition() const {
+ const Decl *D = SimpleCall::getRuntimeDefinition();
if (!D)
return 0;
if (const CXXMethodDecl *Devirtualized = devirtualize(MD, getCXXThisVal()))
return Devirtualized;
- IsDynamicDispatch = true;
- return MD;
+ return 0;
}
Regions.push_back(static_cast<const MemRegion *>(Data));
}
-const Decl *CXXDestructorCall::getDefinition(bool &IsDynamicDispatch) const {
- const Decl *D = AnyFunctionCall::getDefinition(IsDynamicDispatch);
+const Decl *CXXDestructorCall::getRuntimeDefinition() const {
+ const Decl *D = AnyFunctionCall::getRuntimeDefinition();
if (!D)
return 0;
if (const CXXMethodDecl *Devirtualized = devirtualize(MD, getCXXThisVal()))
return Devirtualized;
- IsDynamicDispatch = true;
- return MD;
+ return 0;
}
CallEvent::param_iterator
ObjCMethodCall::param_begin(bool UseDefinitionParams) const {
- bool IgnoredDynamicDispatch;
- const Decl *D = UseDefinitionParams ? getDefinition(IgnoredDynamicDispatch)
+ const Decl *D = UseDefinitionParams ? getRuntimeDefinition()
: getDecl();
if (!D)
return 0;
CallEvent::param_iterator
ObjCMethodCall::param_end(bool UseDefinitionParams) const {
- bool IgnoredDynamicDispatch;
- const Decl *D = UseDefinitionParams ? getDefinition(IgnoredDynamicDispatch)
+ const Decl *D = UseDefinitionParams ? getRuntimeDefinition()
: getDecl();
if (!D)
return 0;
return OCM_Message;
return static_cast<ObjCMessageKind>(Info.getInt());
}
+
+// TODO: This implementation is copied from SemaExprObjC.cpp, needs to be
+// factored into the ObjCInterfaceDecl.
+ObjCMethodDecl *ObjCMethodCall::LookupClassMethodDefinition(Selector Sel,
+ ObjCInterfaceDecl *ClassDecl) const {
+ ObjCMethodDecl *Method = 0;
+ // Lookup in class and all superclasses.
+ while (ClassDecl && !Method) {
+ if (ObjCImplementationDecl *ImpDecl = ClassDecl->getImplementation())
+ Method = ImpDecl->getClassMethod(Sel);
+
+ // Look through local category implementations associated with the class.
+ if (!Method)
+ Method = ClassDecl->getCategoryClassMethod(Sel);
+
+ // Before we give up, check if the selector is an instance method.
+ // But only in the root. This matches gcc's behavior and what the
+ // runtime expects.
+ if (!Method && !ClassDecl->getSuperClass()) {
+ Method = ClassDecl->lookupInstanceMethod(Sel);
+ // Look through local category implementations associated
+ // with the root class.
+ //if (!Method)
+ // Method = LookupPrivateInstanceMethod(Sel, ClassDecl);
+ }
+
+ ClassDecl = ClassDecl->getSuperClass();
+ }
+ return Method;
+}
+
if (!getAnalysisManager().shouldInlineCall())
return false;
- bool IsDynamicDispatch;
- const Decl *D = Call.getDefinition(IsDynamicDispatch);
- if (!D || IsDynamicDispatch)
+ const Decl *D = Call.getRuntimeDefinition();
+ if (!D)
return false;
const LocationContext *CurLC = Pred->getLocationContext();
break;
}
case CE_ObjCMessage:
- // These always use dynamic dispatch; enabling inlining means assuming
- // that a particular method will be called at runtime.
- llvm_unreachable("Dynamic dispatch should be handled above.");
+ break;
}
if (!shouldInlineDecl(D, Pred))
case OMF_self: {
// These methods return their receivers.
return State->BindExpr(E, LCtx, Msg->getReceiverSVal());
- break;
}
}
}
// The origin expression here is just used as a kind of checksum;
// for CallEvents that do not have origin expressions, this should still be
// safe.
- if (!isa<ObjCMethodCall>(Call)) {
- State = getInlineFailedState(Pred->getState(), E);
- if (State == 0 && inlineCall(Call, Pred)) {
- // If we inlined the call, the successor has been manually added onto
- // the work list and we should not consider it for subsequent call
- // handling steps.
- Bldr.takeNodes(Pred);
- return;
- }
+ State = getInlineFailedState(Pred->getState(), E);
+ if (State == 0 && inlineCall(Call, Pred)) {
+ // If we inlined the call, the successor has been manually added onto
+ // the work list and we should not consider it for subsequent call
+ // handling steps.
+ Bldr.takeNodes(Pred);
+ return;
}
// If we can't inline it, handle the return value and invalidate the regions.
// Generate a transition to non-Nil state.
if (notNilState != state)
Pred = Bldr.generateNode(currentStmt, Pred, notNilState);
-
- // Evaluate the call.
- defaultEvalCall(Bldr, Pred, msg);
}
} else {
// Check for special class methods.
}
}
-
- // Evaluate the call.
- defaultEvalCall(Bldr, Pred, msg);
}
+ // Evaluate the call.
+ defaultEvalCall(Bldr, Pred, msg);
+
}
ExplodedNodeSet dstPostvisit;
--- /dev/null
+// RUN: %clang_cc1 -analyze -analyzer-checker=core -verify %s
+
+// Test inlining of ObjC class methods.
+
+typedef signed char BOOL;
+typedef struct objc_class *Class;
+typedef struct objc_object {
+ Class isa;
+} *id;
+@protocol NSObject - (BOOL)isEqual:(id)object; @end
+@interface NSObject <NSObject> {}
++(id)alloc;
+-(id)init;
+-(id)autorelease;
+-(id)copy;
+- (Class)class;
+-(id)retain;
+@end
+
+// Vanila: ObjC class method is called by name.
+@interface MyParent : NSObject
++ (int)getInt;
+@end
+@interface MyClass : MyParent
++ (int)getInt;
+@end
+@implementation MyClass
++ (int)testClassMethodByName {
+ int y = [MyClass getInt];
+ return 5/y; // expected-warning {{Division by zero}}
+}
++ (int)getInt {
+ return 0;
+}
+@end
+
+// The definition is defined by the parent. Make sure we find it and inline.
+@interface MyParentDIP : NSObject
++ (int)getInt;
+@end
+@interface MyClassDIP : MyParentDIP
+@end
+@implementation MyClassDIP
++ (int)testClassMethodByName {
+ int y = [MyClassDIP getInt];
+ return 5/y; // expected-warning {{Division by zero}}
+}
+@end
+@implementation MyParentDIP
++ (int)getInt {
+ return 0;
+}
+@end
+
+// ObjC class method is called by name. Definition is in the category.
+@interface AAA : NSObject
+@end
+@interface AAA (MyCat)
++ (int)getInt;
+@end
+int foo() {
+ int y = [AAA getInt];
+ return 5/y; // expected-warning {{Division by zero}}
+}
+@implementation AAA
+@end
+@implementation AAA (MyCat)
++ (int)getInt {
+ return 0;
+}
+@end
+
+// There is no declaration in the class but there is one in the parent. Make
+// sure we pick the definition from the class and not the parent.
+@interface MyParentTricky : NSObject
++ (int)getInt;
+@end
+@interface MyClassTricky : MyParentTricky
+@end
+@implementation MyParentTricky
++ (int)getInt {
+ return 0;
+}
+@end
+@implementation MyClassTricky
++ (int)getInt {
+ return 1;
+}
++ (int)testClassMethodByName {
+ int y = [MyClassTricky getInt];
+ return 5/y; // no-warning
+}
+@end
+
+// ObjC class method is called by unknown class declaration (passed in as a
+// parameter). We should not inline in such case.
+@interface MyParentUnknown : NSObject
++ (int)getInt;
+@end
+@interface MyClassUnknown : MyParentUnknown
++ (int)getInt;
+@end
+@implementation MyClassUnknown
++ (int)testClassVariableByUnknownVarDecl: (Class)cl {
+ int y = [cl getInt];
+ return 3/y; // no-warning
+}
++ (int)getInt {
+ return 0;
+}
+@end
+
+
+// False negative.
+// ObjC class method call through a decl with a known type.
+// We should be able to track the type of currentClass and inline this call.
+@interface MyClassKT : NSObject
+@end
+@interface MyClassKT (MyCatKT)
++ (int)getInt;
+@end
+@implementation MyClassKT (MyCatKT)
++ (int)getInt {
+ return 0;
+}
+@end
+@implementation MyClassKT
+- (int)testClassMethodByKnownVarDecl {
+ Class currentClass = [self class];
+ int y = [currentClass getInt];
+ return 5/y; // Would be great to get a warning here.
+}
+@end