Skip to content

Commit 9f6290c

Browse files
committed
Fix UB when hashing some doubles in 32bits.
In 32 bits platforms, for some doubles the hash result could be incorrectly zero (or whatever undefined behaviour happens to be). The problem happens when the non fractional part of the double is high and the fractional part is between 0 and 0.5 and high enough to make the sum of both not fit into 32 bits. In that case, the use of += promotes result to double to add it to the fractional double, but when it gets converted back into CFHashCode, since it is outside the 32 bit range, it might end up as zero. Casting fractional to CFHashCode avoids the hidden promotion, and should not fail, since fractional is at most ULONG_MAX/2. The problem doesn't exist in 64 bits, since CFHashCode is 64 bits wide there.
1 parent 2b18f4d commit 9f6290c

File tree

2 files changed

+24
-1
lines changed

2 files changed

+24
-1
lines changed

CoreFoundation/Base.subproj/ForFoundationOnly.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ CF_INLINE CFHashCode _CFHashDouble(const double d) {
542542
result += -((CFHashCode)(fabs(fractional)));
543543
} else if (fractional > 0) {
544544
// Caveat: the > 0 is incredibly important [28612173]
545-
result += fractional;
545+
result += (CFHashCode)fractional;
546546
}
547547
return result;
548548
}

TestFoundation/TestNSNumber.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class TestNSNumber : XCTestCase {
3636
("test_stringValue", test_stringValue),
3737
("test_Equals", test_Equals),
3838
("test_boolValue", test_boolValue),
39+
("test_hash", test_hash),
3940
]
4041
}
4142

@@ -1262,4 +1263,26 @@ class TestNSNumber : XCTestCase {
12621263
XCTAssertEqual(NSNumber(value: Int.min + 1).boolValue, true)
12631264
XCTAssertEqual(NSNumber(value: Int(-1)).boolValue, true)
12641265
}
1266+
1267+
func test_hash() {
1268+
// A zero double hashes as zero.
1269+
XCTAssertEqual(NSNumber(value: 0 as Double).hash, 0)
1270+
1271+
// A positive double without fractional part should hash the same as the
1272+
// equivalent 64 bit number.
1273+
XCTAssertEqual(NSNumber(value: 123456 as Double).hash, NSNumber(value: 123456 as Int64).hash)
1274+
1275+
// A negative double without fractional part should hash the same as the
1276+
// equivalent 64 bit number.
1277+
XCTAssertEqual(NSNumber(value: -123456 as Double).hash, NSNumber(value: -123456 as Int64).hash)
1278+
1279+
#if arch(i386) || arch(arm)
1280+
// This test used to fail in 32 bit platforms.
1281+
XCTAssertNotEqual(NSNumber(value: 551048378.24883795 as Double).hash, 0)
1282+
1283+
// Some hashes are correctly zero, though. Like the following which
1284+
// was found by trial and error.
1285+
XCTAssertEqual(NSNumber(value: 1.3819660135 as Double).hash, 0)
1286+
#endif
1287+
}
12651288
}

0 commit comments

Comments
 (0)