Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -51,6 +51,7 @@ ObjCAtSyncChecker.cpp ObjCContainersASTChecker.cpp ObjCContainersChecker.cpp + ObjCGenericsChecker.cpp ObjCMissingSuperCallChecker.cpp ObjCSelfInitChecker.cpp ObjCUnusedIVarsChecker.cpp Index: lib/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- lib/StaticAnalyzer/Checkers/Checkers.td +++ lib/StaticAnalyzer/Checkers/Checkers.td @@ -452,6 +452,10 @@ HelpText<"Check for direct assignments to instance variables in the methods annotated with objc_no_direct_instance_variable_assignment">, DescFile<"DirectIvarAssignment.cpp">; +def ObjCGenericsChecker : Checker<"ObjCGenerics">, + HelpText<"Checks for type errors.">, + DescFile<"ObjCGenericsChecker.cpp">; + } // end "alpha.osx.cocoa" let ParentPackage = CoreFoundation in { Index: lib/StaticAnalyzer/Checkers/ObjCGenericsChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/ObjCGenericsChecker.cpp @@ -0,0 +1,548 @@ +//=== ObjCGenericsChecker.cpp - Path sensitive checker for Generics *- C++ -*=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker tries to find type errors that the compiler is not able to catch +// due to the implicit conversions that was introduced for backward +// compatibility. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/ParentMap.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +using namespace clang; +using namespace ento; + +namespace { +class ObjCGenericsChecker + : public Checker> { +public: + ProgramStateRef checkPointerEscape(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, + PointerEscapeKind Kind) const; + + void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; + void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; + void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + +private: + mutable std::unique_ptr BT; + void initBugType() const { + if (!BT) + BT.reset( + new BugType(this, "Generics", categories::CoreFoundationObjectiveC)); + } + + class GenericsBugVisitor : public BugReporterVisitorImpl { + public: + GenericsBugVisitor(SymbolRef S) : Sym(S) {} + ~GenericsBugVisitor() override {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Sym); + } + + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; + + private: + // The tracked symbol. + SymbolRef Sym; + }; + + void reportBug(const ObjCObjectPointerType *From, + const ObjCObjectPointerType *To, ExplodedNode *N, + SymbolRef Sym, CheckerContext &C) const { + initBugType(); + SmallString<64> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Incompatible pointer types assigning to '"; + QualType::print(From, Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); + OS << "' from '"; + QualType::print(To, Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); + OS << "'"; + std::unique_ptr R(new BugReport(*BT, OS.str(), N)); + R->markInteresting(Sym); + R->addVisitor(llvm::make_unique(Sym)); + C.emitReport(std::move(R)); + } +}; +} // end anonymous namespace + +// ProgramState trait - a map from symbol to its type with specified params. +REGISTER_MAP_WITH_PROGRAMSTATE(TypeParamMap, SymbolRef, + const ObjCObjectPointerType *) + +PathDiagnosticPiece *ObjCGenericsChecker::GenericsBugVisitor::VisitNode( + const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, + BugReport &BR) { + ProgramStateRef state = N->getState(); + ProgramStateRef statePrev = PrevN->getState(); + + const ObjCObjectPointerType *const *TrackedType = + state->get(Sym); + const ObjCObjectPointerType *const *TrackedTypePrev = + statePrev->get(Sym); + if (!TrackedType) + return nullptr; + + if (TrackedTypePrev && *TrackedTypePrev == *TrackedType) + return nullptr; + + // Retrieve the associated statement. + const Stmt *S = nullptr; + ProgramPoint ProgLoc = N->getLocation(); + if (Optional SP = ProgLoc.getAs()) { + S = SP->getStmt(); + } + + if (!S) + return nullptr; + + const LangOptions &LangOpts = BRC.getASTContext().getLangOpts(); + + SmallString<64> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Type '"; + QualType::print(*TrackedType, Qualifiers(), OS, LangOpts, llvm::Twine()); + OS << "' is infered from "; + + if (const auto *ExplicitCast = dyn_cast(S)) { + OS << "explicit cast (from '"; + QualType::print(ExplicitCast->getSubExpr()->getType().getTypePtr(), + Qualifiers(), OS, LangOpts, llvm::Twine()); + OS << "' to '"; + QualType::print(ExplicitCast->getType().getTypePtr(), Qualifiers(), OS, + LangOpts, llvm::Twine()); + OS << "')"; + } else if (const auto *ImplicitCast = dyn_cast(S)) { + OS << "implicit cast (from '"; + QualType::print(ImplicitCast->getSubExpr()->getType().getTypePtr(), + Qualifiers(), OS, LangOpts, llvm::Twine()); + OS << "' to '"; + QualType::print(ImplicitCast->getType().getTypePtr(), Qualifiers(), OS, + LangOpts, llvm::Twine()); + OS << "')"; + } else { + OS << "this context"; + } + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return new PathDiagnosticEventPiece(Pos, OS.str(), true, nullptr); +} + +void ObjCGenericsChecker::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + if (!SR.hasDeadSymbols()) + return; + + ProgramStateRef State = C.getState(); + TypeParamMapTy TyParMap = State->get(); + for (TypeParamMapTy::iterator I = TyParMap.begin(), E = TyParMap.end(); + I != E; ++I) { + if (SR.isDead(I->first)) { + State = State->remove(I->first); + } + } +} + +static const ObjCObjectPointerType *getMostInformativeDerivedClassImpl( + const ObjCObjectPointerType *From, const ObjCObjectPointerType *To, + const ObjCObjectPointerType *MostInformativeCandidate, ASTContext &C) { + // Checking if from and two are the same classes modulo specialization. + if (From->getInterfaceDecl()->getCanonicalDecl() == + To->getInterfaceDecl()->getCanonicalDecl()) { + if (To->isSpecialized()) { + assert(MostInformativeCandidate->isSpecialized()); + return MostInformativeCandidate; + } + return From; + } + const auto *SuperOfTo = + To->getObjectType()->getSuperClassType()->getAs(); + assert(SuperOfTo); + QualType SuperPtrOfToQual = + C.getObjCObjectPointerType(QualType(SuperOfTo, 0)); + const auto *SuperPtrOfTo = SuperPtrOfToQual->getAs(); + if (To->isUnspecialized()) + return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, SuperPtrOfTo, + C); + else + return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, + MostInformativeCandidate, C); +} + +static const ObjCObjectPointerType * +getMostInformativeDerivedClass(const ObjCObjectPointerType *From, + const ObjCObjectPointerType *To, ASTContext &C) { + return getMostInformativeDerivedClassImpl(From, To, To, C); +} + +static bool storeWhenMoreInformative(ProgramStateRef &State, SymbolRef Sym, + const ObjCObjectPointerType *const *Old, + const ObjCObjectPointerType *New, + ASTContext &C) { + if (!Old || C.canAssignObjCInterfaces(*Old, New)) { + State = State->set(Sym, New); + return true; + } + return false; +} + +void ObjCGenericsChecker::checkPostStmt(const CastExpr *CE, + CheckerContext &C) const { + if (CE->getCastKind() != CK_BitCast) + return; + + QualType OriginType = CE->getSubExpr()->getType(); + QualType DestType = CE->getType(); + + const auto *OrigObjectPtrType = OriginType->getAs(); + const auto *DestObjectPtrType = DestType->getAs(); + + ASTContext &ASTCtxt = C.getASTContext(); + + if (!OrigObjectPtrType || !DestObjectPtrType) + return; + + // In order to detect subtype relation properly, strip the kindofness. + OrigObjectPtrType = OrigObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt); + DestObjectPtrType = DestObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt); + + const ObjCObjectType *OrigObjectType = OrigObjectPtrType->getObjectType(); + const ObjCObjectType *DestObjectType = DestObjectPtrType->getObjectType(); + + if (OrigObjectType->isUnspecialized() && DestObjectType->isUnspecialized()) + return; + + ProgramStateRef State = C.getState(); + SymbolRef Sym = State->getSVal(CE, C.getLocationContext()).getAsSymbol(); + if (!Sym) + return; + + // Check which assignments are legal. + bool OrigToDest = + ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, OrigObjectPtrType); + bool DestToOrig = + ASTCtxt.canAssignObjCInterfaces(OrigObjectPtrType, DestObjectPtrType); + const ObjCObjectPointerType *const *TrackedType = + State->get(Sym); + + // If OrigObjectType could convert to DestObjectType, this could be an + // implicit cast. Handle it as implicit cast. + if (isa(CE) && !OrigToDest) { + // Trust explicit downcasts. + // However a downcast may also lose information. E. g.: + // MutableMap : Map + // The downcast to mutable map loses the information about the types of the + // map, and in general there is no way to recover that information from the + // declaration. So no checks possible against APIs that expect specialized + // Maps. + if (DestToOrig) { + const ObjCObjectPointerType *WithMostInfo = + getMostInformativeDerivedClass(OrigObjectPtrType, DestObjectPtrType, + C.getASTContext()); + if (storeWhenMoreInformative(State, Sym, TrackedType, WithMostInfo, + ASTCtxt)) + C.addTransition(State); + } + return; + } + + if (DestObjectType->isUnspecialized()) { + // In case we already have some type information for this symbol from a + // Specialized -> Specialized conversion, do not record the OrigType, + // because it might contain less type information than the tracked type. + assert(OrigObjectType->isSpecialized()); + if (!TrackedType) { + State = State->set(Sym, OrigObjectPtrType); + C.addTransition(State); + } + } else { + // When upcast happens, store the type with the most information about the + // type parameters. + if (OrigToDest && !DestToOrig) { + const ObjCObjectPointerType *WithMostInfo = + getMostInformativeDerivedClass(DestObjectPtrType, OrigObjectPtrType, + C.getASTContext()); + // When an (implicit) upcast or a downcast happens according to static + // types,the destination type of the cast may contradict the tracked type. + // In this case a warning should be emitted. + if (TrackedType && + !ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, *TrackedType) && + !ASTCtxt.canAssignObjCInterfaces(*TrackedType, DestObjectPtrType)) { + ExplodedNode *N = C.addTransition(); + reportBug(*TrackedType, DestObjectPtrType, N, Sym, C); + return; + } + if (storeWhenMoreInformative(State, Sym, TrackedType, WithMostInfo, + ASTCtxt)) + C.addTransition(State); + return; + } + // Trust tracked type on unspecialized value -> specialized implicit + // downcasts. + if (TrackedType) { + if (storeWhenMoreInformative(State, Sym, TrackedType, DestObjectPtrType, + ASTCtxt)) { + C.addTransition(State); + return; + } + // Illegal cast. + if (!ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, *TrackedType)) { + ExplodedNode *N = C.addTransition(); + reportBug(*TrackedType, DestObjectPtrType, N, Sym, C); + } + } else { + // Just found out what the type of this symbol should be. + State = State->set(Sym, DestObjectPtrType); + C.addTransition(State); + } + } +} + +static const Expr *stripImplicitIdCast(const Expr *E, ASTContext &ASTCtxt) { + const ImplicitCastExpr *CE = dyn_cast(E); + if (CE && CE->getCastKind() == CK_BitCast && + CE->getType() == ASTCtxt.getObjCIdType()) + return CE->getSubExpr(); + else + return E; +} + +void ObjCGenericsChecker::checkPostObjCMessage(const ObjCMethodCall &M, + CheckerContext &C) const { + const ObjCMessageExpr *MessageExpr = M.getOriginExpr(); + + ProgramStateRef State = C.getState(); + SymbolRef Sym = M.getReturnValue().getAsSymbol(); + if (!Sym) + return; + + Selector Sel = MessageExpr->getSelector(); + // When invoking the class selector, store the class to the state. + if (MessageExpr->getReceiverKind() != ObjCMessageExpr::Class || + Sel.getAsString() != "class") + return; + + QualType ReceiverType = MessageExpr->getClassReceiver(); + const auto *ReceiverClassType = ReceiverType->getAs(); + QualType ReceiverClassPointerType = + C.getASTContext().getObjCObjectPointerType( + QualType(ReceiverClassType, 0)); + + if (!ReceiverClassType->isSpecialized()) + return; + const auto *InferredType = + ReceiverClassPointerType->getAs(); + assert(InferredType); + State = State->set(Sym, InferredType); + C.addTransition(State); +} + +class IsObjCTypeParamDependentTypeVisitor + : public RecursiveASTVisitor { +public: + IsObjCTypeParamDependentTypeVisitor() : Result(false) {} + bool VisitTypedefType(const TypedefType *Type) { + if (isa(Type->getDecl())) { + Result = true; + return false; + } + return true; + } + bool getResult() { return Result; } + +private: + bool Result; +}; + +static bool isObjCTypeParamDependent(QualType Type) { + IsObjCTypeParamDependentTypeVisitor Visitor; + Visitor.TraverseType(Type); + return Visitor.getResult(); +} + +void ObjCGenericsChecker::checkPreObjCMessage(const ObjCMethodCall &M, + CheckerContext &C) const { + const ObjCMessageExpr *MessageExpr = M.getOriginExpr(); + + ProgramStateRef State = C.getState(); + SymbolRef Sym = M.getReceiverSVal().getAsSymbol(); + if (!Sym) + return; + + QualType ReceiverType = MessageExpr->getReceiverType(); + + const auto *ReceiverObjectPtrType = + ReceiverType->getAs(); + + if (!ReceiverObjectPtrType) + return; + + const ObjCObjectPointerType *const *TrackedType = + State->get(Sym); + if (!TrackedType) + return; + + ASTContext &ASTCtxt = C.getASTContext(); + + // Get the type arguments from tracked type and substitute type arguments + // before do the semantic check. + + // When the receiver type is id, or some super class of the tracked type (and + // kindof type), look up the method in the tracked type, not in the receiver + // type. This way we preserve more information. Do this "devirtualization" on + // instance and class methods only. Otherwise trust the static type. + const ObjCMethodDecl *Method = nullptr; + if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Instance || + MessageExpr->getReceiverKind() == ObjCMessageExpr::Class) { + if (ASTCtxt.getObjCIdType() == ReceiverType || + ASTCtxt.getObjCClassType() == ReceiverType || + (ReceiverObjectPtrType->getObjectType()->isKindOfType() && + ASTCtxt.canAssignObjCInterfaces(ReceiverObjectPtrType, + *TrackedType))) { + const ObjCInterfaceDecl *InterfaceDecl = + (*TrackedType)->getInterfaceDecl(); + // The method might not be found. + Selector Sel = MessageExpr->getSelector(); + Method = InterfaceDecl->lookupInstanceMethod(Sel); + if (!Method) + Method = InterfaceDecl->lookupClassMethod(Sel); + } + } + + if (!Method) { + Method = MessageExpr->getMethodDecl(); + // When arc is disabled non-existent methods can be called. + if (!Method) + return; + } + + Optional> TypeArgs = + (*TrackedType)->getObjCSubstitutions(Method->getDeclContext()); + // This case might happen when there is an unspecialized override of a + // specialized method. + if (!TypeArgs) + return; + + for (unsigned i = 0; i < Method->param_size(); i++) { + const Expr *Arg = MessageExpr->getArg(i); + // We can't do any type-checking on a type-dependent argument. + if (Arg->isTypeDependent()) + continue; + + const ParmVarDecl *Param = Method->parameters()[i]; + + QualType OrigParamType = Param->getType(); + const auto *ParamTypedef = OrigParamType->getAs(); + if (!ParamTypedef) + continue; + + const auto *TypeParamDecl = + dyn_cast(ParamTypedef->getDecl()); + if (!TypeParamDecl) + continue; + + ObjCTypeParamVariance ParamVariance = TypeParamDecl->getVariance(); + + QualType ParamType = OrigParamType.substObjCTypeArgs( + ASTCtxt, *TypeArgs, ObjCSubstitutionContext::Parameter); + // Check if it can be assigned + const auto *ParamObjectPtrType = ParamType->getAs(); + const auto *ArgObjectPtrType = stripImplicitIdCast(Arg, ASTCtxt) + ->getType() + ->getAs(); + if (!ParamObjectPtrType || !ArgObjectPtrType) + continue; + + // Check if we have more concrete tracked type that is not a super type of + // the static argument type. + SVal ArgSVal = M.getArgSVal(i); + SymbolRef ArgSym = ArgSVal.getAsSymbol(); + if (ArgSym) { + const ObjCObjectPointerType *const *TrackedType = + State->get(ArgSym); + if (TrackedType && ASTCtxt.canAssignObjCInterfaces(ArgObjectPtrType, + *TrackedType)){ + ArgObjectPtrType = *TrackedType; + } + } + + // For covariant type parameters every subclasses and supertypes are both + // accepted. + if (!ASTCtxt.canAssignObjCInterfaces(ParamObjectPtrType, + ArgObjectPtrType) && + (ParamVariance != ObjCTypeParamVariance::Covariant || + !ASTCtxt.canAssignObjCInterfaces(ArgObjectPtrType, + ParamObjectPtrType))) { + ExplodedNode *N = C.addTransition(); + reportBug(ArgObjectPtrType, ParamObjectPtrType, N, Sym, C); + return; + } + } + QualType StaticResultType = Method->getReturnType(); + // Check whether the result type was a type parameter. + bool IsInstanceType = StaticResultType == ASTCtxt.getObjCInstanceType(); + if (!isObjCTypeParamDependent(StaticResultType) && !IsInstanceType) + return; + + QualType ResultType = Method->getReturnType().substObjCTypeArgs( + ASTCtxt, *TypeArgs, ObjCSubstitutionContext::Result); + if (IsInstanceType) + ResultType = QualType(*TrackedType, 0); + + const Stmt *Parent = + C.getCurrentAnalysisDeclContext()->getParentMap().getParent(MessageExpr); + if (M.getMessageKind() != OCM_Message) { + // Properties and subscripts are not direct parents. + Parent = + C.getCurrentAnalysisDeclContext()->getParentMap().getParent(Parent); + } + + const auto *ImplicitCast = dyn_cast_or_null(Parent); + if (!ImplicitCast || ImplicitCast->getCastKind() != CK_BitCast) + return; + + const auto *ExprTypeAboveCast = + ImplicitCast->getType()->getAs(); + const auto *ResultPtrType = ResultType->getAs(); + + if (!ExprTypeAboveCast || !ResultPtrType) + return; + + // Only warn on unrelated types to avoid too many false positives on + // downcasts. + if (!ASTCtxt.canAssignObjCInterfaces(ExprTypeAboveCast, ResultPtrType) && + !ASTCtxt.canAssignObjCInterfaces(ResultPtrType, ExprTypeAboveCast)) { + ExplodedNode *N = C.addTransition(); + reportBug(ResultPtrType, ExprTypeAboveCast, N, Sym, C); + return; + } +} + +/// Register checker. +void ento::registerObjCGenericsChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} Index: test/Analysis/generics.m =================================================================== --- /dev/null +++ test/Analysis/generics.m @@ -0,0 +1,266 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.osx.cocoa.ObjCGenerics -verify -Wno-objc-method-access %s + +#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 + +@protocol NSObject ++ (id)alloc; +- (id)init; +@end + +@protocol NSCopying +@end + +__attribute__((objc_root_class)) +@interface NSObject +@end + +@interface NSString : NSObject +@end + +@interface NSMutableString : NSString +@end + +@interface NSNumber : NSObject +@end + +@interface NSArray<__covariant T> : NSObject ++ (instancetype)arrayWithObjects:(const T [])objects count:(int)count; ++ (instancetype)getEmpty; ++ (NSArray *)getEmpty2; +- (int)contains:(T)obj; +- (T)getObjAtIndex:(int)idx; +- (T)objectAtIndexedSubscript:(int)idx; +@property(readonly) T firstObject; +@end + +@interface MutableArray : NSArray +- (int)addObject:(T)obj; +@end + +@interface LegacyMutableArray : MutableArray +@end + +@interface LegacySpecialMutableArray : LegacyMutableArray +@end + +@interface BuggyMutableArray : MutableArray +@end + +@interface BuggySpecialMutableArray : BuggyMutableArray +@end + +@interface MyMutableStringArray : MutableArray +@end + +@interface ExceptionalArray : MutableArray +- (ExceptionType) getException; +@end + +int getUnknown(); +NSArray *getStuff(); +NSArray *getTypedStuff() { + NSArray *c = getStuff(); + return c; +} + +void doStuff(NSArray *); +void withArrString(NSArray *); +void withArrMutableString(NSArray *); +void withMutArrString(MutableArray *); +void withMutArrMutableString(MutableArray *); + +void test(NSArray *a, NSArray *b, + NSArray *c) { + a = b; + c = a; // expected-warning {{Incompatible}} + [a contains: [[NSNumber alloc] init]]; // expected-warning {{Incompatible}} + [a contains: [[NSString alloc] init]]; + doStuff(a); // expected-warning {{Incompatible}} +} + +void test2() { + NSArray *a = getTypedStuff(); // expected-warning {{Incompatible}} +} + +void test3(NSArray *a, NSArray *b) { + b = a; + [a contains: [[NSNumber alloc] init]]; // expected-warning {{Incompatible}} + [a contains: [[NSString alloc] init]]; + doStuff(a); // expected-warning {{Incompatible}} +} + +void test4(id a, NSArray *b) { + b = a; + [a contains: [[NSNumber alloc] init]]; // expected-warning {{Incompatible}} + [a contains: [[NSString alloc] init]]; + doStuff(a); // expected-warning {{Incompatible}} +} + +void test5(id a, NSArray *b, + NSArray *c) { + a = b; + c = a; // expected-warning {{Incompatible}} + [a contains: [[NSNumber alloc] init]]; // expected-warning {{Incompatible}} + [a contains: [[NSString alloc] init]]; + doStuff(a); // expected-warning {{Incompatible}} +} + +void test6(MutableArray *m, MutableArray *a, + MutableArray *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 {{Incompatible}} + [m addObject: [[NSMutableString alloc] init]]; +} + +void test8(id a, MutableArray *b) { + b = a; + doStuff(a); // expected-warning {{Incompatible}} +} + +void test9(MutableArray *a, + MutableArray *b) { + b = (MutableArray *)a; + [a addObject: [[NSString alloc] init]]; // expected-warning {{Incompatible}} +} + +void test10(id d, MyMutableStringArray *a, + MutableArray *b, + MutableArray *c) { + d = a; + b = d; + c = d; // expected-warning {{Incompatible}} +} + +void test11(id d, ExceptionalArray *a, + MutableArray *b, + MutableArray *c) { + d = a; + [d contains: [[NSString alloc] init]]; + [d contains: [[NSNumber alloc] init]]; // expected-warning {{Incompatible}} + b = d; + c = d; // expected-warning {{Incompatible}} +} + +void test12(id d, ExceptionalArray *a, + MutableArray *b, + MutableArray *c) { + a = d; + [d contains: [[NSString alloc] init]]; + [d contains: [[NSNumber alloc] init]]; // expected-warning {{Incompatible}} + b = d; + c = d; // expected-warning {{Incompatible}} +} + +void test13(id a) { + withMutArrString(a); + withMutArrMutableString(a); // expected-warning {{Incompatible}} +} + +void test14(id a) { + withMutArrMutableString(a); + withMutArrString(a); // expected-warning {{Incompatible}} +} + +void test15(LegacyMutableArray *a) { + withMutArrMutableString(a); + withMutArrString(a); // expected-warning {{Incompatible}} +} + +void test16(LegacySpecialMutableArray *a) { + withMutArrString(a); + withMutArrMutableString(a); // expected-warning {{Incompatible}} +} + +void test17(BuggyMutableArray *a) { + withMutArrString(a); + withMutArrMutableString(a); // expected-warning {{Incompatible}} +} + +void test18(BuggySpecialMutableArray *a) { + withMutArrMutableString(a); + withMutArrString(a); // expected-warning {{Incompatible}} +} + +NSArray *getStrings(); +void test19(NSArray *a) { + NSArray *b = a; + // Valid uses of NSArray of NSNumbers. + b = getStrings(); + // Valid uses of NSArray of NSStrings. +} + +void test20(NSArray *a) { + NSArray *b = a; + NSString *str = [b getObjAtIndex: 0]; // expected-warning {{Incompatible}} + NSNumber *num = [b getObjAtIndex: 0]; + str = [b firstObject]; // expected-warning {{Incompatible}} + num = [b firstObject]; + str = b.firstObject; // expected-warning {{Incompatible}} + num = b.firstObject; + str = b[0]; // expected-warning {{Incompatible}} + num = b[0]; +} + +void test21(id m, NSArray *a, + MutableArray *b) { + a = b; + if (getUnknown() == 5) { + m = a; + [m addObject: [[NSString alloc] init]]; // expected-warning {{Incompatible}} + } else { + m = b; + [m addObject: [[NSMutableString alloc] init]]; + } +} + +void test22(__kindof NSArray *a, + MutableArray *b) { + a = b; + if (getUnknown() == 5) { + [a addObject: [[NSString alloc] init]]; // expected-warning {{Incompatible}} + } else { + [a addObject: [[NSMutableString alloc] init]]; + } +} + +void test23() { + // ObjCArrayLiterals are not specialized in the AST. + NSArray *arr = @[@"A", @"B"]; + [arr contains: [[NSNumber alloc] init]]; +} + +void test24() { + NSArray *arr = @[@"A", @"B"]; + NSArray *arr2 = arr; + [arr2 contains: [[NSNumber alloc] init]]; // expected-warning {{Incompatible}} +} + +void test25(id a, MutableArray *b) { + a = b; + [a nonExistentMethod]; +} + +void test26() { + Class c = [NSArray class]; + NSArray *a = [c getEmpty]; // expected-warning {{Incompatible}} + a = [c getEmpty2]; // expected-warning {{Incompatible}} +} + +void test27(NSArray *> *mat, NSArray *row) { + id temp = row; + [mat contains: temp]; // expected-warning {{Incompatible}} +}