From: Douglas Gregor Date: Tue, 7 Jul 2015 03:58:22 +0000 (+0000) Subject: Warn when an Objective-C collection literal element is converted to an incompatible... X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=61fd1cbc6d9a4fa6f5e02166390e0de7e9a38a1d;p=clang Warn when an Objective-C collection literal element is converted to an incompatible type. Objective-C collection literals produce unspecialized NSArray/NSDictionary objects that can then be implicitly converted to specialized versions of these types. In such cases, check that the elements in the collection are suitable for the specialized collection. Part of rdar://problem/6294649. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@241546 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 1cf9e676cf..8032b0b8b4 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -2131,6 +2131,11 @@ def warn_concatenated_nsarray_literal : Warning< InGroup; def note_objc_literal_comparison_isequal : Note< "use 'isEqual:' instead">; +def warn_objc_collection_literal_element : Warning< + "object of type %0 is not compatible with " + "%select{array element type|dictionary key type|dictionary value type}1 %2">, + InGroup; + def err_attribute_argument_is_zero : Error< "%0 attribute must be greater than 0">; def warn_attribute_argument_n_negative : Warning< diff --git a/lib/Sema/SemaChecking.cpp b/lib/Sema/SemaChecking.cpp index bdfff80c7f..d9d26e2af6 100644 --- a/lib/Sema/SemaChecking.cpp +++ b/lib/Sema/SemaChecking.cpp @@ -6884,6 +6884,101 @@ static void DiagnoseNullConversion(Sema &S, Expr *E, QualType T, S.getFixItZeroLiteralForType(T, Loc)); } +static void checkObjCArrayLiteral(Sema &S, QualType TargetType, + ObjCArrayLiteral *ArrayLiteral); +static void checkObjCDictionaryLiteral(Sema &S, QualType TargetType, + ObjCDictionaryLiteral *DictionaryLiteral); + +/// Check a single element within a collection literal against the +/// target element type. +static void checkObjCCollectionLiteralElement(Sema &S, + QualType TargetElementType, + Expr *Element, + unsigned ElementKind) { + // Skip a bitcast to 'id' or qualified 'id'. + if (auto ICE = dyn_cast(Element)) { + if (ICE->getCastKind() == CK_BitCast && + ICE->getSubExpr()->getType()->getAs()) + Element = ICE->getSubExpr(); + } + + QualType ElementType = Element->getType(); + ExprResult ElementResult(Element); + if (ElementType->getAs() && + S.CheckSingleAssignmentConstraints(TargetElementType, + ElementResult, + false, false) + != Sema::Compatible) { + S.Diag(Element->getLocStart(), + diag::warn_objc_collection_literal_element) + << ElementType << ElementKind << TargetElementType + << Element->getSourceRange(); + } + + if (auto ArrayLiteral = dyn_cast(Element)) + checkObjCArrayLiteral(S, TargetElementType, ArrayLiteral); + else if (auto DictionaryLiteral = dyn_cast(Element)) + checkObjCDictionaryLiteral(S, TargetElementType, DictionaryLiteral); +} + +/// Check an Objective-C array literal being converted to the given +/// target type. +static void checkObjCArrayLiteral(Sema &S, QualType TargetType, + ObjCArrayLiteral *ArrayLiteral) { + if (!S.NSArrayDecl) + return; + + const auto *TargetObjCPtr = TargetType->getAs(); + if (!TargetObjCPtr) + return; + + if (TargetObjCPtr->isUnspecialized() || + TargetObjCPtr->getInterfaceDecl()->getCanonicalDecl() + != S.NSArrayDecl->getCanonicalDecl()) + return; + + auto TypeArgs = TargetObjCPtr->getTypeArgs(); + if (TypeArgs.size() != 1) + return; + + QualType TargetElementType = TypeArgs[0]; + for (unsigned I = 0, N = ArrayLiteral->getNumElements(); I != N; ++I) { + checkObjCCollectionLiteralElement(S, TargetElementType, + ArrayLiteral->getElement(I), + 0); + } +} + +/// Check an Objective-C dictionary literal being converted to the given +/// target type. +static void checkObjCDictionaryLiteral( + Sema &S, QualType TargetType, + ObjCDictionaryLiteral *DictionaryLiteral) { + if (!S.NSDictionaryDecl) + return; + + const auto *TargetObjCPtr = TargetType->getAs(); + if (!TargetObjCPtr) + return; + + if (TargetObjCPtr->isUnspecialized() || + TargetObjCPtr->getInterfaceDecl()->getCanonicalDecl() + != S.NSDictionaryDecl->getCanonicalDecl()) + return; + + auto TypeArgs = TargetObjCPtr->getTypeArgs(); + if (TypeArgs.size() != 2) + return; + + QualType TargetKeyType = TypeArgs[0]; + QualType TargetObjectType = TypeArgs[1]; + for (unsigned I = 0, N = DictionaryLiteral->getNumElements(); I != N; ++I) { + auto Element = DictionaryLiteral->getKeyValueElement(I); + checkObjCCollectionLiteralElement(S, TargetKeyType, Element.Key, 1); + checkObjCCollectionLiteralElement(S, TargetObjectType, Element.Value, 2); + } +} + void CheckImplicitConversion(Sema &S, Expr *E, QualType T, SourceLocation CC, bool *ICContext = nullptr) { if (E->isTypeDependent() || E->isValueDependent()) return; @@ -6923,6 +7018,13 @@ void CheckImplicitConversion(Sema &S, Expr *E, QualType T, } } + // Check implicit casts from Objective-C collection literals to specialized + // collection types, e.g., NSArray *. + if (auto *ArrayLiteral = dyn_cast(E)) + checkObjCArrayLiteral(S, QualType(Target, 0), ArrayLiteral); + else if (auto *DictionaryLiteral = dyn_cast(E)) + checkObjCDictionaryLiteral(S, QualType(Target, 0), DictionaryLiteral); + // Strip vector types. if (isa(Source)) { if (!isa(Target)) { diff --git a/test/SemaObjC/parameterized_classes_collection_literal.m b/test/SemaObjC/parameterized_classes_collection_literal.m new file mode 100644 index 0000000000..472746e09d --- /dev/null +++ b/test/SemaObjC/parameterized_classes_collection_literal.m @@ -0,0 +1,52 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +// RUN: %clang_cc1 -x objective-c++ -fsyntax-only -verify %s + +#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64 +typedef unsigned long NSUInteger; +#else +typedef unsigned int NSUInteger; +#endif + +@protocol NSObject +@end + +@protocol NSCopying +@end + +__attribute__((objc_root_class)) +@interface NSObject +@end + +@interface NSString : NSObject +@end + +@interface NSNumber : NSObject ++ (NSNumber *)numberWithInt:(int)value; +@end + +@interface NSArray : NSObject ++ (instancetype)arrayWithObjects:(const T [])objects count:(NSUInteger)cnt; +@end + +@interface NSDictionary : NSObject ++ (instancetype)dictionaryWithObjects:(const V [])objects forKeys:(const K [])keys count:(NSUInteger)cnt; +@end + +void testArrayLiteral(void) { + NSArray *array1 = @[@"hello", + @1, // expected-warning{{of type 'NSNumber *' is not compatible with array element type 'NSString *'}} + @"world", + @[@1, @2]]; // expected-warning{{of type 'NSArray *' is not compatible with array element type 'NSString *'}} + + NSArray *> *array2 = @[@[@"hello", @"world"], + @"blah", // expected-warning{{object of type 'NSString *' is not compatible with array element type 'NSArray *'}} + @[@1]]; // expected-warning{{object of type 'NSNumber *' is not compatible with array element type 'NSString *'}} +} + +void testDictionaryLiteral(void) { + NSDictionary *dict1 = @{ + @"hello" : @17, + @18 : @18, // expected-warning{{object of type 'NSNumber *' is not compatible with dictionary key type 'NSString *'}} + @"world" : @"blah" // expected-warning{{object of type 'NSString *' is not compatible with dictionary value type 'NSNumber *'}} + }; +}