Skip to content

Commit 873e279

Browse files
committed
[SemaObjC] Add a warning for dictionary literals with duplicate keys
Duplicate keys in a literal break NSDictionary's invariants. rdar://50454461A Differential revision: https://reviews.llvm.org/D78660
1 parent 6f790f7 commit 873e279

File tree

3 files changed

+131
-6
lines changed

3 files changed

+131
-6
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2969,6 +2969,11 @@ def warn_objc_collection_literal_element : Warning<
29692969
"object of type %0 is not compatible with "
29702970
"%select{array element type|dictionary key type|dictionary value type}1 %2">,
29712971
InGroup<ObjCLiteralConversion>;
2972+
def warn_nsdictionary_duplicate_key : Warning<
2973+
"duplicate key in dictionary literal">,
2974+
InGroup<DiagGroup<"objc-dictionary-duplicate-keys">>;
2975+
def note_nsdictionary_duplicate_key_here : Note<
2976+
"previous equal key is here">;
29722977
def err_swift_param_attr_not_swiftcall : Error<
29732978
"'%0' parameter can only be used with swiftcall calling convention">;
29742979
def err_swift_indirect_result_not_first : Error<

clang/lib/Sema/SemaExprObjC.cpp

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,62 @@ ExprResult Sema::BuildObjCArrayLiteral(SourceRange SR, MultiExprArg Elements) {
894894
ArrayWithObjectsMethod, SR));
895895
}
896896

897+
/// Check for duplicate keys in an ObjC dictionary literal. For instance:
898+
/// NSDictionary *nd = @{ @"foo" : @"bar", @"foo" : @"baz" };
899+
static void
900+
CheckObjCDictionaryLiteralDuplicateKeys(Sema &S,
901+
ObjCDictionaryLiteral *Literal) {
902+
if (Literal->isValueDependent() || Literal->isTypeDependent())
903+
return;
904+
905+
// NSNumber has quite relaxed equality semantics (for instance, @YES is
906+
// considered equal to @1.0). For now, ignore floating points and just do a
907+
// bit-width and sign agnostic integer compare.
908+
struct APSIntCompare {
909+
bool operator()(const llvm::APSInt &LHS, const llvm::APSInt &RHS) const {
910+
return llvm::APSInt::compareValues(LHS, RHS) < 0;
911+
}
912+
};
913+
914+
llvm::DenseMap<StringRef, SourceLocation> StringKeys;
915+
std::map<llvm::APSInt, SourceLocation, APSIntCompare> IntegralKeys;
916+
917+
auto checkOneKey = [&](auto &Map, const auto &Key, SourceLocation Loc) {
918+
auto Pair = Map.insert({Key, Loc});
919+
if (!Pair.second) {
920+
S.Diag(Loc, diag::warn_nsdictionary_duplicate_key);
921+
S.Diag(Pair.first->second, diag::note_nsdictionary_duplicate_key_here);
922+
}
923+
};
924+
925+
for (unsigned Idx = 0, End = Literal->getNumElements(); Idx != End; ++Idx) {
926+
Expr *Key = Literal->getKeyValueElement(Idx).Key->IgnoreParenImpCasts();
927+
928+
if (auto *StrLit = dyn_cast<ObjCStringLiteral>(Key)) {
929+
StringRef Bytes = StrLit->getString()->getBytes();
930+
SourceLocation Loc = StrLit->getExprLoc();
931+
checkOneKey(StringKeys, Bytes, Loc);
932+
}
933+
934+
if (auto *BE = dyn_cast<ObjCBoxedExpr>(Key)) {
935+
Expr *Boxed = BE->getSubExpr();
936+
SourceLocation Loc = BE->getExprLoc();
937+
938+
// Check for @("foo").
939+
if (auto *Str = dyn_cast<StringLiteral>(Boxed->IgnoreParenImpCasts())) {
940+
checkOneKey(StringKeys, Str->getBytes(), Loc);
941+
continue;
942+
}
943+
944+
Expr::EvalResult Result;
945+
if (Boxed->EvaluateAsInt(Result, S.getASTContext(),
946+
Expr::SE_AllowSideEffects)) {
947+
checkOneKey(IntegralKeys, Result.Val.getInt(), Loc);
948+
}
949+
}
950+
}
951+
}
952+
897953
ExprResult Sema::BuildObjCDictionaryLiteral(SourceRange SR,
898954
MutableArrayRef<ObjCDictionaryElement> Elements) {
899955
SourceLocation Loc = SR.getBegin();
@@ -1061,12 +1117,14 @@ ExprResult Sema::BuildObjCDictionaryLiteral(SourceRange SR,
10611117
HasPackExpansions = true;
10621118
}
10631119

1064-
QualType Ty
1065-
= Context.getObjCObjectPointerType(
1066-
Context.getObjCInterfaceType(NSDictionaryDecl));
1067-
return MaybeBindToTemporary(ObjCDictionaryLiteral::Create(
1068-
Context, Elements, HasPackExpansions, Ty,
1069-
DictionaryWithObjectsMethod, SR));
1120+
QualType Ty = Context.getObjCObjectPointerType(
1121+
Context.getObjCInterfaceType(NSDictionaryDecl));
1122+
1123+
auto *Literal =
1124+
ObjCDictionaryLiteral::Create(Context, Elements, HasPackExpansions, Ty,
1125+
DictionaryWithObjectsMethod, SR);
1126+
CheckObjCDictionaryLiteralDuplicateKeys(*this, Literal);
1127+
return MaybeBindToTemporary(Literal);
10701128
}
10711129

10721130
ExprResult Sema::BuildObjCEncodeExpression(SourceLocation AtLoc,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// RUN: %clang_cc1 -Wno-objc-root-class %s -verify
2+
// RUN: %clang_cc1 -xobjective-c++ -Wno-objc-root-class %s -verify
3+
4+
#define YES __objc_yes
5+
#define NO __objc_no
6+
7+
@interface NSNumber
8+
+(instancetype)numberWithChar:(char)value;
9+
+(instancetype)numberWithInt:(int)value;
10+
+(instancetype)numberWithDouble:(double)value;
11+
+(instancetype)numberWithBool:(unsigned char)value;
12+
+(instancetype)numberWithUnsignedLong:(unsigned long)value;
13+
+(instancetype)numberWithLongLong:(long long) value;
14+
+(instancetype)numberWithUnsignedInt:(unsigned)value;
15+
@end
16+
17+
@interface NSString
18+
@end
19+
20+
@interface NSDictionary
21+
+ (instancetype)dictionaryWithObjects:(const id[])objects
22+
forKeys:(const id[])keys
23+
count:(unsigned long)cnt;
24+
@end
25+
26+
void test() {
27+
NSDictionary *t1 = @{
28+
@"foo" : @0, // expected-note 2 {{previous equal key is here}}
29+
@"foo" : @0, // expected-warning{{duplicate key in dictionary literal}}
30+
@("foo") : @0, // expected-warning{{duplicate key in dictionary literal}}
31+
@"foo\0" : @0,
32+
33+
@1 : @0, // expected-note + {{previous equal key is here}}
34+
@YES : @0, // expected-warning{{duplicate key in dictionary literal}}
35+
@'\1' : @0, // expected-warning{{duplicate key in dictionary literal}}
36+
@1 : @0, // expected-warning{{duplicate key in dictionary literal}}
37+
@1ul : @0, // expected-warning{{duplicate key in dictionary literal}}
38+
@1ll : @0, // expected-warning{{duplicate key in dictionary literal}}
39+
#ifdef __cplusplus
40+
@true : @0, // expected-warning{{duplicate key in dictionary literal}}
41+
#endif
42+
@1.0 : @0, // FIXME: should warn
43+
44+
@-1 : @0, // expected-note + {{previous equal key is here}}
45+
@4294967295u : @0, // no warning
46+
@-1ll : @0, // expected-warning{{duplicate key in dictionary literal}}
47+
@(NO-YES) : @0, // expected-warning{{duplicate key in dictionary literal}}
48+
};
49+
}
50+
51+
#ifdef __cplusplus
52+
template <class... Ts> void variadic(Ts... ts) {
53+
NSDictionary *nd = @{
54+
ts : @0 ...,
55+
@0 : ts ... // expected-warning 2 {{duplicate key in dictionary literal}} expected-note 2 {{previous equal key is here}}
56+
};
57+
}
58+
59+
void call_variadic() {
60+
variadic(@0, @1, @2); // expected-note {{in instantiation}}
61+
}
62+
#endif

0 commit comments

Comments
 (0)