409 lines
14 KiB
Objective-C
409 lines
14 KiB
Objective-C
// RUN: %clang_analyze_cc1 -analyzer-checker=core,osx.cocoa.ObjCGenerics,alpha.core.DynamicTypeChecker -verify -Wno-objc-method-access %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=core,osx.cocoa.ObjCGenerics,alpha.core.DynamicTypeChecker -verify -Wno-objc-method-access %s -analyzer-output=plist -o %t.plist
|
|
// RUN: %normalize_plist <%t.plist | diff -ub %S/Inputs/expected-plists/generics.m.plist -
|
|
|
|
#if !__has_feature(objc_generics)
|
|
# error Compiler does not support Objective-C generics?
|
|
#endif
|
|
|
|
#if !__has_feature(objc_generics_variance)
|
|
# error Compiler does not support co- and contr-variance?
|
|
#endif
|
|
|
|
#define nil 0
|
|
typedef unsigned long NSUInteger;
|
|
typedef int BOOL;
|
|
|
|
@protocol NSObject
|
|
+ (id)alloc;
|
|
- (id)init;
|
|
@end
|
|
|
|
@protocol NSCopying
|
|
@end
|
|
|
|
__attribute__((objc_root_class))
|
|
@interface NSObject <NSObject>
|
|
@end
|
|
|
|
@interface NSString : NSObject <NSCopying>
|
|
@end
|
|
|
|
@interface NSMutableString : NSString
|
|
@end
|
|
|
|
@interface NSNumber : NSObject <NSCopying>
|
|
@end
|
|
|
|
@interface NSSet : NSObject <NSCopying>
|
|
@end
|
|
|
|
@interface NSArray<__covariant ObjectType> : NSObject
|
|
+ (instancetype)arrayWithObjects:(const ObjectType [])objects count:(NSUInteger)count;
|
|
+ (instancetype)getEmpty;
|
|
+ (NSArray<ObjectType> *)getEmpty2;
|
|
- (BOOL)contains:(ObjectType)obj;
|
|
- (BOOL)containsObject:(ObjectType)anObject;
|
|
- (ObjectType)getObjAtIndex:(NSUInteger)idx;
|
|
- (ObjectType)objectAtIndexedSubscript:(NSUInteger)idx;
|
|
- (NSArray<ObjectType> *)arrayByAddingObject:(ObjectType)anObject;
|
|
@property(readonly) ObjectType firstObject;
|
|
@end
|
|
|
|
@interface NSMutableArray<ObjectType> : NSArray<ObjectType>
|
|
- (void)addObject:(ObjectType)anObject;
|
|
- (instancetype)init;
|
|
@end
|
|
|
|
@interface MutableArray<ObjectType> : NSArray<ObjectType>
|
|
- (void)addObject:(ObjectType)anObject;
|
|
@end
|
|
|
|
@interface LegacyMutableArray : MutableArray
|
|
@end
|
|
|
|
@interface LegacySpecialMutableArray : LegacyMutableArray
|
|
@end
|
|
|
|
@interface BuggyMutableArray<T> : MutableArray
|
|
@end
|
|
|
|
@interface BuggySpecialMutableArray<T> : BuggyMutableArray<T>
|
|
@end
|
|
|
|
@interface MyMutableStringArray : MutableArray<NSString *>
|
|
@end
|
|
|
|
@interface ExceptionalArray<ExceptionType> : MutableArray<NSString *>
|
|
- (ExceptionType) getException;
|
|
@end
|
|
|
|
@interface UnrelatedType : NSObject<NSCopying>
|
|
@end
|
|
|
|
int getUnknown();
|
|
NSArray *getStuff();
|
|
NSArray *getTypedStuff() {
|
|
NSArray<NSNumber *> *c = getStuff();
|
|
return c;
|
|
}
|
|
|
|
void doStuff(NSArray<NSNumber *> *);
|
|
void withArrString(NSArray<NSString *> *);
|
|
void withArrMutableString(NSArray<NSMutableString *> *);
|
|
void withMutArrString(MutableArray<NSString *> *);
|
|
void withMutArrMutableString(MutableArray<NSMutableString *> *);
|
|
|
|
void incompatibleTypesErased(NSArray *a, NSMutableArray<NSString *> *b,
|
|
NSArray<NSNumber *> *c,
|
|
NSMutableArray *d) {
|
|
a = b;
|
|
c = a; // expected-warning {{Conversion from value of type 'NSMutableArray<NSString *> *' to incompatible type 'NSArray<NSNumber *> *'}}
|
|
[a contains: [[NSNumber alloc] init]];
|
|
[a contains: [[NSString alloc] init]];
|
|
doStuff(a); // expected-warning {{Conversion}}
|
|
|
|
d = b;
|
|
[d addObject: [[NSNumber alloc] init]]; // expected-warning {{Conversion from value of type 'NSNumber *' to incompatible type 'NSString *'}}
|
|
}
|
|
|
|
void crossProceduralErasedTypes() {
|
|
NSArray<NSString *> *a = getTypedStuff(); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void incompatibleTypesErasedReverseConversion(NSMutableArray *a,
|
|
NSMutableArray<NSString *> *b) {
|
|
b = a;
|
|
[a contains: [[NSNumber alloc] init]];
|
|
[a contains: [[NSString alloc] init]];
|
|
doStuff(a); // expected-warning {{Conversion}}
|
|
|
|
[a addObject: [[NSNumber alloc] init]]; // expected-warning {{Conversion from value of type 'NSNumber *' to incompatible type 'NSString *'}}
|
|
}
|
|
|
|
void idErasedIncompatibleTypesReverseConversion(id a, NSMutableArray<NSString *> *b) {
|
|
b = a;
|
|
[a contains: [[NSNumber alloc] init]];
|
|
[a contains: [[NSString alloc] init]];
|
|
doStuff(a); // expected-warning {{Conversion}}
|
|
|
|
[a addObject:[[NSNumber alloc] init]]; // expected-warning {{Conversion from value of type 'NSNumber *' to incompatible type 'NSString *'}}
|
|
}
|
|
|
|
void idErasedIncompatibleTypes(id a, NSMutableArray<NSString *> *b,
|
|
NSArray<NSNumber *> *c) {
|
|
a = b;
|
|
c = a; // expected-warning {{Conversion}}
|
|
[a contains: [[NSNumber alloc] init]];
|
|
[a contains: [[NSString alloc] init]];
|
|
doStuff(a); // expected-warning {{Conversion}}
|
|
|
|
[a addObject:[[NSNumber alloc] init]]; // expected-warning {{Conversion from value of type 'NSNumber *' to incompatible type 'NSString *'}}
|
|
}
|
|
|
|
void pathSensitiveInference(MutableArray *m, MutableArray<NSString *> *a,
|
|
MutableArray<NSMutableString *> *b) {
|
|
if (getUnknown() == 5) {
|
|
m = a;
|
|
[m contains: [[NSString alloc] init]];
|
|
} else {
|
|
m = b;
|
|
[m contains: [[NSMutableString alloc] init]];
|
|
}
|
|
[m addObject: [[NSString alloc] init]]; // expected-warning {{Conversion}}
|
|
[m addObject: [[NSMutableString alloc] init]];
|
|
}
|
|
|
|
void verifyAPIusage(id a, MutableArray<NSString *> *b) {
|
|
b = a;
|
|
doStuff(a); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void dontInferFromExplicitCastsOnUnspecialized(MutableArray *a,
|
|
MutableArray<NSMutableString *> *b) {
|
|
b = (MutableArray<NSMutableString *> *)a;
|
|
[a addObject: [[NSString alloc] init]]; // no-warning
|
|
}
|
|
|
|
void dontWarnOnExplicitCastsAfterInference(MutableArray *a) {
|
|
withMutArrString(a);
|
|
withMutArrMutableString((MutableArray<NSMutableString *> *)a); // no-warning
|
|
}
|
|
|
|
void dontDiagnoseOnExplicitCrossCasts(MutableArray<NSSet *> *a,
|
|
MutableArray<NSMutableString *> *b) {
|
|
// Treat an explicit cast to a specialized type as an indication that
|
|
// Objective-C's type system is not expressive enough to represent a
|
|
// the invariant the programmer wanted. After an explicit cast, do not
|
|
// warn about potential generics shenanigans.
|
|
b = (MutableArray<NSMutableString *> *)a; // no-warning
|
|
[a addObject: [[NSSet alloc] init]]; // no-warning
|
|
[b addObject: [[NSMutableString alloc] init]]; //no-warning
|
|
}
|
|
|
|
void subtypeOfGeneric(id d, MyMutableStringArray *a,
|
|
MutableArray<NSString *> *b,
|
|
MutableArray<NSNumber *> *c) {
|
|
d = a;
|
|
b = d;
|
|
c = d; // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void genericSubtypeOfGeneric(id d, ExceptionalArray<NSString *> *a,
|
|
MutableArray<NSString *> *b,
|
|
MutableArray<NSNumber *> *c) {
|
|
d = a;
|
|
[d contains: [[NSString alloc] init]];
|
|
[d contains: [[NSNumber alloc] init]];
|
|
b = d;
|
|
c = d; // expected-warning {{Conversion}}
|
|
|
|
[d addObject: [[NSNumber alloc] init]]; // expected-warning {{Conversion from value of type 'NSNumber *' to incompatible type 'NSString *'}}
|
|
}
|
|
|
|
void genericSubtypeOfGenericReverse(id d, ExceptionalArray<NSString *> *a,
|
|
MutableArray<NSString *> *b,
|
|
MutableArray<NSNumber *> *c) {
|
|
a = d;
|
|
[d contains: [[NSString alloc] init]];
|
|
[d contains: [[NSNumber alloc] init]];
|
|
b = d;
|
|
c = d; // expected-warning {{Conversion}}
|
|
|
|
[d addObject: [[NSNumber alloc] init]]; // expected-warning {{Conversion from value of type 'NSNumber *' to incompatible type 'NSString *'}}
|
|
}
|
|
|
|
void inferenceFromAPI(id a) {
|
|
// Here the type parameter is invariant. There should be a warning every time
|
|
// when the type parameter changes during the conversions.
|
|
withMutArrString(a);
|
|
withMutArrMutableString(a); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void inferenceFromAPI2(id a) {
|
|
withMutArrMutableString(a);
|
|
withMutArrString(a); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void inferenceFromAPIWithLegacyTypes(LegacyMutableArray *a) {
|
|
withMutArrMutableString(a);
|
|
withMutArrString(a); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void inferenceFromAPIWithLegacyTypes2(LegacySpecialMutableArray *a) {
|
|
withMutArrString(a);
|
|
withMutArrMutableString(a); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void inferenceFromAPIWithLegacyTypes3(__kindof NSArray<NSString *> *a) {
|
|
LegacyMutableArray *b = a;
|
|
withMutArrString(b);
|
|
withMutArrMutableString(b); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void inferenceFromAPIWithBuggyTypes(BuggyMutableArray<NSMutableString *> *a) {
|
|
withMutArrString(a);
|
|
withMutArrMutableString(a); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void InferenceFromAPIWithBuggyTypes2(BuggySpecialMutableArray<NSMutableString *> *a) {
|
|
withMutArrMutableString(a);
|
|
withMutArrString(a); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void InferenceFromAPIWithBuggyTypes3(MutableArray<NSMutableString *> *a) {
|
|
id b = a;
|
|
withMutArrMutableString((BuggyMutableArray<NSMutableString *> *)b);
|
|
withMutArrString(b); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void InferenceFromAPIWithBuggyTypes4(__kindof NSArray<NSString *> *a) {
|
|
BuggyMutableArray<NSMutableString *> *b = a;
|
|
withMutArrString(b);
|
|
withMutArrMutableString(b); // expected-warning {{Conversion}}
|
|
}
|
|
|
|
NSArray<NSString *> *getStrings();
|
|
void enforceDynamicRulesInsteadOfStatic(NSArray<NSNumber *> *a) {
|
|
NSArray *b = a;
|
|
// Valid uses of NSArray of NSNumbers.
|
|
b = getStrings();
|
|
// Valid uses of NSArray of NSStrings.
|
|
}
|
|
|
|
void workWithProperties(NSArray<NSNumber *> *a) {
|
|
NSArray *b = a;
|
|
NSString *str = [b getObjAtIndex: 0]; // expected-warning {{Object has a dynamic type 'NSNumber *' which is incompatible with static type 'NSString *'}}
|
|
NSNumber *num = [b getObjAtIndex: 0];
|
|
str = [b firstObject]; // expected-warning {{Object has a dynamic type 'NSNumber *' which is incompatible with static type 'NSString *'}}
|
|
num = [b firstObject];
|
|
str = b.firstObject; // expected-warning {{Object has a dynamic type 'NSNumber *' which is incompatible with static type 'NSString *'}}
|
|
num = b.firstObject;
|
|
str = b[0]; // expected-warning {{Object has a dynamic type 'NSNumber *' which is incompatible with static type 'NSString *'}}
|
|
num = b[0];
|
|
}
|
|
|
|
void findMethodDeclInTrackedType(id m, NSArray<NSMutableString *> *a,
|
|
MutableArray<NSMutableString *> *b) {
|
|
a = b;
|
|
if (getUnknown() == 5) {
|
|
m = a;
|
|
[m addObject: [[NSString alloc] init]]; // expected-warning {{Conversion}}
|
|
} else {
|
|
m = b;
|
|
[m addObject: [[NSMutableString alloc] init]];
|
|
}
|
|
}
|
|
|
|
void findMethodDeclInTrackedType2(__kindof NSArray<NSString *> *a,
|
|
MutableArray<NSMutableString *> *b) {
|
|
a = b;
|
|
if (getUnknown() == 5) {
|
|
[a addObject: [[NSString alloc] init]]; // expected-warning {{Conversion}}
|
|
} else {
|
|
[a addObject: [[NSMutableString alloc] init]];
|
|
}
|
|
}
|
|
|
|
void testUnannotatedLiterals() {
|
|
// ObjCArrayLiterals are not specialized in the AST.
|
|
NSArray *arr = @[@"A", @"B"];
|
|
[arr contains: [[NSNumber alloc] init]];
|
|
}
|
|
|
|
void testAnnotatedLiterals() {
|
|
NSArray<NSString *> *arr = @[@"A", @"B"];
|
|
NSArray *arr2 = arr;
|
|
[arr2 contains: [[NSNumber alloc] init]];
|
|
}
|
|
|
|
void nonExistentMethodDoesNotCrash(id a, MutableArray<NSMutableString *> *b) {
|
|
a = b;
|
|
[a nonExistentMethod];
|
|
}
|
|
|
|
void trackedClassVariables() {
|
|
Class c = [NSArray<NSString *> class];
|
|
NSArray<NSNumber *> *a = [c getEmpty]; // expected-warning {{Conversion}}
|
|
a = [c getEmpty2]; // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void nestedCollections(NSArray<NSArray<NSNumber *> *> *mat, NSArray<NSString *> *row) {
|
|
id temp = row;
|
|
[mat contains: temp]; // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void testMistmatchedTypeCast(MutableArray<NSMutableString *> *a) {
|
|
MutableArray *b = (MutableArray<NSNumber *> *)a;
|
|
[b addObject: [[NSNumber alloc] init]];
|
|
id c = (UnrelatedType *)a;
|
|
[c addObject: [[NSNumber alloc] init]];
|
|
[c addObject: [[NSString alloc] init]];
|
|
}
|
|
|
|
void returnCollectionToIdVariable(NSArray<NSArray<NSString *> *> *arr) {
|
|
NSArray *erased = arr;
|
|
id a = [erased firstObject];
|
|
NSArray<NSNumber *> *res = a; // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void eraseSpecialization(NSArray<NSArray<NSString *> *> *arr) {
|
|
NSArray *erased = arr;
|
|
NSArray* a = [erased firstObject];
|
|
NSArray<NSNumber *> *res = a; // expected-warning {{Conversion}}
|
|
}
|
|
|
|
void returnToUnrelatedType(NSArray<NSArray<NSString *> *> *arr) {
|
|
NSArray *erased = arr;
|
|
NSSet* a = [erased firstObject]; // expected-warning {{Object has a dynamic type 'NSArray<NSString *> *' which is incompatible with static type 'NSSet *'}}
|
|
(void)a;
|
|
}
|
|
|
|
void returnToIdVariable(NSArray<NSString *> *arr) {
|
|
NSArray *erased = arr;
|
|
id a = [erased firstObject];
|
|
NSNumber *res = a; // expected-warning {{Object has a dynamic type 'NSString *' which is incompatible with static type 'NSNumber *'}}
|
|
}
|
|
|
|
@interface UnrelatedTypeGeneric<T> : NSObject<NSCopying>
|
|
- (void)takesType:(T)v;
|
|
@end
|
|
|
|
void testGetMostInformativeDerivedForId(NSArray<NSString *> *a,
|
|
UnrelatedTypeGeneric<NSString *> *b) {
|
|
id idB = b;
|
|
a = idB; // expected-warning {{Conversion from value of type 'UnrelatedTypeGeneric<NSString *> *' to incompatible type 'NSArray<NSString *> *'}}
|
|
|
|
// rdar://problem/26086914 crash here caused by symbolic type being unrelated
|
|
// to compile-time source type of cast.
|
|
id x = a; // Compile-time type is NSArray<>, Symbolic type is UnrelatedTypeGeneric<>.
|
|
[x takesType:[[NSNumber alloc] init]]; // expected-warning {{Conversion from value of type 'NSNumber *' to incompatible type 'NSString *'}}
|
|
}
|
|
|
|
void testArgumentAfterUpcastToRootWithCovariantTypeParameter(NSArray<NSString *> *allStrings, NSNumber *number) {
|
|
NSArray<NSObject *> *allObjects = allStrings; // no-warning
|
|
NSArray<NSObject *> *moreObjects = [allObjects arrayByAddingObject:number]; // no-warning
|
|
}
|
|
|
|
void testArgumentAfterUpcastWithCovariantTypeParameter(NSArray<NSMutableString *> *allMutableStrings, NSNumber *number) {
|
|
NSArray<NSString *> *allStrings = allMutableStrings; // no-warning
|
|
id numberAsId = number;
|
|
NSArray<NSString *> *moreStrings = [allStrings arrayByAddingObject:numberAsId]; // Sema: expected-warning {{Object has a dynamic type 'NSNumber *' which is incompatible with static type 'NSString *'}}
|
|
}
|
|
|
|
void testArgumentAfterCastToUnspecializedWithCovariantTypeParameter(NSArray<NSMutableString *> *allMutableStrings, NSNumber *number) {
|
|
NSArray *allStrings = allMutableStrings; // no-warning
|
|
id numberAsId = number;
|
|
|
|
NSArray *moreStringsUnspecialized = [allStrings arrayByAddingObject:numberAsId]; // no-warning
|
|
|
|
// Ideally the analyzer would warn here.
|
|
NSArray<NSString *> *moreStringsSpecialized = [allStrings arrayByAddingObject:numberAsId];
|
|
}
|
|
|
|
void testCallToMethodWithCovariantParameterOnInstanceOfSubclassWithInvariantParameter(NSMutableArray<NSMutableString *> *mutableArrayOfMutableStrings, NSString *someString) {
|
|
NSArray<NSString *> *arrayOfStrings = mutableArrayOfMutableStrings;
|
|
[arrayOfStrings containsObject:someString]; // no-warning
|
|
}
|
|
|