Summary: Objective-C Class objects can be used to do a dynamic dispatch on class methods. The analyzer had a few places where we tried to overcome the dynamic nature of it and still guess the actual function that is being called. That was done mostly using some simple heuristics covering the most widespread cases (e.g. [[self class] classmethod]). This solution introduces a way to track types represented by Class objects and work with that instead of direct AST matching. rdar://problem/50739539 Differential Revision: https://reviews.llvm.org/D78286
403 lines
9.7 KiB
Objective-C
403 lines
9.7 KiB
Objective-C
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config ipa=dynamic-bifurcate -verify -analyzer-config eagerly-assume=false %s
|
|
|
|
void clang_analyzer_checkInlined(int);
|
|
void clang_analyzer_eval(int);
|
|
|
|
// Test inlining of ObjC class methods.
|
|
|
|
typedef signed char BOOL;
|
|
#define YES ((BOOL)1)
|
|
#define NO ((BOOL)0)
|
|
typedef struct objc_class *Class;
|
|
typedef struct objc_object {
|
|
Class isa;
|
|
} * id;
|
|
@protocol NSObject
|
|
- (BOOL)isEqual:(id)object;
|
|
@end
|
|
@interface NSObject <NSObject> {}
|
|
+ (id)alloc;
|
|
+ (Class)class;
|
|
+ (Class)superclass;
|
|
- (id)init;
|
|
- (id)autorelease;
|
|
- (id)copy;
|
|
- (Class)class;
|
|
- (instancetype)self;
|
|
- (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
|
|
|
|
// ObjC class method is called by name. Definition is in the parent category.
|
|
@interface PPP : NSObject
|
|
@end
|
|
@interface PPP (MyCat)
|
|
+ (int)getInt;
|
|
@end
|
|
@interface CCC : PPP
|
|
@end
|
|
int foo4() {
|
|
int y = [CCC getInt];
|
|
return 5/y; // expected-warning {{Division by zero}}
|
|
}
|
|
@implementation PPP
|
|
@end
|
|
@implementation PPP (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
|
|
|
|
// ObjC class method call through a decl with a known type.
|
|
// Note, [self class] could be a subclass. Do we still want to inline here?
|
|
@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; // expected-warning{{Division by zero}}
|
|
}
|
|
@end
|
|
|
|
// Another false negative due to us not reasoning about self, which in this
|
|
// case points to the object of the class in the call site and should be equal
|
|
// to [MyParent class].
|
|
@interface MyParentSelf : NSObject
|
|
+ (int)testSelf;
|
|
@end
|
|
@implementation MyParentSelf
|
|
+ (int)testSelf {
|
|
if (self == [MyParentSelf class])
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
@end
|
|
@interface MyClassSelf : MyParentSelf
|
|
@end
|
|
@implementation MyClassSelf
|
|
+ (int)testClassMethodByKnownVarDecl {
|
|
int y = [MyParentSelf testSelf];
|
|
return 5/y; // expected-warning{{Division by zero}}
|
|
}
|
|
@end
|
|
int foo2() {
|
|
int y = [MyParentSelf testSelf];
|
|
return 5/y; // expected-warning{{Division by zero}}
|
|
}
|
|
|
|
// TODO: We do not inline 'getNum' in the following case, where the value of
|
|
// 'self' in call '[self getNum]' is available and evaualtes to
|
|
// 'SelfUsedInParentChild' if it's called from fooA.
|
|
// Self region should get created before we call foo and yje call to super
|
|
// should keep it live.
|
|
@interface SelfUsedInParent : NSObject
|
|
+ (int)getNum;
|
|
+ (int)foo;
|
|
@end
|
|
@implementation SelfUsedInParent
|
|
+ (int)getNum {return 5;}
|
|
+ (int)foo {
|
|
int r = [self getNum];
|
|
clang_analyzer_eval(r == 5); // expected-warning{{TRUE}}
|
|
return r;
|
|
}
|
|
@end
|
|
@interface SelfUsedInParentChild : SelfUsedInParent
|
|
+ (int)getNum;
|
|
+ (int)fooA;
|
|
@end
|
|
@implementation SelfUsedInParentChild
|
|
+ (int)getNum {return 0;}
|
|
+ (int)fooA {
|
|
return [super foo];
|
|
}
|
|
@end
|
|
int checkSelfUsedInparentClassMethod() {
|
|
return 5/[SelfUsedInParentChild fooA];
|
|
}
|
|
|
|
|
|
@interface Rdar15037033 : NSObject
|
|
@end
|
|
|
|
void rdar15037033() {
|
|
[Rdar15037033 forwardDeclaredMethod]; // expected-warning {{class method '+forwardDeclaredMethod' not found}}
|
|
[Rdar15037033 forwardDeclaredVariadicMethod:1, 2, 3, 0]; // expected-warning {{class method '+forwardDeclaredVariadicMethod:' not found}}
|
|
}
|
|
|
|
@implementation Rdar15037033
|
|
|
|
+ (void)forwardDeclaredMethod {
|
|
clang_analyzer_checkInlined(1); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
+ (void)forwardDeclaredVariadicMethod:(int)x, ... {
|
|
clang_analyzer_checkInlined(0); // no-warning
|
|
}
|
|
@end
|
|
|
|
@interface SelfClassTestParent : NSObject
|
|
-(unsigned)returns10;
|
|
+(unsigned)returns20;
|
|
+(unsigned)returns30;
|
|
@end
|
|
|
|
@interface SelfClassTest : SelfClassTestParent
|
|
- (unsigned)returns10;
|
|
+ (unsigned)returns20;
|
|
+ (unsigned)returns30;
|
|
@end
|
|
|
|
@implementation SelfClassTestParent
|
|
- (unsigned)returns10 {
|
|
return 100;
|
|
}
|
|
+ (unsigned)returns20 {
|
|
return 100;
|
|
}
|
|
+ (unsigned)returns30 {
|
|
return 100;
|
|
}
|
|
|
|
- (void)testSelfReassignment {
|
|
// Check that we didn't hardcode type for self.
|
|
self = [[[SelfClassTest class] alloc] init];
|
|
Class actuallyChildClass = [self class];
|
|
unsigned result = [actuallyChildClass returns30];
|
|
clang_analyzer_eval(result == 30); // expected-warning{{TRUE}}
|
|
}
|
|
@end
|
|
|
|
@implementation SelfClassTest
|
|
- (unsigned)returns10 {
|
|
return 10;
|
|
}
|
|
+ (unsigned)returns20 {
|
|
return 20;
|
|
}
|
|
+ (unsigned)returns30 {
|
|
return 30;
|
|
}
|
|
+ (BOOL)isClass {
|
|
return YES;
|
|
}
|
|
- (BOOL)isClass {
|
|
return NO;
|
|
}
|
|
+ (SelfClassTest *)create {
|
|
return [[self alloc] init];
|
|
}
|
|
+ (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{{TRUE}}
|
|
|
|
// Check that class info is propagated with data
|
|
Class class41 = [self class];
|
|
Class class42 = class41;
|
|
unsigned result4 = [class42 returns30];
|
|
clang_analyzer_eval(result4 == 30); // expected-warning{{TRUE}}
|
|
|
|
Class class51 = [super class];
|
|
Class class52 = class51;
|
|
unsigned result5 = [class52 returns30];
|
|
clang_analyzer_eval(result5 == 100); // expected-warning{{TRUE}}
|
|
}
|
|
- (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{{TRUE}}
|
|
|
|
// Check that class info is propagated with data
|
|
Class class41 = [self class];
|
|
Class class42 = class41;
|
|
unsigned result4 = [class42 returns30];
|
|
clang_analyzer_eval(result4 == 30); // expected-warning{{TRUE}}
|
|
|
|
Class class51 = [super class];
|
|
Class class52 = class51;
|
|
unsigned result5 = [class52 returns30];
|
|
clang_analyzer_eval(result5 == 100); // expected-warning{{TRUE}}
|
|
|
|
// Check that we inline class methods when class object is a receiver
|
|
Class class6 = [self class];
|
|
BOOL calledClassMethod = [class6 isClass];
|
|
clang_analyzer_eval(calledClassMethod == YES); // expected-warning{{TRUE}}
|
|
|
|
// Check that class info is propagated through the 'self' method
|
|
Class class71 = [self class];
|
|
Class class72 = [class71 self];
|
|
unsigned result7 = [class72 returns30];
|
|
clang_analyzer_eval(result7 == 30); // expected-warning{{TRUE}}
|
|
|
|
// Check that 'class' and 'super' info from direct invocation of the
|
|
// corresponding class methods is propagated with data
|
|
Class class8 = [SelfClassTest class];
|
|
unsigned result8 = [class8 returns30];
|
|
clang_analyzer_eval(result8 == 30); // expected-warning{{TRUE}}
|
|
|
|
Class class9 = [SelfClassTest superclass];
|
|
unsigned result9 = [class9 returns30];
|
|
clang_analyzer_eval(result9 == 100); // expected-warning{{TRUE}}
|
|
|
|
// Check that we get class from a propagated type
|
|
SelfClassTestParent *selfAsParent10 = [[SelfClassTest alloc] init];
|
|
Class class10 = [selfAsParent10 class];
|
|
unsigned result10 = [class10 returns30];
|
|
clang_analyzer_eval(result10 == 30); // expected-warning{{TRUE}}
|
|
|
|
SelfClassTestParent *selfAsParent11 = [[[self class] alloc] init];
|
|
Class class11 = [selfAsParent11 class];
|
|
unsigned result11 = [class11 returns30];
|
|
clang_analyzer_eval(result11 == 30); // expected-warning{{TRUE}}
|
|
}
|
|
@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
|