Skip to content

Commit 3388825

Browse files
committed
Log a message when Obj-C -hash is invoked for a Swift object that is Equatable but not Hashable
This is worth telling people about: Since we're being asked for the hash value, that probably means someone is trying to put this object into some form of set or dictionary. But we don't know anything about the Equatable implementation, so the only valid hash value we can possibly return is a constant. And hash table implementations typically degrade into unsorted lists when provided constant hash values. Note: In order to avoid hammering the logs, we only emit the warning once for each class that hits this path.
1 parent 3e49b5c commit 3388825

File tree

2 files changed

+28
-9
lines changed

2 files changed

+28
-9
lines changed

stdlib/public/runtime/SwiftObject.mm

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -388,19 +388,28 @@ - (NSUInteger)hash {
388388

389389
// If a type is Equatable (but not Hashable), we
390390
// have to return something here that is compatible
391-
// with the `isEqual:` below. NSObject's default
392-
// of `(NSUInteger)self` won't work, so we instead
393-
// return a constant fallback value:
391+
// with the `isEqual:` below.
394392
auto equatableConformance =
395393
reinterpret_cast<const equatable_support::EquatableWitnessTable *>(
396394
swift_conformsToProtocolCommon(
397395
selfMetadata, &equatable_support::EquatableProtocolDescriptor));
398396
if (equatableConformance != nullptr) {
399-
const char *clsName = class_getName([self class]);
400-
warning(0,
401-
"Obj-C `-hash` invoked on a Swift object of type %s that is not Hashable; "
402-
"this can lead to severe performance problems",
403-
clsName);
397+
// Warn once per class about this
398+
auto selfClass = [self class];
399+
static Lazy<std::unordered_set<Class>> warned;
400+
static LazyMutex warnedLock;
401+
LazyMutex::ScopedLock guard(warnedLock);
402+
auto result = warned.get().insert(selfClass);
403+
auto inserted = std::get<1>(result);
404+
if (inserted) {
405+
const char *clsName = class_getName([self class]);
406+
warning(0,
407+
"Obj-C `-hash` method was invoked on a Swift object of type `%s` "
408+
"that is Equatable but not Hashable; "
409+
"this can lead to severe performance problems.\n",
410+
clsName);
411+
}
412+
// Constant value (yuck!) is the only choice here
404413
return (NSUInteger)1;
405414
}
406415

test/stdlib/SwiftObjectNSObject.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// RUN: %target-build-swift %s -g -I %S/Inputs/SwiftObjectNSObject/ -Xlinker %t/SwiftObjectNSObject.o -o %t/SwiftObjectNSObject
1717
// RUN: %target-codesign %t/SwiftObjectNSObject
1818
// RUN: %target-run %t/SwiftObjectNSObject 2> %t/log.txt
19+
// RUN: cat %t/log.txt 1>&2
1920
// RUN: %FileCheck %s < %t/log.txt
2021
// REQUIRES: executable_test
2122

@@ -115,11 +116,17 @@ func TestNonEquatableHash(_ e: AnyObject)
115116
TestSwiftObjectNSObjectDefaultHashValue(e)
116117
}
117118
118-
// This check is for NSLog() output from TestSwiftObjectNSObject().
119+
// Check NSLog() output from TestSwiftObjectNSObject().
120+
119121
// CHECK: c ##SwiftObjectNSObject.C##
120122
// CHECK-NEXT: d ##SwiftObjectNSObject.D##
121123
// CHECK-NEXT: S ##{{.*}}SwiftObject##
122124
125+
// Full message is longer, but this is the essential part...
126+
// CHECK-NEXT: Obj-C `-hash` {{.*}} type `SwiftObjectNSObject.E` {{.*}} Equatable but not Hashable
127+
// CHECK-NEXT: Obj-C `-hash` {{.*}} type `SwiftObjectNSObject.E1` {{.*}} Equatable but not Hashable
128+
// CHECK-NEXT: Obj-C `-hash` {{.*}} type `SwiftObjectNSObject.E2` {{.*}} Equatable but not Hashable
129+
123130
// Temporarily disable this test on older OSes until we have time to
124131
// look into why it's failing there. rdar://problem/47870743
125132
if #available(OSX 10.12, iOS 10.0, *) {
@@ -185,4 +192,7 @@ if #available(OSX 10.12, iOS 10.0, *) {
185192
fputs("c ##SwiftObjectNSObject.C##\n", stderr)
186193
fputs("d ##SwiftObjectNSObject.D##\n", stderr)
187194
fputs("S ##Swift._SwiftObject##\n", stderr)
195+
fputs("Obj-C `-hash` ... type `SwiftObjectNSObject.E` ... Equatable but not Hashable", stderr)
196+
fputs("Obj-C `-hash` ... type `SwiftObjectNSObject.E1` ... Equatable but not Hashable", stderr)
197+
fputs("Obj-C `-hash` ... type `SwiftObjectNSObject.E2` ... Equatable but not Hashable", stderr)
188198
}

0 commit comments

Comments
 (0)