diff options
author | Gil <mcg@google.com> | 2018-01-19 12:27:11 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-19 12:27:11 -0800 |
commit | 39d8252300015c26f1932cff42032613fdb36a09 (patch) | |
tree | 93bb3ab7c476289c4afe6cf91773a60b6f53a94d | |
parent | 9f7c094f9f00a6efc0107071f109ef1bc4d7357d (diff) |
Port comparison to C++ (#678)
This reimplements our comparison functions as C++ Comparators and then provides compatibility shims for interoperating with existing Objective-C usage.
A few specialized comparators aren't suitable for porting but only have a single usage (e.g. CompareBytes for comparing NSData * instances). In these cases I've moved them into the caller.
* Use int32_t for typeof(ID) in FSTDocumentReference
* Migrate callers of FSTComparison.h to Objective-C++
* Port comparison to C++
* Migrate usages of FSTComparison.h to C++ equivalents
* Remove FSTComparison
17 files changed, 614 insertions, 420 deletions
diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 538758d..7e922b6 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAA1FAA0C320085E60A /* string_util_test.cc */; }; 54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; 548DB927200D590300E00ABC /* assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB926200D590300E00ABC /* assert_test.cc */; }; + 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; 5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; 5491BC731FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; 54C2294F1FECABAE007D065B /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; }; @@ -148,7 +149,6 @@ DE51B1FD1F0D492C0013853F /* FSTSpecTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1991F0D48AC0013853F /* FSTSpecTests.m */; }; DE51B1FE1F0D492C0013853F /* FSTSyncEngineTestDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B19B1F0D48AC0013853F /* FSTSyncEngineTestDriver.m */; }; DE51B1FF1F0D493A0013853F /* FSTAssertTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1861F0D48AC0013853F /* FSTAssertTests.m */; }; - DE51B2001F0D493A0013853F /* FSTComparisonTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1871F0D48AC0013853F /* FSTComparisonTests.m */; }; DE51B2011F0D493E0013853F /* FSTHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1891F0D48AC0013853F /* FSTHelpers.m */; }; F104BBD69BC3F0796E3A77C1 /* Pods_Firestore_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */; }; /* End PBXBuildFile section */ @@ -204,6 +204,7 @@ 54764FAA1FAA0C320085E60A /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_util_test.cc; path = ../../Port/string_util_test.cc; sourceTree = "<group>"; }; 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FSTGoogleTestTests.mm; path = GoogleTest/FSTGoogleTestTests.mm; sourceTree = "<group>"; }; 548DB926200D590300E00ABC /* assert_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = assert_test.cc; path = ../../core/test/firebase/firestore/util/assert_test.cc; sourceTree = "<group>"; }; + 548DB928200D59F600E00ABC /* comparison_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = comparison_test.cc; path = ../../core/test/firebase/firestore/util/comparison_test.cc; sourceTree = "<group>"; }; 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTIntegrationTestCase.mm; sourceTree = "<group>"; }; 54C2294E1FECABAE007D065B /* log_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = log_test.cc; path = ../../core/test/firebase/firestore/util/log_test.cc; sourceTree = "<group>"; }; 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = collection_spec_test.json; sourceTree = "<group>"; }; @@ -308,7 +309,6 @@ DE51B1821F0D48AC0013853F /* FSTPathTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTPathTests.m; sourceTree = "<group>"; }; DE51B1841F0D48AC0013853F /* FIRGeoPointTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRGeoPointTests.m; sourceTree = "<group>"; }; DE51B1861F0D48AC0013853F /* FSTAssertTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTAssertTests.m; sourceTree = "<group>"; }; - DE51B1871F0D48AC0013853F /* FSTComparisonTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTComparisonTests.m; sourceTree = "<group>"; }; DE51B1881F0D48AC0013853F /* FSTHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTHelpers.h; sourceTree = "<group>"; }; DE51B1891F0D48AC0013853F /* FSTHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTHelpers.m; sourceTree = "<group>"; }; DE51B1941F0D48AC0013853F /* FSTLevelDBSpecTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTLevelDBSpecTests.m; sourceTree = "<group>"; }; @@ -400,6 +400,7 @@ children = ( 548DB926200D590300E00ABC /* assert_test.cc */, 54740A521FC913E500713A1A /* autoid_test.cc */, + 548DB928200D59F600E00ABC /* comparison_test.cc */, 54C2294E1FECABAE007D065B /* log_test.cc */, 54740A531FC913E500713A1A /* secure_random_test.cc */, 5436F32320008FAD006E51E3 /* string_printf_test.cc */, @@ -646,7 +647,6 @@ 54E9281E1F33950B00C1953E /* FSTIntegrationTestCase.h */, 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */, DE51B1861F0D48AC0013853F /* FSTAssertTests.m */, - DE51B1871F0D48AC0013853F /* FSTComparisonTests.m */, DE51B1881F0D48AC0013853F /* FSTHelpers.h */, DE51B1891F0D48AC0013853F /* FSTHelpers.m */, 54E9282A1F339CAD00C1953E /* XCTestCase+Await.h */, @@ -1210,7 +1210,6 @@ DE2EF0881F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m in Sources */, DE51B1FD1F0D492C0013853F /* FSTSpecTests.m in Sources */, ABAEEF4F1FD5F8B100C966CB /* FIRQueryTests.m in Sources */, - DE51B2001F0D493A0013853F /* FSTComparisonTests.m in Sources */, ABF341051FE860CA00C48322 /* FSTAPIHelpers.m in Sources */, DE51B1CC1F0D48C00013853F /* FIRGeoPointTests.m in Sources */, DE51B1E11F0D490D0013853F /* FSTMemoryRemoteDocumentCacheTests.m in Sources */, @@ -1276,6 +1275,7 @@ DE51B1E41F0D490D0013853F /* FSTQueryCacheTests.m in Sources */, DE51B1CD1F0D48CD0013853F /* FSTDatabaseInfoTests.m in Sources */, AB382F7E1FE03059007CA955 /* FIRFieldPathTests.m in Sources */, + 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */, DE51B1F21F0D49140013853F /* FSTPathTests.m in Sources */, AB99452C1FE3018D00DFC1E6 /* FIRQuerySnapshotTests.m in Sources */, 54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */, diff --git a/Firestore/Example/Tests/Util/FSTComparisonTests.m b/Firestore/Example/Tests/Util/FSTComparisonTests.m deleted file mode 100644 index 5632e64..0000000 --- a/Firestore/Example/Tests/Util/FSTComparisonTests.m +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Firestore/Source/Util/FSTComparison.h" - -#import <XCTest/XCTest.h> - -union DoubleBits { - double d; - uint64_t bits; -}; - -#define ASSERT_BIT_EQUALS(expected, actual) \ - do { \ - union DoubleBits expectedBits = {.d = expected}; \ - union DoubleBits actualBits = {.d = expected}; \ - if (expectedBits.bits != actualBits.bits) { \ - XCTFail(@"Expected <%f> to compare equal to <%f> with bits <%llX> equal to <%llX>", actual, \ - expected, actualBits.bits, expectedBits.bits); \ - } \ - } while (0); - -#define ASSERT_ORDERED_SAME(doubleValue, longValue) \ - do { \ - NSComparisonResult result = FSTCompareMixed(doubleValue, longValue); \ - if (result != NSOrderedSame) { \ - XCTFail(@"Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \ - } \ - } while (0); - -#define ASSERT_ORDERED_DESCENDING(doubleValue, longValue) \ - do { \ - NSComparisonResult result = FSTCompareMixed(doubleValue, longValue); \ - if (result != NSOrderedDescending) { \ - XCTFail(@"Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \ - } \ - } while (0); - -#define ASSERT_ORDERED_ASCENDING(doubleValue, longValue) \ - do { \ - NSComparisonResult result = FSTCompareMixed(doubleValue, longValue); \ - if (result != NSOrderedAscending) { \ - XCTFail(@"Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \ - } \ - } while (0); - -@interface FSTComparisonTests : XCTestCase -@end - -@implementation FSTComparisonTests - -- (void)testMixedComparison { - // Infinities - ASSERT_ORDERED_ASCENDING(-INFINITY, LLONG_MIN); - ASSERT_ORDERED_ASCENDING(-INFINITY, LLONG_MAX); - ASSERT_ORDERED_ASCENDING(-INFINITY, 0LL); - - ASSERT_ORDERED_DESCENDING(INFINITY, LLONG_MIN); - ASSERT_ORDERED_DESCENDING(INFINITY, LLONG_MAX); - ASSERT_ORDERED_DESCENDING(INFINITY, 0LL); - - // NaN - ASSERT_ORDERED_ASCENDING(NAN, LLONG_MIN); - ASSERT_ORDERED_ASCENDING(NAN, LLONG_MAX); - ASSERT_ORDERED_ASCENDING(NAN, 0LL); - - // Large values (note DBL_MIN is positive and near zero). - ASSERT_ORDERED_ASCENDING(-DBL_MAX, LLONG_MIN); - - // Tests around LLONG_MIN - ASSERT_BIT_EQUALS((double)LLONG_MIN, -0x1.0p63); - ASSERT_ORDERED_SAME(-0x1.0p63, LLONG_MIN); - ASSERT_ORDERED_ASCENDING(-0x1.0p63, LLONG_MIN + 1); - - XCTAssertLessThan(-0x1.0000000000001p63, -0x1.0p63); - ASSERT_ORDERED_ASCENDING(-0x1.0000000000001p63, LLONG_MIN); - ASSERT_ORDERED_DESCENDING(-0x1.FFFFFFFFFFFFFp62, LLONG_MIN); - - // Tests around LLONG_MAX - // Note LLONG_MAX cannot be exactly represented by a double, so the system rounds it to the - // nearest double, which is 2^63. This number, in turn is larger than the maximum representable - // as a long. - ASSERT_BIT_EQUALS(0x1.0p63, (double)LLONG_MAX); - ASSERT_ORDERED_DESCENDING(0x1.0p63, LLONG_MAX); - - // The largest value with an exactly long representation - XCTAssertEqual((long)0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC00LL); - ASSERT_ORDERED_SAME(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC00LL); - - ASSERT_ORDERED_DESCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFB00LL); - ASSERT_ORDERED_DESCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFBFFLL); - ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC01LL); - ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFD00LL); - - ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFEp62, 0x7FFFFFFFFFFFFC00LL); - - // Tests around MAX_SAFE_INTEGER - ASSERT_ORDERED_SAME(0x1.FFFFFFFFFFFFFp52, 0x1FFFFFFFFFFFFFLL); - ASSERT_ORDERED_DESCENDING(0x1.FFFFFFFFFFFFFp52, 0x1FFFFFFFFFFFFELL); - ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFEp52, 0x1FFFFFFFFFFFFFLL); - ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFFp52, 0x20000000000000LL); - - // Tests around MIN_SAFE_INTEGER - ASSERT_ORDERED_SAME(-0x1.FFFFFFFFFFFFFp52, -0x1FFFFFFFFFFFFFLL); - ASSERT_ORDERED_ASCENDING(-0x1.FFFFFFFFFFFFFp52, -0x1FFFFFFFFFFFFELL); - ASSERT_ORDERED_DESCENDING(-0x1.FFFFFFFFFFFFEp52, -0x1FFFFFFFFFFFFFLL); - ASSERT_ORDERED_DESCENDING(-0x1.FFFFFFFFFFFFFp52, -0x20000000000000LL); - - // Tests around zero. - ASSERT_ORDERED_SAME(-0.0, 0LL); - ASSERT_ORDERED_SAME(0.0, 0LL); - - // The smallest representable positive value should be greater than zero - ASSERT_ORDERED_DESCENDING(DBL_MIN, 0LL); - ASSERT_ORDERED_ASCENDING(-DBL_MIN, 0LL); - - // Note that 0x1.0p-1074 is a hex floating point literal representing the minimum subnormal - // number: <https://en.wikipedia.org/wiki/Denormal_number>. - double minSubNormal = 0x1.0p-1074; - ASSERT_ORDERED_DESCENDING(minSubNormal, 0LL); - ASSERT_ORDERED_ASCENDING(-minSubNormal, 0LL); - - // Other sanity checks - ASSERT_ORDERED_ASCENDING(0.5, 1LL); - ASSERT_ORDERED_DESCENDING(0.5, 0LL); - ASSERT_ORDERED_ASCENDING(1.5, 2LL); - ASSERT_ORDERED_DESCENDING(1.5, 1LL); -} - -@end diff --git a/Firestore/Source/API/FIRGeoPoint.m b/Firestore/Source/API/FIRGeoPoint.mm index 72e9e7d..8d89633 100644 --- a/Firestore/Source/API/FIRGeoPoint.m +++ b/Firestore/Source/API/FIRGeoPoint.mm @@ -16,9 +16,14 @@ #import "Firestore/Source/API/FIRGeoPoint+Internal.h" -#import "Firestore/Source/Util/FSTComparison.h" +#import "Firestore/core/src/firebase/firestore/util/comparison.h" + #import "Firestore/Source/Util/FSTUsageValidation.h" +using firebase::firestore::util::DoubleBitwiseEquals; +using firebase::firestore::util::DoubleBitwiseHash; +using firebase::firestore::util::WrapCompare; + NS_ASSUME_NONNULL_BEGIN @implementation FIRGeoPoint @@ -45,11 +50,11 @@ NS_ASSUME_NONNULL_BEGIN } - (NSComparisonResult)compare:(FIRGeoPoint *)other { - NSComparisonResult result = FSTCompareDoubles(self.latitude, other.latitude); + NSComparisonResult result = WrapCompare<double>(self.latitude, other.latitude); if (result != NSOrderedSame) { return result; } else { - return FSTCompareDoubles(self.longitude, other.longitude); + return WrapCompare<double>(self.longitude, other.longitude); } } @@ -67,12 +72,12 @@ NS_ASSUME_NONNULL_BEGIN return NO; } FIRGeoPoint *otherGeoPoint = (FIRGeoPoint *)other; - return FSTDoubleBitwiseEquals(self.latitude, otherGeoPoint.latitude) && - FSTDoubleBitwiseEquals(self.longitude, otherGeoPoint.longitude); + return DoubleBitwiseEquals(self.latitude, otherGeoPoint.latitude) && + DoubleBitwiseEquals(self.longitude, otherGeoPoint.longitude); } - (NSUInteger)hash { - return 31 * FSTDoubleBitwiseHash(self.latitude) + FSTDoubleBitwiseHash(self.longitude); + return 31 * DoubleBitwiseHash(self.latitude) + DoubleBitwiseHash(self.longitude); } /** Implements NSCopying without actually copying because geopoints are immutable. */ diff --git a/Firestore/Source/Core/FSTTimestamp.m b/Firestore/Source/Core/FSTTimestamp.mm index 6d9e314..d2b492a 100644 --- a/Firestore/Source/Core/FSTTimestamp.m +++ b/Firestore/Source/Core/FSTTimestamp.mm @@ -16,8 +16,11 @@ #import "Firestore/Source/Core/FSTTimestamp.h" +#include "Firestore/core/src/firebase/firestore/util/comparison.h" + #import "Firestore/Source/Util/FSTAssert.h" -#import "Firestore/Source/Util/FSTComparison.h" + +using firebase::firestore::util::WrapCompare; NS_ASSUME_NONNULL_BEGIN @@ -110,11 +113,11 @@ static const int kNanosPerSecond = 1000000000; } - (NSComparisonResult)compare:(FSTTimestamp *)other { - NSComparisonResult result = FSTCompareInt64s(self.seconds, other.seconds); + NSComparisonResult result = WrapCompare<int64_t>(self.seconds, other.seconds); if (result != NSOrderedSame) { return result; } - return FSTCompareInt32s(self.nanos, other.nanos); + return WrapCompare<int32_t>(self.nanos, other.nanos); } @end diff --git a/Firestore/Source/Local/FSTDocumentReference.h b/Firestore/Source/Local/FSTDocumentReference.h index eff60e4..04b8416 100644 --- a/Firestore/Source/Local/FSTDocumentReference.h +++ b/Firestore/Source/Local/FSTDocumentReference.h @@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN @interface FSTDocumentReference : NSObject <NSCopying> /** Initializes the document reference with the given key and ID. */ -- (instancetype)initWithKey:(FSTDocumentKey *)key ID:(int)ID NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithKey:(FSTDocumentKey *)key ID:(int32_t)ID NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; @@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN * The targetID of a referring target or the batchID of a referring mutation batch. (Which this * is depends upon which FSTReferenceSet this reference is a part of.) */ -@property(nonatomic, assign, readonly) int ID; +@property(nonatomic, assign, readonly) int32_t ID; @end diff --git a/Firestore/Source/Local/FSTDocumentReference.m b/Firestore/Source/Local/FSTDocumentReference.mm index 25a5935..4310baa 100644 --- a/Firestore/Source/Local/FSTDocumentReference.m +++ b/Firestore/Source/Local/FSTDocumentReference.mm @@ -16,14 +16,17 @@ #import "Firestore/Source/Local/FSTDocumentReference.h" +#include "Firestore/core/src/firebase/firestore/util/comparison.h" + #import "Firestore/Source/Model/FSTDocumentKey.h" -#import "Firestore/Source/Util/FSTComparison.h" + +using firebase::firestore::util::WrapCompare; NS_ASSUME_NONNULL_BEGIN @implementation FSTDocumentReference -- (instancetype)initWithKey:(FSTDocumentKey *)key ID:(int)ID { +- (instancetype)initWithKey:(FSTDocumentKey *)key ID:(int32_t)ID { self = [super init]; if (self) { _key = key; @@ -67,13 +70,13 @@ const NSComparator FSTDocumentReferenceComparatorByKey = if (result != NSOrderedSame) { return result; } - return FSTCompareInts(left.ID, right.ID); + return WrapCompare<int32_t>(left.ID, right.ID); }; /** Sorts document references by ID then key. */ const NSComparator FSTDocumentReferenceComparatorByID = ^NSComparisonResult(FSTDocumentReference *left, FSTDocumentReference *right) { - NSComparisonResult result = FSTCompareInts(left.ID, right.ID); + NSComparisonResult result = WrapCompare<int32_t>(left.ID, right.ID); if (result != NSOrderedSame) { return result; } diff --git a/Firestore/Source/Local/FSTMemoryMutationQueue.m b/Firestore/Source/Local/FSTMemoryMutationQueue.mm index b155264..702f614 100644 --- a/Firestore/Source/Local/FSTMemoryMutationQueue.m +++ b/Firestore/Source/Local/FSTMemoryMutationQueue.mm @@ -23,10 +23,13 @@ #import "Firestore/Source/Model/FSTMutationBatch.h" #import "Firestore/Source/Model/FSTPath.h" #import "Firestore/Source/Util/FSTAssert.h" -#import "Firestore/Source/Util/FSTComparison.h" NS_ASSUME_NONNULL_BEGIN +static const NSComparator NumberComparator = ^NSComparisonResult(NSNumber *left, NSNumber *right) { + return [left compare:right]; +}; + @interface FSTMemoryMutationQueue () /** @@ -260,7 +263,7 @@ NS_ASSUME_NONNULL_BEGIN // Find unique batchIDs referenced by all documents potentially matching the query. __block FSTImmutableSortedSet<NSNumber *> *uniqueBatchIDs = - [FSTImmutableSortedSet setWithComparator:FSTNumberComparator]; + [FSTImmutableSortedSet setWithComparator:NumberComparator]; FSTDocumentReferenceBlock block = ^(FSTDocumentReference *reference, BOOL *stop) { FSTResourcePath *rowKeyPath = reference.key.path; if (![prefix isPrefixOfPath:rowKeyPath]) { diff --git a/Firestore/Source/Model/FSTFieldValue.m b/Firestore/Source/Model/FSTFieldValue.mm index a6326a7..8ffc98e 100644 --- a/Firestore/Source/Model/FSTFieldValue.m +++ b/Firestore/Source/Model/FSTFieldValue.mm @@ -16,6 +16,9 @@ #import "Firestore/Source/Model/FSTFieldValue.h" +#include "Firestore/core/src/firebase/firestore/util/comparison.h" +#include "Firestore/core/src/firebase/firestore/util/string_apple.h" + #import "Firestore/Source/API/FIRGeoPoint+Internal.h" #import "Firestore/Source/API/FIRSnapshotOptions+Internal.h" #import "Firestore/Source/Core/FSTTimestamp.h" @@ -24,7 +27,14 @@ #import "Firestore/Source/Model/FSTPath.h" #import "Firestore/Source/Util/FSTAssert.h" #import "Firestore/Source/Util/FSTClasses.h" -#import "Firestore/Source/Util/FSTComparison.h" + +using firebase::firestore::util::Comparator; +using firebase::firestore::util::CompareMixedNumber; +using firebase::firestore::util::DoubleBitwiseEquals; +using firebase::firestore::util::DoubleBitwiseHash; +using firebase::firestore::util::MakeStringView; +using firebase::firestore::util::ReverseOrder; +using firebase::firestore::util::WrapCompare; NS_ASSUME_NONNULL_BEGIN @@ -208,7 +218,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSComparisonResult)compare:(FSTFieldValue *)other { if ([other isKindOfClass:[FSTBooleanValue class]]) { - return FSTCompareBools(self.internalValue, ((FSTBooleanValue *)other).internalValue); + return WrapCompare<bool>(self.internalValue, ((FSTBooleanValue *)other).internalValue); } else { return [self defaultCompare:other]; } @@ -231,19 +241,22 @@ NS_ASSUME_NONNULL_BEGIN if ([self isKindOfClass:[FSTDoubleValue class]]) { double thisDouble = ((FSTDoubleValue *)self).internalValue; if ([other isKindOfClass:[FSTDoubleValue class]]) { - return FSTCompareDoubles(thisDouble, ((FSTDoubleValue *)other).internalValue); + return WrapCompare(thisDouble, ((FSTDoubleValue *)other).internalValue); } else { FSTAssert([other isKindOfClass:[FSTIntegerValue class]], @"Unknown number value: %@", other); - return FSTCompareMixed(thisDouble, ((FSTIntegerValue *)other).internalValue); + auto result = CompareMixedNumber(thisDouble, ((FSTIntegerValue *)other).internalValue); + return static_cast<NSComparisonResult>(result); } } else { int64_t thisInt = ((FSTIntegerValue *)self).internalValue; if ([other isKindOfClass:[FSTIntegerValue class]]) { - return FSTCompareInt64s(thisInt, ((FSTIntegerValue *)other).internalValue); + return WrapCompare(thisInt, ((FSTIntegerValue *)other).internalValue); } else { FSTAssert([other isKindOfClass:[FSTDoubleValue class]], @"Unknown number value: %@", other); - return -1 * FSTCompareMixed(((FSTDoubleValue *)other).internalValue, thisInt); + double otherDouble = ((FSTDoubleValue *)other).internalValue; + auto result = ReverseOrder(CompareMixedNumber(otherDouble, thisInt)); + return static_cast<NSComparisonResult>(result); } } } @@ -334,11 +347,11 @@ NS_ASSUME_NONNULL_BEGIN // NOTE: isEqual: should compare NaN equal to itself and -0.0 not equal to 0.0. return [other isKindOfClass:[FSTDoubleValue class]] && - FSTDoubleBitwiseEquals(self.internalValue, ((FSTDoubleValue *)other).internalValue); + DoubleBitwiseEquals(self.internalValue, ((FSTDoubleValue *)other).internalValue); } - (NSUInteger)hash { - return FSTDoubleBitwiseHash(self.internalValue); + return DoubleBitwiseHash(self.internalValue); } // NOTE: compare: is implemented in NumberValue. @@ -347,6 +360,17 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTStringValue +/** + * Specialization of Comparator for NSStrings. + */ +template <> +struct Comparator<NSString *> { + bool operator()(NSString *left, NSString *right) const { + Comparator<absl::string_view> lessThan; + return lessThan(MakeStringView(left), MakeStringView(right)); + } +}; + @interface FSTStringValue () @property(nonatomic, copy, readonly) NSString *internalValue; @end @@ -385,7 +409,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSComparisonResult)compare:(FSTFieldValue *)other { if ([other isKindOfClass:[FSTStringValue class]]) { - return FSTCompareStrings(self.internalValue, ((FSTStringValue *)other).internalValue); + return WrapCompare(self.internalValue, ((FSTStringValue *)other).internalValue); } else { return [self defaultCompare:other]; } @@ -556,6 +580,22 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTBlobValue +static NSComparisonResult CompareBytes(NSData *left, NSData *right) { + NSUInteger minLength = MIN(left.length, right.length); + int result = memcmp(left.bytes, right.bytes, minLength); + if (result < 0) { + return NSOrderedAscending; + } else if (result > 0) { + return NSOrderedDescending; + } else if (left.length < right.length) { + return NSOrderedAscending; + } else if (left.length > right.length) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } +} + @interface FSTBlobValue () @property(nonatomic, copy, readonly) NSData *internalValue; @end @@ -594,7 +634,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSComparisonResult)compare:(FSTFieldValue *)other { if ([other isKindOfClass:[FSTBlobValue class]]) { - return FSTCompareBytes(self.internalValue, ((FSTBlobValue *)other).internalValue); + return CompareBytes(self.internalValue, ((FSTBlobValue *)other).internalValue); } else { return [self defaultCompare:other]; } @@ -664,6 +704,10 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTObjectValue +static const NSComparator StringComparator = ^NSComparisonResult(NSString *left, NSString *right) { + return WrapCompare(left, right); +}; + @interface FSTObjectValue () @property(nonatomic, strong, readonly) FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *internalValue; @@ -677,7 +721,7 @@ NS_ASSUME_NONNULL_BEGIN dispatch_once(&onceToken, ^{ FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *empty = - [FSTImmutableSortedDictionary dictionaryWithComparator:FSTStringComparator]; + [FSTImmutableSortedDictionary dictionaryWithComparator:StringComparator]; sharedEmptyInstance = [[FSTObjectValue alloc] initWithImmutableDictionary:empty]; }); return sharedEmptyInstance; @@ -694,7 +738,7 @@ NS_ASSUME_NONNULL_BEGIN - (id)initWithDictionary:(NSDictionary<NSString *, FSTFieldValue *> *)value { FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *dictionary = - [FSTImmutableSortedDictionary dictionaryWithDictionary:value comparator:FSTStringComparator]; + [FSTImmutableSortedDictionary dictionaryWithDictionary:value comparator:StringComparator]; return [self initWithImmutableDictionary:dictionary]; } @@ -748,7 +792,7 @@ NS_ASSUME_NONNULL_BEGIN key2 = [enumerator2 nextObject]; } // Only equal if both enumerators are exhausted. - return FSTCompareBools(key1 != nil, key2 != nil); + return WrapCompare(key1 != nil, key2 != nil); } else { return [self defaultCompare:other]; } @@ -876,7 +920,7 @@ NS_ASSUME_NONNULL_BEGIN return cmp; } } - return FSTCompareUIntegers(selfArray.count, otherArray.count); + return WrapCompare<int64_t>(selfArray.count, otherArray.count); } else { return [self defaultCompare:other]; } diff --git a/Firestore/Source/Util/FSTComparison.h b/Firestore/Source/Util/FSTComparison.h deleted file mode 100644 index e6e57e6..0000000 --- a/Firestore/Source/Util/FSTComparison.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import <Foundation/Foundation.h> - -NS_ASSUME_NONNULL_BEGIN - -/** Compares two NSStrings. */ -NSComparisonResult FSTCompareStrings(NSString *left, NSString *right); - -/** Compares two BOOLs. */ -NSComparisonResult FSTCompareBools(BOOL left, BOOL right); - -/** Compares two integers. */ -NSComparisonResult FSTCompareInts(int left, int right); - -/** Compares two int32_t. */ -NSComparisonResult FSTCompareInt32s(int32_t left, int32_t right); - -/** Compares two int64_t. */ -NSComparisonResult FSTCompareInt64s(int64_t left, int64_t right); - -/** Compares two NSUIntegers. */ -NSComparisonResult FSTCompareUIntegers(NSUInteger left, NSUInteger right); - -/** Compares two doubles (using Firestore semantics for NaN). */ -NSComparisonResult FSTCompareDoubles(double left, double right); - -/** Compares a double and an int64_t. */ -NSComparisonResult FSTCompareMixed(double doubleValue, int64_t longValue); - -/** Compare two NSData byte sequences. */ -NSComparisonResult FSTCompareBytes(NSData *left, NSData *right); - -/** A simple NSComparator for comparing NSNumber instances. */ -extern const NSComparator FSTNumberComparator; - -/** A simple NSComparator for comparing NSString instances. */ -extern const NSComparator FSTStringComparator; - -/** - * Compares the bitwise representation of two doubles, but normalizes NaN values. This is - * similar to what the backend and android clients do, including comparing -0.0 as not equal to 0.0. - */ -BOOL FSTDoubleBitwiseEquals(double left, double right); - -/** - * Computes a bitwise hash of a double, but normalizes NaN values, suitable for use when using - * FSTDoublesAreBitwiseEqual for equality. - */ -NSUInteger FSTDoubleBitwiseHash(double d); - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Util/FSTComparison.m b/Firestore/Source/Util/FSTComparison.m deleted file mode 100644 index 9c5c3eb..0000000 --- a/Firestore/Source/Util/FSTComparison.m +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Firestore/Source/Util/FSTComparison.h" - -NS_ASSUME_NONNULL_BEGIN - -union DoubleBits { - double d; - uint64_t bits; -}; - -const NSComparator FSTNumberComparator = ^NSComparisonResult(NSNumber *left, NSNumber *right) { - return [left compare:right]; -}; - -const NSComparator FSTStringComparator = ^NSComparisonResult(NSString *left, NSString *right) { - return FSTCompareStrings(left, right); -}; - -NSComparisonResult FSTCompareStrings(NSString *left, NSString *right) { - // NOTE: NSLiteralSearch is necessary to compare the raw character codes. By default, - // precomposed characters are considered equivalent to their decomposed equivalents. - return [left compare:right options:NSLiteralSearch]; -} - -NSComparisonResult FSTCompareBools(BOOL left, BOOL right) { - if (!left) { - return right ? NSOrderedAscending : NSOrderedSame; - } else { - return right ? NSOrderedSame : NSOrderedDescending; - } -} - -NSComparisonResult FSTCompareInts(int left, int right) { - if (left > right) { - return NSOrderedDescending; - } - if (right > left) { - return NSOrderedAscending; - } - return NSOrderedSame; -} - -NSComparisonResult FSTCompareInt32s(int32_t left, int32_t right) { - if (left > right) { - return NSOrderedDescending; - } - if (right > left) { - return NSOrderedAscending; - } - return NSOrderedSame; -} - -NSComparisonResult FSTCompareInt64s(int64_t left, int64_t right) { - if (left > right) { - return NSOrderedDescending; - } - if (right > left) { - return NSOrderedAscending; - } - return NSOrderedSame; -} - -NSComparisonResult FSTCompareUIntegers(NSUInteger left, NSUInteger right) { - if (left > right) { - return NSOrderedDescending; - } - if (right > left) { - return NSOrderedAscending; - } - return NSOrderedSame; -} - -NSComparisonResult FSTCompareDoubles(double left, double right) { - // NaN sorts equal to itself and before any other number. - if (left < right) { - return NSOrderedAscending; - } else if (left > right) { - return NSOrderedDescending; - } else if (left == right) { - return NSOrderedSame; - } else { - // One or both left and right is NaN. - if (isnan(left)) { - return isnan(right) ? NSOrderedSame : NSOrderedAscending; - } else { - return NSOrderedDescending; - } - } -} - -static const double LONG_MIN_VALUE_AS_DOUBLE = (double)LLONG_MIN; -static const double LONG_MAX_VALUE_AS_DOUBLE = (double)LLONG_MAX; - -NSComparisonResult FSTCompareMixed(double doubleValue, int64_t longValue) { - // LLONG_MIN has an exact representation as double, so to check for a value outside the range - // representable by long, we have to check for strictly less than LLONG_MIN. Note that this also - // handles negative infinity. - if (doubleValue < LONG_MIN_VALUE_AS_DOUBLE) { - return NSOrderedAscending; - } - - // LLONG_MAX has no exact representation as double (casting as we've done makes 2^63, which is - // larger than LLONG_MAX), so consider any value greater than or equal to the threshold to be out - // of range. This also handles positive infinity. - if (doubleValue >= LONG_MAX_VALUE_AS_DOUBLE) { - return NSOrderedDescending; - } - - // In Firestore NaN is defined to compare before all other numbers. - if (isnan(doubleValue)) { - return NSOrderedAscending; - } - - int64_t doubleAsLong = (int64_t)doubleValue; - NSComparisonResult cmp = FSTCompareInt64s(doubleAsLong, longValue); - if (cmp != NSOrderedSame) { - return cmp; - } - - // At this point the long representations are equal but this could be due to rounding. - double longAsDouble = (double)longValue; - return FSTCompareDoubles(doubleValue, longAsDouble); -} - -NSComparisonResult FSTCompareBytes(NSData *left, NSData *right) { - NSUInteger minLength = MIN(left.length, right.length); - int result = memcmp(left.bytes, right.bytes, minLength); - if (result < 0) { - return NSOrderedAscending; - } else if (result > 0) { - return NSOrderedDescending; - } else if (left.length < right.length) { - return NSOrderedAscending; - } else if (left.length > right.length) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } -} - -/** Helper to normalize a double and then return the raw bits as a uint64_t. */ -uint64_t FSTDoubleBits(double d) { - if (isnan(d)) { - d = NAN; - } - union DoubleBits converter = {.d = d}; - return converter.bits; -} - -BOOL FSTDoubleBitwiseEquals(double left, double right) { - return FSTDoubleBits(left) == FSTDoubleBits(right); -} - -NSUInteger FSTDoubleBitwiseHash(double d) { - uint64_t bits = FSTDoubleBits(d); - // Note that x ^ (x >> 32) works fine for both 32 and 64 bit definitions of NSUInteger - return (((NSUInteger)bits) ^ (NSUInteger)(bits >> 32)); -} - -NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt index 737173b..09db164 100644 --- a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt @@ -49,6 +49,7 @@ cc_library( string_apple.h DEPENDS FirebaseCore + absl_strings EXCLUDE_FROM_ALL ) @@ -106,6 +107,8 @@ cc_library( SOURCES autoid.cc autoid.h + comparison.cc + comparison.h config.h firebase_assert.h log.h diff --git a/Firestore/core/src/firebase/firestore/util/comparison.cc b/Firestore/core/src/firebase/firestore/util/comparison.cc new file mode 100644 index 0000000..4bef843 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/util/comparison.cc @@ -0,0 +1,112 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/util/comparison.h" + +#include <math.h> + +#include <limits> + +namespace firebase { +namespace firestore { +namespace util { + +bool Comparator<absl::string_view>::operator()( + const absl::string_view& left, const absl::string_view& right) const { + // TODO(wilhuff): truncation aware comparison + return left < right; +} + +bool Comparator<double>::operator()(double left, double right) const { + // NaN sorts equal to itself and before any other number. + if (left < right) { + return true; + } else if (left >= right) { + return false; + } else { + // One or both left and right is NaN. + return isnan(left) && !isnan(right); + } +} + +static constexpr double INT64_MIN_VALUE_AS_DOUBLE = + static_cast<double>(std::numeric_limits<int64_t>::min()); + +static constexpr double INT64_MAX_VALUE_AS_DOUBLE = + static_cast<double>(std::numeric_limits<int64_t>::max()); + +ComparisonResult CompareMixedNumber(double double_value, int64_t int64_value) { + // LLONG_MIN has an exact representation as double, so to check for a value + // outside the range representable by long, we have to check for strictly less + // than LLONG_MIN. Note that this also handles negative infinity. + if (double_value < INT64_MIN_VALUE_AS_DOUBLE) { + return ComparisonResult::Ascending; + } + + // LLONG_MAX has no exact representation as double (casting as we've done + // makes 2^63, which is larger than LLONG_MAX), so consider any value greater + // than or equal to the threshold to be out of range. This also handles + // positive infinity. + if (double_value >= INT64_MAX_VALUE_AS_DOUBLE) { + return ComparisonResult::Descending; + } + + // In Firestore NaN is defined to compare before all other numbers. + if (isnan(double_value)) { + return ComparisonResult::Ascending; + } + + auto double_as_int64 = static_cast<int64_t>(double_value); + ComparisonResult cmp = Compare<int64_t>(double_as_int64, int64_value); + if (cmp != ComparisonResult::Same) { + return cmp; + } + + // At this point the long representations are equal but this could be due to + // rounding. + double int64_as_double = static_cast<double>(int64_value); + return Compare<double>(double_value, int64_as_double); +} + +/** Helper to normalize a double and then return the raw bits as a uint64_t. */ +uint64_t DoubleBits(double d) { + if (isnan(d)) { + d = NAN; + } + + // Unlike C, C++ does not define type punning through a union type. + + // TODO(wilhuff): replace with absl::bit_cast + static_assert(sizeof(double) == sizeof(uint64_t), "doubles must be 8 bytes"); + uint64_t bits; + memcpy(&bits, &d, sizeof(bits)); + return bits; +} + +bool DoubleBitwiseEquals(double left, double right) { + return DoubleBits(left) == DoubleBits(right); +} + +size_t DoubleBitwiseHash(double d) { + uint64_t bits = DoubleBits(d); + // Note that x ^ (x >> 32) works fine for both 32 and 64 bit definitions of + // size_t + return static_cast<size_t>(bits) ^ static_cast<size_t>(bits >> 32); +} + +} // namespace util +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/util/comparison.h b/Firestore/core/src/firebase/firestore/util/comparison.h new file mode 100644 index 0000000..6fd1e2b --- /dev/null +++ b/Firestore/core/src/firebase/firestore/util/comparison.h @@ -0,0 +1,181 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARISON_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARISON_H_ + +#if __OBJC__ +#import <Foundation/Foundation.h> +#endif + +#include <stdint.h> +#include <sys/types.h> + +#include <functional> +#include <string> +#include <vector> + +#include "Firestore/core/src/firebase/firestore/util/string_apple.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace util { + +/** + * An enumeration describing the result of a three-way comparison among + * strongly-ordered values (i.e. where comparison between values always yields + * less-than, equal-to, or greater-than). + * + * This is equivalent to: + * + * * NSComparisonResult from the iOS/macOS Foundation framework. + * * std::strong_ordering from C++20 + * + * The values of the constants are specifically chosen so as to make casting + * between this type and NSComparisonResult possible. + */ +enum class ComparisonResult { + /** The left hand side was less than the right. */ + Ascending = -1, + + /** The left hand side was equal to the right. */ + Same = 0, + + /** The left hand side was greater than the right. */ + Descending = 1 +}; + +/** + * Returns the reverse order (i.e. Ascending => Descending) etc. + */ +constexpr ComparisonResult ReverseOrder(ComparisonResult result) { + return static_cast<ComparisonResult>(-static_cast<int>(result)); +} + +/** + * A generalized comparator for types in Firestore, with ordering defined + * according to Firestore's semantics. This is useful as argument to e.g. + * std::sort. + * + * Comparators are only defined for the limited set of types for which + * Firestore defines an ordering. + */ +template <typename T> +struct Comparator { + // By default comparison is not defined +}; + +/** Compares two strings. */ +template <> +struct Comparator<absl::string_view> { + bool operator()(const absl::string_view& left, + const absl::string_view& right) const; +}; + +/** Compares two bools: false < true. */ +template <> +struct Comparator<bool> : public std::less<bool> {}; + +/** Compares two int32_t. */ +template <> +struct Comparator<int32_t> : public std::less<int32_t> {}; + +/** Compares two int64_t. */ +template <> +struct Comparator<int64_t> : public std::less<int64_t> {}; + +/** Compares two doubles (using Firestore semantics for NaN). */ +template <> +struct Comparator<double> { + bool operator()(double left, double right) const; +}; + +/** Compare two byte sequences. */ +// TODO(wilhuff): perhaps absl::Span<uint8_t> would be better? +template <> +struct Comparator<std::vector<uint8_t>> + : public std::less<std::vector<uint8_t>> {}; + +/** + * Perform a three-way comparison between the left and right values using + * the appropriate Comparator for the values based on their type. + */ +template <typename T> +ComparisonResult Compare(const T& left, const T& right) { + Comparator<T> less_than; + if (less_than(left, right)) { + return ComparisonResult::Ascending; + } else if (less_than(right, left)) { + return ComparisonResult::Descending; + } else { + return ComparisonResult::Same; + } +} + +#if __OBJC__ +/** + * Returns true if the given ComparisonResult and NSComparisonResult have the + * same integer values (at compile time). + */ +constexpr bool EqualValue(ComparisonResult lhs, NSComparisonResult rhs) { + return static_cast<int>(lhs) == static_cast<int>(rhs); +} + +/** + * Performs a three-way comparison, identically to Compare, but converts the + * result to an NSComparisonResult. + * + * This function exists for interoperation with Objective-C++ and should + * eventually be removed. + */ +template <typename T> +inline NSComparisonResult WrapCompare(const T& left, const T& right) { + static_assert(EqualValue(ComparisonResult::Ascending, NSOrderedAscending), + "Ascending invalid"); + static_assert(EqualValue(ComparisonResult::Same, NSOrderedSame), + "Same invalid"); + static_assert(EqualValue(ComparisonResult::Descending, NSOrderedDescending), + "Descending invalid"); + + return static_cast<NSComparisonResult>(Compare<T>(left, right)); +} +#endif + +/** Compares a double and an int64_t. */ +ComparisonResult CompareMixedNumber(double doubleValue, int64_t longValue); + +/** Normalizes a double and then return the raw bits as a uint64_t. */ +uint64_t DoubleBits(double d); + +/** + * Compares the bitwise representation of two doubles, but normalizes NaN + * values. This is similar to what the backend and android clients do, including + * comparing -0.0 as not equal to 0.0. + */ +bool DoubleBitwiseEquals(double left, double right); + +/** + * Computes a bitwise hash of a double, but normalizes NaN values, suitable for + * use when using FSTDoublesAreBitwiseEqual for equality. + */ +size_t DoubleBitwiseHash(double d); + +} // namespace util +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARISON_H_ diff --git a/Firestore/core/src/firebase/firestore/util/string_apple.h b/Firestore/core/src/firebase/firestore/util/string_apple.h index e1be8c3..108ade7 100644 --- a/Firestore/core/src/firebase/firestore/util/string_apple.h +++ b/Firestore/core/src/firebase/firestore/util/string_apple.h @@ -17,8 +17,13 @@ #ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_APPLE_H_ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_APPLE_H_ +// Everything in this header exists for compatibility with Objective-C. +#if __OBJC__ + #import <Foundation/Foundation.h> +#include "absl/strings/string_view.h" + namespace firebase { namespace firestore { namespace util { @@ -32,8 +37,16 @@ inline NSString* WrapNSStringNoCopy(const char* c_str) { freeWhenDone:NO]; } +// Creates an absl::string_view wrapper for the contents of the given NSString. +inline absl::string_view MakeStringView(NSString* str) { + return absl::string_view( + [str UTF8String], [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); +} + } // namespace util } // namespace firestore } // namespace firebase +#endif // __OBJC__ + #endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_APPLE_H_ diff --git a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt index 468c62e..5e7612c 100644 --- a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt @@ -36,6 +36,7 @@ cc_test( firebase_firestore_util_test SOURCES autoid_test.cc + comparison_test.cc string_printf_test.cc DEPENDS firebase_firestore_util diff --git a/Firestore/core/test/firebase/firestore/util/comparison_test.cc b/Firestore/core/test/firebase/firestore/util/comparison_test.cc new file mode 100644 index 0000000..ecbed4a --- /dev/null +++ b/Firestore/core/test/firebase/firestore/util/comparison_test.cc @@ -0,0 +1,210 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/util/comparison.h" + +#include <math.h> + +#include <limits> + +#include "Firestore/core/src/firebase/firestore/util/string_printf.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace util { + +#define ASSERT_SAME(comparison) \ + do { \ + ASSERT_EQ(ComparisonResult::Same, comparison); \ + } while (0) + +#define ASSERT_ASCENDING(comparison) \ + do { \ + ASSERT_EQ(ComparisonResult::Ascending, comparison); \ + } while (0) + +#define ASSERT_DESCENDING(comparison) \ + do { \ + ASSERT_EQ(ComparisonResult::Descending, comparison); \ + } while (0) + +TEST(Comparison, ReverseOrder) { + ASSERT_ASCENDING(ReverseOrder(ComparisonResult::Descending)); + ASSERT_DESCENDING(ReverseOrder(ComparisonResult::Ascending)); + ASSERT_SAME(ReverseOrder(ComparisonResult::Same)); +} + +TEST(Comparison, StringCompare) { + ASSERT_ASCENDING(Compare<absl::string_view>("", "a")); + ASSERT_ASCENDING(Compare<absl::string_view>("a", "b")); + ASSERT_ASCENDING(Compare<absl::string_view>("a", "aa")); + + ASSERT_DESCENDING(Compare<absl::string_view>("a", "")); + ASSERT_DESCENDING(Compare<absl::string_view>("b", "a")); + ASSERT_DESCENDING(Compare<absl::string_view>("aa", "a")); + + ASSERT_SAME(Compare<absl::string_view>("", "")); + ASSERT_SAME(Compare<absl::string_view>("", std::string())); + ASSERT_SAME(Compare<absl::string_view>("a", "a")); +} + +TEST(Comparison, BooleanCompare) { + ASSERT_SAME(Compare<bool>(false, false)); + ASSERT_SAME(Compare<bool>(true, true)); + ASSERT_ASCENDING(Compare<bool>(false, true)); + ASSERT_DESCENDING(Compare<bool>(true, false)); +} + +TEST(Comparison, DoubleCompare) { + ASSERT_SAME(Compare<double>(NAN, NAN)); + ASSERT_ASCENDING(Compare<double>(NAN, 0)); + ASSERT_DESCENDING(Compare<double>(0, NAN)); + + ASSERT_SAME(Compare<double>(-INFINITY, -INFINITY)); + ASSERT_SAME(Compare<double>(INFINITY, INFINITY)); + ASSERT_ASCENDING(Compare<double>(-INFINITY, INFINITY)); + ASSERT_DESCENDING(Compare<double>(INFINITY, -INFINITY)); + + ASSERT_SAME(Compare<double>(0, 0)); + ASSERT_SAME(Compare<double>(-0, -0)); + ASSERT_SAME(Compare<double>(-0, 0)); +} + +#define ASSERT_BIT_EQUALS(expected, actual) \ + do { \ + uint64_t expectedBits = DoubleBits(expected); \ + uint64_t actualBits = DoubleBits(actual); \ + if (expectedBits != actualBits) { \ + std::string message = StringPrintf( \ + "Expected <%f> to compare equal to <%f> " \ + "with bits <%llX> equal to <%llX>", \ + actual, expected, actualBits, expectedBits); \ + FAIL() << message; \ + } \ + } while (0); + +#define ASSERT_MIXED_SAME(doubleValue, longValue) \ + do { \ + ComparisonResult result = CompareMixedNumber(doubleValue, longValue); \ + if (result != ComparisonResult::Same) { \ + std::string message = StringPrintf( \ + "Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \ + FAIL() << message; \ + } \ + } while (0); + +#define ASSERT_MIXED_DESCENDING(doubleValue, longValue) \ + do { \ + ComparisonResult result = CompareMixedNumber(doubleValue, longValue); \ + if (result != ComparisonResult::Descending) { \ + std::string message = StringPrintf( \ + "Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \ + FAIL() << message; \ + } \ + } while (0); + +#define ASSERT_MIXED_ASCENDING(doubleValue, longValue) \ + do { \ + ComparisonResult result = CompareMixedNumber(doubleValue, longValue); \ + if (result != ComparisonResult::Ascending) { \ + std::string message = StringPrintf( \ + "Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \ + FAIL() << message; \ + } \ + } while (0); + +TEST(Comparison, MixedNumberCompare) { + // Infinities + ASSERT_MIXED_ASCENDING(-INFINITY, LLONG_MIN); + ASSERT_MIXED_ASCENDING(-INFINITY, LLONG_MAX); + ASSERT_MIXED_ASCENDING(-INFINITY, 0LL); + + ASSERT_MIXED_DESCENDING(INFINITY, LLONG_MIN); + ASSERT_MIXED_DESCENDING(INFINITY, LLONG_MAX); + ASSERT_MIXED_DESCENDING(INFINITY, 0LL); + + // NaN + ASSERT_MIXED_ASCENDING(NAN, LLONG_MIN); + ASSERT_MIXED_ASCENDING(NAN, LLONG_MAX); + ASSERT_MIXED_ASCENDING(NAN, 0LL); + + // Large values (note DBL_MIN is positive and near zero). + ASSERT_MIXED_ASCENDING(-DBL_MAX, LLONG_MIN); + + // Tests around LLONG_MIN + ASSERT_BIT_EQUALS((double)LLONG_MIN, -0x1.0p63); + ASSERT_MIXED_SAME(-0x1.0p63, LLONG_MIN); + ASSERT_MIXED_ASCENDING(-0x1.0p63, LLONG_MIN + 1); + + ASSERT_LT(-0x1.0000000000001p63, -0x1.0p63); + ASSERT_MIXED_ASCENDING(-0x1.0000000000001p63, LLONG_MIN); + ASSERT_MIXED_DESCENDING(-0x1.FFFFFFFFFFFFFp62, LLONG_MIN); + + // Tests around LLONG_MAX + // Note LLONG_MAX cannot be exactly represented by a double, so the system + // rounds it to the nearest double, which is 2^63. This number, in turn is + // larger than the maximum representable as a long. + ASSERT_BIT_EQUALS(0x1.0p63, (double)LLONG_MAX); + ASSERT_MIXED_DESCENDING(0x1.0p63, LLONG_MAX); + + // The largest value with an exactly long representation + ASSERT_EQ((int64_t)0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC00LL); + ASSERT_MIXED_SAME(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC00LL); + + ASSERT_MIXED_DESCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFB00LL); + ASSERT_MIXED_DESCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFBFFLL); + ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC01LL); + ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFD00LL); + + ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFEp62, 0x7FFFFFFFFFFFFC00LL); + + // Tests around MAX_SAFE_INTEGER + ASSERT_MIXED_SAME(0x1.FFFFFFFFFFFFFp52, 0x1FFFFFFFFFFFFFLL); + ASSERT_MIXED_DESCENDING(0x1.FFFFFFFFFFFFFp52, 0x1FFFFFFFFFFFFELL); + ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFEp52, 0x1FFFFFFFFFFFFFLL); + ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFFp52, 0x20000000000000LL); + + // Tests around MIN_SAFE_INTEGER + ASSERT_MIXED_SAME(-0x1.FFFFFFFFFFFFFp52, -0x1FFFFFFFFFFFFFLL); + ASSERT_MIXED_ASCENDING(-0x1.FFFFFFFFFFFFFp52, -0x1FFFFFFFFFFFFELL); + ASSERT_MIXED_DESCENDING(-0x1.FFFFFFFFFFFFEp52, -0x1FFFFFFFFFFFFFLL); + ASSERT_MIXED_DESCENDING(-0x1.FFFFFFFFFFFFFp52, -0x20000000000000LL); + + // Tests around zero. + ASSERT_MIXED_SAME(-0.0, 0LL); + ASSERT_MIXED_SAME(0.0, 0LL); + + // The smallest representable positive value should be greater than zero + ASSERT_MIXED_DESCENDING(DBL_MIN, 0LL); + ASSERT_MIXED_ASCENDING(-DBL_MIN, 0LL); + + // Note that 0x1.0p-1074 is a hex floating point literal representing the + // minimum subnormal number: <https://en.wikipedia.org/wiki/Denormal_number>. + double minSubNormal = 0x1.0p-1074; + ASSERT_MIXED_DESCENDING(minSubNormal, 0LL); + ASSERT_MIXED_ASCENDING(-minSubNormal, 0LL); + + // Other sanity checks + ASSERT_MIXED_ASCENDING(0.5, 1LL); + ASSERT_MIXED_DESCENDING(0.5, 0LL); + ASSERT_MIXED_ASCENDING(1.5, 2LL); + ASSERT_MIXED_DESCENDING(1.5, 1LL); +} + +} // namespace util +} // namespace firestore +} // namespace firebase diff --git a/Firestore/third_party/abseil-cpp/absl/base/macros.h b/Firestore/third_party/abseil-cpp/absl/base/macros.h index d414087..07c5cf7 100644 --- a/Firestore/third_party/abseil-cpp/absl/base/macros.h +++ b/Firestore/third_party/abseil-cpp/absl/base/macros.h @@ -196,7 +196,7 @@ enum LinkerInitialized { #define ABSL_ASSERT(expr) (false ? (void)(expr) : (void)0) #else #define ABSL_ASSERT(expr) \ - (ABSL_PREDICT_TRUE((expr)) ? (void)0 : [] { assert(false && #expr); }()) + (void) (ABSL_PREDICT_TRUE((expr)) ? (void)0 : [] { assert(false && #expr); }()) #endif #endif // ABSL_BASE_MACROS_H_ |