[analyzer] Support inlining of '[self classMethod]' and '[[self class] classMethod]'

Differential Revision: https://reviews.llvm.org/D28495

llvm-svn: 291867
This commit is contained in:
Anna Zaks 2017-01-13 00:50:47 +00:00
parent 14b1af5dcd
commit dcfc191404
2 changed files with 137 additions and 4 deletions

View File

@ -896,6 +896,38 @@ bool ObjCMethodCall::canBeOverridenInSubclass(ObjCInterfaceDecl *IDecl,
llvm_unreachable("The while loop should always terminate.");
}
static const ObjCMethodDecl *findDefiningRedecl(const ObjCMethodDecl *MD) {
if (!MD)
return MD;
// Find the redeclaration that defines the method.
if (!MD->hasBody()) {
for (auto I : MD->redecls())
if (I->hasBody())
MD = cast<ObjCMethodDecl>(I);
}
return MD;
}
static bool isCallToSelfClass(const ObjCMessageExpr *ME) {
const Expr* InstRec = ME->getInstanceReceiver();
if (!InstRec)
return false;
const auto *InstRecIg = dyn_cast<DeclRefExpr>(InstRec->IgnoreParenImpCasts());
// Check that receiver is called 'self'.
if (!InstRecIg || !InstRecIg->getFoundDecl() ||
!InstRecIg->getFoundDecl()->getName().equals("self"))
return false;
// Check that the method name is 'class'.
if (ME->getSelector().getNumArgs() != 0 ||
!ME->getSelector().getNameForSlot(0).equals("class"))
return false;
return true;
}
RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
const ObjCMessageExpr *E = getOriginExpr();
assert(E);
@ -910,6 +942,7 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
const MemRegion *Receiver = nullptr;
if (!SupersType.isNull()) {
// The receiver is guaranteed to be 'super' in this case.
// Super always means the type of immediate predecessor to the method
// where the call occurs.
ReceiverT = cast<ObjCObjectPointerType>(SupersType);
@ -921,7 +954,7 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
DynamicTypeInfo DTI = getDynamicTypeInfo(getState(), Receiver);
QualType DynType = DTI.getType();
CanBeSubClassed = DTI.canBeASubClass();
ReceiverT = dyn_cast<ObjCObjectPointerType>(DynType);
ReceiverT = dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType());
if (ReceiverT && CanBeSubClassed)
if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl())
@ -929,7 +962,32 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
CanBeSubClassed = false;
}
// Lookup the method implementation.
// Handle special cases of '[self classMethod]' and
// '[[self class] classMethod]', which are treated by the compiler as
// instance (not class) messages. We will statically dispatch to those.
if (auto *PT = dyn_cast_or_null<ObjCObjectPointerType>(ReceiverT)) {
// For [self classMethod], return the compiler visible declaration.
if (PT->getObjectType()->isObjCClass() &&
Receiver == getSelfSVal().getAsRegion())
return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl()));
// Similarly, handle [[self class] classMethod].
// TODO: We are currently doing a syntactic match for this pattern with is
// limiting as the test cases in Analysis/inlining/InlineObjCClassMethod.m
// shows. A better way would be to associate the meta type with the symbol
// using the dynamic type info tracking and use it here. We can add a new
// SVal for ObjC 'Class' values that know what interface declaration they
// come from. Then 'self' in a class method would be filled in with
// something meaningful in ObjCMethodCall::getReceiverSVal() and we could
// do proper dynamic dispatch for class methods just like we do for
// instance methods now.
if (E->getInstanceReceiver())
if (const auto *M = dyn_cast<ObjCMessageExpr>(E->getInstanceReceiver()))
if (isCallToSelfClass(M))
return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl()));
}
// Lookup the instance method implementation.
if (ReceiverT)
if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) {
// Repeatedly calling lookupPrivateMethod() is expensive, especially

View File

@ -1,6 +1,7 @@
// RUN: %clang_cc1 -analyze -analyzer-checker=core,debug.ExprInspection -analyzer-config ipa=dynamic-bifurcate -verify %s
void clang_analyzer_checkInlined(int);
void clang_analyzer_eval(int);
// Test inlining of ObjC class methods.
@ -194,7 +195,9 @@ int foo2() {
@implementation SelfUsedInParent
+ (int)getNum {return 5;}
+ (int)foo {
return [self getNum];
int r = [self getNum];
clang_analyzer_eval(r == 5); // expected-warning{{TRUE}}
return r;
}
@end
@interface SelfUsedInParentChild : SelfUsedInParent
@ -229,8 +232,80 @@ void rdar15037033() {
+ (void)forwardDeclaredVariadicMethod:(int)x, ... {
clang_analyzer_checkInlined(0); // no-warning
}
@end
@interface SelfClassTestParent : NSObject
-(unsigned)returns10;
+(unsigned)returns20;
+(unsigned)returns30;
@end
@implementation SelfClassTestParent
-(unsigned)returns10 { return 100; }
+(unsigned)returns20 { return 100; }
+(unsigned)returns30 { return 100; }
@end
@interface SelfClassTest : SelfClassTestParent
-(unsigned)returns10;
+(unsigned)returns20;
+(unsigned)returns30;
@end
@implementation SelfClassTest
-(unsigned)returns10 { return 10; }
+(unsigned)returns20 { return 20; }
+(unsigned)returns30 { return 30; }
+(void)classMethod {
unsigned result1 = [self returns20];
clang_analyzer_eval(result1 == 20); // expected-warning{{TRUE}}
unsigned result2 = [[self class] returns30];
clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}}
unsigned result3 = [[super class] returns30];
clang_analyzer_eval(result3 == 100); // expected-warning{{UNKNOWN}}
}
-(void)instanceMethod {
unsigned result0 = [self returns10];
clang_analyzer_eval(result0 == 10); // expected-warning{{TRUE}}
unsigned result2 = [[self class] returns30];
clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}}
unsigned result3 = [[super class] returns30];
clang_analyzer_eval(result3 == 100); // expected-warning{{UNKNOWN}}
}
@end
@interface Parent : NSObject
+ (int)a;
+ (int)b;
@end
@interface Child : Parent
@end
@interface Other : NSObject
+(void)run;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Other run];
}
return 0;
}
@implementation Other
+(void)run {
int result = [Child a];
// TODO: This should return 100.
clang_analyzer_eval(result == 12); // expected-warning{{TRUE}}
}
@end
@implementation Parent
+ (int)a; {
return [self b];
}
+ (int)b; {
return 12;
}
@end
@implementation Child
+ (int)b; {
return 100;
}
@end