RLMUtil.mm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2014 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMUtil.hpp"
  19. #import "RLMArray_Private.hpp"
  20. #import "RLMListBase.h"
  21. #import "RLMObjectSchema_Private.hpp"
  22. #import "RLMObjectStore.h"
  23. #import "RLMObject_Private.hpp"
  24. #import "RLMProperty_Private.h"
  25. #import "RLMSchema_Private.h"
  26. #import "RLMSwiftSupport.h"
  27. #import "shared_realm.hpp"
  28. #import <realm/mixed.hpp>
  29. #import <realm/table_view.hpp>
  30. #include <sys/sysctl.h>
  31. #include <sys/types.h>
  32. #if !defined(REALM_COCOA_VERSION)
  33. #import "RLMVersion.h"
  34. #endif
  35. static inline bool numberIsInteger(__unsafe_unretained NSNumber *const obj) {
  36. char data_type = [obj objCType][0];
  37. return data_type == *@encode(bool) ||
  38. data_type == *@encode(char) ||
  39. data_type == *@encode(short) ||
  40. data_type == *@encode(int) ||
  41. data_type == *@encode(long) ||
  42. data_type == *@encode(long long) ||
  43. data_type == *@encode(unsigned short) ||
  44. data_type == *@encode(unsigned int) ||
  45. data_type == *@encode(unsigned long) ||
  46. data_type == *@encode(unsigned long long);
  47. }
  48. static inline bool numberIsBool(__unsafe_unretained NSNumber *const obj) {
  49. // @encode(BOOL) is 'B' on iOS 64 and 'c'
  50. // objcType is always 'c'. Therefore compare to "c".
  51. if ([obj objCType][0] == 'c') {
  52. return true;
  53. }
  54. if (numberIsInteger(obj)) {
  55. int value = [obj intValue];
  56. return value == 0 || value == 1;
  57. }
  58. return false;
  59. }
  60. static inline bool numberIsFloat(__unsafe_unretained NSNumber *const obj) {
  61. char data_type = [obj objCType][0];
  62. return data_type == *@encode(float) ||
  63. data_type == *@encode(short) ||
  64. data_type == *@encode(int) ||
  65. data_type == *@encode(long) ||
  66. data_type == *@encode(long long) ||
  67. data_type == *@encode(unsigned short) ||
  68. data_type == *@encode(unsigned int) ||
  69. data_type == *@encode(unsigned long) ||
  70. data_type == *@encode(unsigned long long) ||
  71. // A double is like float if it fits within float bounds or is NaN.
  72. (data_type == *@encode(double) && (ABS([obj doubleValue]) <= FLT_MAX || isnan([obj doubleValue])));
  73. }
  74. static inline bool numberIsDouble(__unsafe_unretained NSNumber *const obj) {
  75. char data_type = [obj objCType][0];
  76. return data_type == *@encode(double) ||
  77. data_type == *@encode(float) ||
  78. data_type == *@encode(short) ||
  79. data_type == *@encode(int) ||
  80. data_type == *@encode(long) ||
  81. data_type == *@encode(long long) ||
  82. data_type == *@encode(unsigned short) ||
  83. data_type == *@encode(unsigned int) ||
  84. data_type == *@encode(unsigned long) ||
  85. data_type == *@encode(unsigned long long);
  86. }
  87. static inline RLMArray *asRLMArray(__unsafe_unretained id const value) {
  88. return RLMDynamicCast<RLMArray>(value) ?: RLMDynamicCast<RLMListBase>(value)._rlmArray;
  89. }
  90. static inline bool checkArrayType(__unsafe_unretained RLMArray *const array,
  91. RLMPropertyType type, bool optional,
  92. __unsafe_unretained NSString *const objectClassName) {
  93. return array.type == type && array.optional == optional
  94. && (type != RLMPropertyTypeObject || [array.objectClassName isEqualToString:objectClassName]);
  95. }
  96. BOOL RLMValidateValue(__unsafe_unretained id const value,
  97. RLMPropertyType type, bool optional, bool array,
  98. __unsafe_unretained NSString *const objectClassName) {
  99. if (optional && !RLMCoerceToNil(value)) {
  100. return YES;
  101. }
  102. if (array) {
  103. if (auto rlmArray = asRLMArray(value)) {
  104. return checkArrayType(rlmArray, type, optional, objectClassName);
  105. }
  106. if ([value conformsToProtocol:@protocol(NSFastEnumeration)]) {
  107. // check each element for compliance
  108. for (id el in (id<NSFastEnumeration>)value) {
  109. if (!RLMValidateValue(el, type, optional, false, objectClassName)) {
  110. return NO;
  111. }
  112. }
  113. return YES;
  114. }
  115. if (!value || value == NSNull.null) {
  116. return YES;
  117. }
  118. return NO;
  119. }
  120. switch (type) {
  121. case RLMPropertyTypeString:
  122. return [value isKindOfClass:[NSString class]];
  123. case RLMPropertyTypeBool:
  124. if ([value isKindOfClass:[NSNumber class]]) {
  125. return numberIsBool(value);
  126. }
  127. return NO;
  128. case RLMPropertyTypeDate:
  129. return [value isKindOfClass:[NSDate class]];
  130. case RLMPropertyTypeInt:
  131. if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
  132. return numberIsInteger(number);
  133. }
  134. return NO;
  135. case RLMPropertyTypeFloat:
  136. if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
  137. return numberIsFloat(number);
  138. }
  139. return NO;
  140. case RLMPropertyTypeDouble:
  141. if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
  142. return numberIsDouble(number);
  143. }
  144. return NO;
  145. case RLMPropertyTypeData:
  146. return [value isKindOfClass:[NSData class]];
  147. case RLMPropertyTypeAny:
  148. return NO;
  149. case RLMPropertyTypeLinkingObjects:
  150. return YES;
  151. case RLMPropertyTypeObject: {
  152. // only NSNull, nil, or objects which derive from RLMObject and match the given
  153. // object class are valid
  154. RLMObjectBase *objBase = RLMDynamicCast<RLMObjectBase>(value);
  155. return objBase && [objBase->_objectSchema.className isEqualToString:objectClassName];
  156. }
  157. }
  158. @throw RLMException(@"Invalid RLMPropertyType specified");
  159. }
  160. void RLMThrowTypeError(__unsafe_unretained id const obj,
  161. __unsafe_unretained RLMObjectSchema *const objectSchema,
  162. __unsafe_unretained RLMProperty *const prop) {
  163. @throw RLMException(@"Invalid value '%@' of type '%@' for '%@%s'%s property '%@.%@'.",
  164. obj, [obj class],
  165. prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "",
  166. prop.array ? " array" : "", objectSchema.className, prop.name);
  167. }
  168. void RLMValidateValueForProperty(__unsafe_unretained id const obj,
  169. __unsafe_unretained RLMObjectSchema *const objectSchema,
  170. __unsafe_unretained RLMProperty *const prop,
  171. bool validateObjects) {
  172. // This duplicates a lot of the checks in RLMIsObjectValidForProperty()
  173. // for the sake of more specific error messages
  174. if (prop.array) {
  175. // nil is considered equivalent to an empty array for historical reasons
  176. // since we don't support null arrays (only arrays containing null),
  177. // it's not worth the BC break to change this
  178. if (!obj || obj == NSNull.null) {
  179. return;
  180. }
  181. if (![obj conformsToProtocol:@protocol(NSFastEnumeration)]) {
  182. @throw RLMException(@"Invalid value (%@) for '%@%s' array property '%@.%@': value is not enumerable.",
  183. obj, prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "",
  184. objectSchema.className, prop.name);
  185. }
  186. if (!validateObjects && prop.type == RLMPropertyTypeObject) {
  187. return;
  188. }
  189. if (RLMArray *array = asRLMArray(obj)) {
  190. if (!checkArrayType(array, prop.type, prop.optional, prop.objectClassName)) {
  191. @throw RLMException(@"RLMArray<%@%s> does not match expected type '%@%s' for property '%@.%@'.",
  192. array.objectClassName ?: RLMTypeToString(array.type), array.optional ? "?" : "",
  193. prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "",
  194. objectSchema.className, prop.name);
  195. }
  196. return;
  197. }
  198. for (id value in obj) {
  199. if (!RLMValidateValue(value, prop.type, prop.optional, false, prop.objectClassName)) {
  200. RLMThrowTypeError(value, objectSchema, prop);
  201. }
  202. }
  203. return;
  204. }
  205. // For create() we want to skip the validation logic for objects because
  206. // we allow much fuzzier matching (any KVC-compatible object with at least
  207. // all the non-defaulted fields), and all the logic for that lives in the
  208. // object store rather than here
  209. if (prop.type == RLMPropertyTypeObject && !validateObjects) {
  210. return;
  211. }
  212. if (RLMIsObjectValidForProperty(obj, prop)) {
  213. return;
  214. }
  215. RLMThrowTypeError(obj, objectSchema, prop);
  216. }
  217. BOOL RLMIsObjectValidForProperty(__unsafe_unretained id const obj,
  218. __unsafe_unretained RLMProperty *const property) {
  219. return RLMValidateValue(obj, property.type, property.optional, property.array, property.objectClassName);
  220. }
  221. NSDictionary *RLMDefaultValuesForObjectSchema(__unsafe_unretained RLMObjectSchema *const objectSchema) {
  222. if (!objectSchema.isSwiftClass) {
  223. return [objectSchema.objectClass defaultPropertyValues];
  224. }
  225. NSMutableDictionary *defaults = nil;
  226. if ([objectSchema.objectClass isSubclassOfClass:RLMObject.class]) {
  227. defaults = [NSMutableDictionary dictionaryWithDictionary:[objectSchema.objectClass defaultPropertyValues]];
  228. }
  229. else {
  230. defaults = [NSMutableDictionary dictionary];
  231. }
  232. RLMObject *defaultObject = [[objectSchema.objectClass alloc] init];
  233. for (RLMProperty *prop in objectSchema.properties) {
  234. if (!defaults[prop.name] && defaultObject[prop.name]) {
  235. defaults[prop.name] = defaultObject[prop.name];
  236. }
  237. }
  238. return defaults;
  239. }
  240. static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) {
  241. NSMutableDictionary *userInfo = @{RLMRealmVersionKey: REALM_COCOA_VERSION,
  242. RLMRealmCoreVersionKey: @REALM_VERSION}.mutableCopy;
  243. if (additionalUserInfo != nil) {
  244. [userInfo addEntriesFromDictionary:additionalUserInfo];
  245. }
  246. NSException *e = [NSException exceptionWithName:RLMExceptionName
  247. reason:reason
  248. userInfo:userInfo];
  249. return e;
  250. }
  251. NSException *RLMException(NSString *fmt, ...) {
  252. va_list args;
  253. va_start(args, fmt);
  254. NSException *e = RLMException([[NSString alloc] initWithFormat:fmt arguments:args], @{});
  255. va_end(args);
  256. return e;
  257. }
  258. NSException *RLMException(std::exception const& exception) {
  259. return RLMException(@"%s", exception.what());
  260. }
  261. NSError *RLMMakeError(RLMError code, std::exception const& exception) {
  262. return [NSError errorWithDomain:RLMErrorDomain
  263. code:code
  264. userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
  265. @"Error Code": @(code)}];
  266. }
  267. NSError *RLMMakeError(RLMError code, const realm::util::File::AccessError& exception) {
  268. return [NSError errorWithDomain:RLMErrorDomain
  269. code:code
  270. userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
  271. NSFilePathErrorKey: @(exception.get_path().c_str()),
  272. @"Error Code": @(code)}];
  273. }
  274. NSError *RLMMakeError(RLMError code, const realm::RealmFileException& exception) {
  275. NSString *underlying = @(exception.underlying().c_str());
  276. return [NSError errorWithDomain:RLMErrorDomain
  277. code:code
  278. userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
  279. NSFilePathErrorKey: @(exception.path().c_str()),
  280. @"Error Code": @(code),
  281. @"Underlying": underlying.length == 0 ? @"n/a" : underlying}];
  282. }
  283. NSError *RLMMakeError(std::system_error const& exception) {
  284. BOOL isGenericCategoryError = (exception.code().category() == std::generic_category());
  285. NSString *category = @(exception.code().category().name());
  286. NSString *errorDomain = isGenericCategoryError ? NSPOSIXErrorDomain : RLMUnknownSystemErrorDomain;
  287. return [NSError errorWithDomain:errorDomain
  288. code:exception.code().value()
  289. userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
  290. @"Error Code": @(exception.code().value()),
  291. @"Category": category}];
  292. }
  293. void RLMSetErrorOrThrow(NSError *error, NSError **outError) {
  294. if (outError) {
  295. *outError = error;
  296. }
  297. else {
  298. NSString *msg = error.localizedDescription;
  299. if (error.userInfo[NSFilePathErrorKey]) {
  300. msg = [NSString stringWithFormat:@"%@: %@", error.userInfo[NSFilePathErrorKey], error.localizedDescription];
  301. }
  302. @throw RLMException(msg, @{NSUnderlyingErrorKey: error});
  303. }
  304. }
  305. BOOL RLMIsDebuggerAttached()
  306. {
  307. int name[] = {
  308. CTL_KERN,
  309. KERN_PROC,
  310. KERN_PROC_PID,
  311. getpid()
  312. };
  313. struct kinfo_proc info;
  314. size_t info_size = sizeof(info);
  315. if (sysctl(name, sizeof(name)/sizeof(name[0]), &info, &info_size, NULL, 0) == -1) {
  316. NSLog(@"sysctl() failed: %s", strerror(errno));
  317. return false;
  318. }
  319. return (info.kp_proc.p_flag & P_TRACED) != 0;
  320. }
  321. BOOL RLMIsRunningInPlayground() {
  322. return [[NSBundle mainBundle].bundleIdentifier hasPrefix:@"com.apple.dt.playground."];
  323. }
  324. id RLMMixedToObjc(realm::Mixed const& mixed) {
  325. switch (mixed.get_type()) {
  326. case realm::type_String:
  327. return RLMStringDataToNSString(mixed.get_string());
  328. case realm::type_Int:
  329. return @(mixed.get_int());
  330. case realm::type_Float:
  331. return @(mixed.get_float());
  332. case realm::type_Double:
  333. return @(mixed.get_double());
  334. case realm::type_Bool:
  335. return @(mixed.get_bool());
  336. case realm::type_Timestamp:
  337. return RLMTimestampToNSDate(mixed.get_timestamp());
  338. case realm::type_Binary:
  339. return RLMBinaryDataToNSData(mixed.get_binary());
  340. case realm::type_Link:
  341. case realm::type_LinkList:
  342. default:
  343. @throw RLMException(@"Invalid data type for RLMPropertyTypeAny property.");
  344. }
  345. }
  346. NSString *RLMDefaultDirectoryForBundleIdentifier(NSString *bundleIdentifier) {
  347. #if TARGET_OS_TV
  348. (void)bundleIdentifier;
  349. // tvOS prohibits writing to the Documents directory, so we use the Library/Caches directory instead.
  350. return NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
  351. #elif TARGET_OS_IPHONE
  352. (void)bundleIdentifier;
  353. // On iOS the Documents directory isn't user-visible, so put files there
  354. return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
  355. #else
  356. // On OS X it is, so put files in Application Support. If we aren't running
  357. // in a sandbox, put it in a subdirectory based on the bundle identifier
  358. // to avoid accidentally sharing files between applications
  359. NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0];
  360. if (![[NSProcessInfo processInfo] environment][@"APP_SANDBOX_CONTAINER_ID"]) {
  361. if (!bundleIdentifier) {
  362. bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  363. }
  364. if (!bundleIdentifier) {
  365. bundleIdentifier = [NSBundle mainBundle].executablePath.lastPathComponent;
  366. }
  367. path = [path stringByAppendingPathComponent:bundleIdentifier];
  368. // create directory
  369. [[NSFileManager defaultManager] createDirectoryAtPath:path
  370. withIntermediateDirectories:YES
  371. attributes:nil
  372. error:nil];
  373. }
  374. return path;
  375. #endif
  376. }