FLEXRuntimeExporter.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. //
  2. // FLEXRuntimeExporter.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 3/26/20.
  6. // Copyright (c) 2020 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXRuntimeExporter.h"
  9. #import "FLEXSQLiteDatabaseManager.h"
  10. #import "FLEX-Runtime.h"
  11. #import "NSObject+Reflection.h"
  12. #import "FLEXRuntimeController.h"
  13. #import "FLEXRuntimeClient.h"
  14. #import "NSArray+Functional.h"
  15. #import <sqlite3.h>
  16. NSString * const kFREEnableForeignKeys = @"PRAGMA foreign_keys = ON;";
  17. /// Loaded images
  18. NSString * const kFRECreateTableMachOCommand = @"CREATE TABLE MachO( "
  19. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  20. "shortName TEXT, "
  21. "imagePath TEXT, "
  22. "bundleID TEXT "
  23. ");";
  24. NSString * const kFREInsertImage = @"INSERT INTO MachO ( "
  25. "shortName, imagePath, bundleID "
  26. ") VALUES ( "
  27. "$shortName, $imagePath, $bundleID "
  28. ");";
  29. /// Objc classes
  30. NSString * const kFRECreateTableClassCommand = @"CREATE TABLE Class( "
  31. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  32. "className TEXT, "
  33. "superclass INTEGER, "
  34. "instanceSize INTEGER, "
  35. "version INTEGER, "
  36. "image INTEGER, "
  37. "FOREIGN KEY(superclass) REFERENCES Class(id), "
  38. "FOREIGN KEY(image) REFERENCES MachO(id) "
  39. ");";
  40. NSString * const kFREInsertClass = @"INSERT INTO Class ( "
  41. "className, instanceSize, version, image "
  42. ") VALUES ( "
  43. "$className, $instanceSize, $version, $image "
  44. ");";
  45. NSString * const kFREUpdateClassSetSuper = @"UPDATE Class SET superclass = $super WHERE id = $id;";
  46. /// Unique objc selectors
  47. NSString * const kFRECreateTableSelectorCommand = @"CREATE TABLE Selector( "
  48. "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
  49. "name text NOT NULL UNIQUE "
  50. ");";
  51. NSString * const kFREInsertSelector = @"INSERT OR IGNORE INTO Selector (name) VALUES ($name);";
  52. /// Unique objc type encodings
  53. NSString * const kFRECreateTableTypeEncodingCommand = @"CREATE TABLE TypeEncoding( "
  54. "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
  55. "string text NOT NULL UNIQUE, "
  56. "size integer "
  57. ");";
  58. NSString * const kFREInsertTypeEncoding = @"INSERT OR IGNORE INTO TypeEncoding "
  59. "(string, size) VALUES ($type, $size);";
  60. /// Unique objc type signatures
  61. NSString * const kFRECreateTableTypeSignatureCommand = @"CREATE TABLE TypeSignature( "
  62. "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
  63. "string text NOT NULL UNIQUE "
  64. ");";
  65. NSString * const kFREInsertTypeSignature = @"INSERT OR IGNORE INTO TypeSignature "
  66. "(string) VALUES ($type);";
  67. NSString * const kFRECreateTableMethodSignatureCommand = @"CREATE TABLE MethodSignature( "
  68. "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
  69. "typeEncoding TEXT, "
  70. "argc INTEGER, "
  71. "returnType INTEGER, "
  72. "frameLength INTEGER, "
  73. "FOREIGN KEY(returnType) REFERENCES TypeEncoding(id) "
  74. ");";
  75. NSString * const kFREInsertMethodSignature = @"INSERT INTO MethodSignature ( "
  76. "typeEncoding, argc, returnType, frameLength "
  77. ") VALUES ( "
  78. "$typeEncoding, $argc, $returnType, $frameLength "
  79. ");";
  80. NSString * const kFRECreateTableMethodCommand = @"CREATE TABLE Method( "
  81. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  82. "sel INTEGER, "
  83. "class INTEGER, "
  84. "instance INTEGER, " // 0 if class method, 1 if instance method
  85. "signature INTEGER, "
  86. "image INTEGER, "
  87. "FOREIGN KEY(sel) REFERENCES Selector(id), "
  88. "FOREIGN KEY(class) REFERENCES Class(id), "
  89. "FOREIGN KEY(signature) REFERENCES MethodSignature(id), "
  90. "FOREIGN KEY(image) REFERENCES MachO(id) "
  91. ");";
  92. NSString * const kFREInsertMethod = @"INSERT INTO Method ( "
  93. "sel, class, instance, signature, image "
  94. ") VALUES ( "
  95. "$sel, $class, $instance, $signature, $image "
  96. ");";
  97. NSString * const kFRECreateTablePropertyCommand = @"CREATE TABLE Property( "
  98. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  99. "name TEXT, "
  100. "class INTEGER, "
  101. "instance INTEGER, " // 0 if class prop, 1 if instance prop
  102. "image INTEGER, "
  103. "attributes TEXT, "
  104. "customGetter INTEGER, "
  105. "customSetter INTEGER, "
  106. "type INTEGER, "
  107. "ivar TEXT, "
  108. "readonly INTEGER, "
  109. "copy INTEGER, "
  110. "retained INTEGER, "
  111. "nonatomic INTEGER, "
  112. "dynamic INTEGER, "
  113. "weak INTEGER, "
  114. "canGC INTEGER, "
  115. "FOREIGN KEY(class) REFERENCES Class(id), "
  116. "FOREIGN KEY(customGetter) REFERENCES Selector(id), "
  117. "FOREIGN KEY(customSetter) REFERENCES Selector(id), "
  118. "FOREIGN KEY(image) REFERENCES MachO(id) "
  119. ");";
  120. NSString * const kFREInsertProperty = @"INSERT INTO Property ( "
  121. "name, class, instance, attributes, image, "
  122. "customGetter, customSetter, type, ivar, readonly, "
  123. "copy, retained, nonatomic, dynamic, weak, canGC "
  124. ") VALUES ( "
  125. "$name, $class, $instance, $attributes, $image, "
  126. "$customGetter, $customSetter, $type, $ivar, $readonly, "
  127. "$copy, $retained, $nonatomic, $dynamic, $weak, $canGC "
  128. ");";
  129. NSString * const kFRECreateTableIvarCommand = @"CREATE TABLE Ivar( "
  130. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  131. "name TEXT, "
  132. "offset INTEGER, "
  133. "type INTEGER, "
  134. "class INTEGER, "
  135. "image INTEGER, "
  136. "FOREIGN KEY(type) REFERENCES TypeEncoding(id), "
  137. "FOREIGN KEY(class) REFERENCES Class(id), "
  138. "FOREIGN KEY(image) REFERENCES MachO(id) "
  139. ");";
  140. NSString * const kFREInsertIvar = @"INSERT INTO Ivar ( "
  141. "name, offset, type, class, image "
  142. ") VALUES ( "
  143. "$name, $offset, $type, $class, $image "
  144. ");";
  145. NSString * const kFRECreateTableProtocolCommand = @"CREATE TABLE Protocol( "
  146. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  147. "name TEXT, "
  148. "image INTEGER, "
  149. "FOREIGN KEY(image) REFERENCES MachO(id) "
  150. ");";
  151. NSString * const kFREInsertProtocol = @"INSERT INTO Protocol "
  152. "(name, image) VALUES ($name, $image);";
  153. NSString * const kFRECreateTableProtocolPropertyCommand = @"CREATE TABLE ProtocolMember( "
  154. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  155. "protocol INTEGER, "
  156. "required INTEGER, "
  157. "instance INTEGER, " // 0 if class member, 1 if instance member
  158. // Only of the two below is used
  159. "property TEXT, "
  160. "method TEXT, "
  161. "image INTEGER, "
  162. "FOREIGN KEY(protocol) REFERENCES Protocol(id), "
  163. "FOREIGN KEY(image) REFERENCES MachO(id) "
  164. ");";
  165. NSString * const kFREInsertProtocolMember = @"INSERT INTO ProtocolMember ( "
  166. "protocol, required, instance, property, method, image "
  167. ") VALUES ( "
  168. "$protocol, $required, $instance, $property, $method, $image "
  169. ");";
  170. /// For protocols conforming to other protocols
  171. NSString * const kFRECreateTableProtocolConformanceCommand = @"CREATE TABLE ProtocolConformance( "
  172. "protocol INTEGER, "
  173. "conformance INTEGER, "
  174. "FOREIGN KEY(protocol) REFERENCES Protocol(id), "
  175. "FOREIGN KEY(conformance) REFERENCES Protocol(id) "
  176. ");";
  177. NSString * const kFREInsertProtocolConformance = @"INSERT INTO ProtocolConformance "
  178. "(protocol, conformance) VALUES ($protocol, $conformance);";
  179. /// For classes conforming to protocols
  180. NSString * const kFRECreateTableClassConformanceCommand = @"CREATE TABLE ClassConformance( "
  181. "class INTEGER, "
  182. "conformance INTEGER, "
  183. "FOREIGN KEY(class) REFERENCES Class(id), "
  184. "FOREIGN KEY(conformance) REFERENCES Protocol(id) "
  185. ");";
  186. NSString * const kFREInsertClassConformance = @"INSERT INTO ClassConformance "
  187. "(class, conformance) VALUES ($class, $conformance);";
  188. @interface FLEXRuntimeExporter ()
  189. @property (nonatomic, readonly) FLEXSQLiteDatabaseManager *db;
  190. @property (nonatomic, copy) NSArray<NSString *> *loadedShortBundleNames;
  191. @property (nonatomic, copy) NSArray<NSString *> *loadedBundlePaths;
  192. @property (nonatomic, copy) NSArray<FLEXProtocol *> *protocols;
  193. @property (nonatomic, copy) NSArray<Class> *classes;
  194. @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *bundlePathsToIDs;
  195. @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *protocolsToIDs;
  196. @property (nonatomic) NSMutableDictionary<Class, NSNumber *> *classesToIDs;
  197. @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *typeEncodingsToIDs;
  198. @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *methodSignaturesToIDs;
  199. @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *selectorsToIDs;
  200. @end
  201. @implementation FLEXRuntimeExporter
  202. + (NSString *)tempFilename {
  203. NSString *temp = NSTemporaryDirectory();
  204. NSString *uuid = [NSUUID.UUID.UUIDString substringToIndex:8];
  205. NSString *filename = [NSString stringWithFormat:@"FLEXRuntimeDatabase-%@.db", uuid];
  206. return [temp stringByAppendingPathComponent:filename];
  207. }
  208. + (void)createRuntimeDatabaseAtPath:(NSString *)path
  209. progressHandler:(void(^)(NSString *status))progress
  210. completion:(void (^)(NSString *))completion {
  211. [self createRuntimeDatabaseAtPath:path forImages:nil progressHandler:progress completion:completion];
  212. }
  213. + (void)createRuntimeDatabaseAtPath:(NSString *)path
  214. forImages:(NSArray<NSString *> *)images
  215. progressHandler:(void(^)(NSString *status))progress
  216. completion:(void(^)(NSString *_Nullable error))completion {
  217. __typeof(completion) callback = ^(NSString *error) {
  218. dispatch_async(dispatch_get_main_queue(), ^{
  219. completion(error);
  220. });
  221. };
  222. // This must be called on the main thread first
  223. if (NSThread.isMainThread) {
  224. [FLEXRuntimeClient initializeWebKitLegacy];
  225. } else {
  226. dispatch_sync(dispatch_get_main_queue(), ^{
  227. [FLEXRuntimeClient initializeWebKitLegacy];
  228. });
  229. }
  230. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  231. NSError *error = nil;
  232. NSString *errorMessage = nil;
  233. // Get unused temp filename, remove existing database if any
  234. NSString *tempPath = [self tempFilename];
  235. if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
  236. [NSFileManager.defaultManager removeItemAtPath:tempPath error:&error];
  237. if (error) {
  238. callback(error.localizedDescription);
  239. return;
  240. }
  241. }
  242. // Attempt to create and populate the database, abort if we fail
  243. FLEXRuntimeExporter *exporter = [self new];
  244. exporter.loadedBundlePaths = images;
  245. if (![exporter createAndPopulateDatabaseAtPath:tempPath
  246. progressHandler:progress
  247. error:&errorMessage]) {
  248. // Remove temp database if it was not moved
  249. if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
  250. [NSFileManager.defaultManager removeItemAtPath:tempPath error:nil];
  251. }
  252. callback(errorMessage);
  253. return;
  254. }
  255. // Remove old database at given path
  256. if ([NSFileManager.defaultManager fileExistsAtPath:path]) {
  257. [NSFileManager.defaultManager removeItemAtPath:path error:&error];
  258. if (error) {
  259. callback(error.localizedDescription);
  260. return;
  261. }
  262. }
  263. // Move new database to desired path
  264. [NSFileManager.defaultManager moveItemAtPath:tempPath toPath:path error:&error];
  265. if (error) {
  266. callback(error.localizedDescription);
  267. }
  268. // Remove temp database if it was not moved
  269. if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
  270. [NSFileManager.defaultManager removeItemAtPath:tempPath error:nil];
  271. }
  272. callback(nil);
  273. });
  274. }
  275. - (id)init {
  276. self = [super init];
  277. if (self) {
  278. _bundlePathsToIDs = [NSMutableDictionary new];
  279. _protocolsToIDs = [NSMutableDictionary new];
  280. _classesToIDs = [NSMutableDictionary new];
  281. _typeEncodingsToIDs = [NSMutableDictionary new];
  282. _methodSignaturesToIDs = [NSMutableDictionary new];
  283. _selectorsToIDs = [NSMutableDictionary new];
  284. _bundlePathsToIDs[NSNull.null] = (id)NSNull.null;
  285. }
  286. return self;
  287. }
  288. - (BOOL)createAndPopulateDatabaseAtPath:(NSString *)path
  289. progressHandler:(void(^)(NSString *status))step
  290. error:(NSString **)error {
  291. _db = [FLEXSQLiteDatabaseManager managerForDatabase:path];
  292. [self loadMetadata:step];
  293. if ([self createTables] && [self addImages:step] && [self addProtocols:step] &&
  294. [self addClasses:step] && [self setSuperclasses:step] &&
  295. [self addProtocolConformances:step] && [self addClassConformances:step] &&
  296. [self addIvars:step] && [self addMethods:step] && [self addProperties:step]) {
  297. _db = nil; // Close the database
  298. return YES;
  299. }
  300. *error = self.db.lastResult.message;
  301. return NO;
  302. }
  303. - (void)loadMetadata:(void(^)(NSString *status))progress {
  304. progress(@"Loading metadata…");
  305. FLEXRuntimeClient *runtime = FLEXRuntimeClient.runtime;
  306. // Only load metadata for the existing paths if any
  307. if (self.loadedBundlePaths) {
  308. // Images
  309. self.loadedShortBundleNames = [self.loadedBundlePaths flex_mapped:^id(NSString *path, NSUInteger idx) {
  310. return [runtime shortNameForImageName:path];
  311. }];
  312. // Classes
  313. self.classes = [[runtime classesForToken:FLEXSearchToken.any
  314. inBundles:self.loadedBundlePaths.mutableCopy
  315. ] flex_mapped:^id(NSString *cls, NSUInteger idx) {
  316. return NSClassFromString(cls);
  317. }];
  318. } else {
  319. // Images
  320. self.loadedShortBundleNames = runtime.imageDisplayNames;
  321. self.loadedBundlePaths = [self.loadedShortBundleNames flex_mapped:^id(NSString *name, NSUInteger idx) {
  322. return [runtime imageNameForShortName:name];
  323. }];
  324. // Classes
  325. self.classes = [runtime copySafeClassList];
  326. }
  327. // ...except protocols, because there's not a lot of them
  328. // and there's no way load the protocols for a given image
  329. self.protocols = [[runtime copyProtocolList] flex_mapped:^id(Protocol *proto, NSUInteger idx) {
  330. return [FLEXProtocol protocol:proto];
  331. }];
  332. }
  333. - (BOOL)createTables {
  334. NSArray<NSString *> *commands = @[
  335. kFREEnableForeignKeys,
  336. kFRECreateTableMachOCommand,
  337. kFRECreateTableClassCommand,
  338. kFRECreateTableSelectorCommand,
  339. kFRECreateTableTypeEncodingCommand,
  340. kFRECreateTableTypeSignatureCommand,
  341. kFRECreateTableMethodSignatureCommand,
  342. kFRECreateTableMethodCommand,
  343. kFRECreateTablePropertyCommand,
  344. kFRECreateTableIvarCommand,
  345. kFRECreateTableProtocolCommand,
  346. kFRECreateTableProtocolPropertyCommand,
  347. kFRECreateTableProtocolConformanceCommand,
  348. kFRECreateTableClassConformanceCommand
  349. ];
  350. for (NSString *command in commands) {
  351. if (![self.db executeStatement:command]) {
  352. return NO;
  353. }
  354. }
  355. return YES;
  356. }
  357. - (BOOL)addImages:(void(^)(NSString *status))progress {
  358. progress(@"Adding loaded images…");
  359. FLEXSQLiteDatabaseManager *database = self.db;
  360. NSArray *shortNames = self.loadedShortBundleNames;
  361. NSArray *fullPaths = self.loadedBundlePaths;
  362. NSParameterAssert(shortNames.count == fullPaths.count);
  363. NSInteger count = shortNames.count;
  364. for (NSInteger i = 0; i < count; i++) {
  365. // Grab bundle ID
  366. NSString *bundleID = [NSBundle
  367. bundleWithPath:fullPaths[i]
  368. ].bundleIdentifier;
  369. [database executeStatement:kFREInsertImage arguments:@{
  370. @"$shortName": shortNames[i],
  371. @"$imagePath": fullPaths[i],
  372. @"$bundleID": bundleID ?: NSNull.null
  373. }];
  374. if (database.lastResult.isError) {
  375. return NO;
  376. } else {
  377. self.bundlePathsToIDs[fullPaths[i]] = @(database.lastRowID);
  378. }
  379. }
  380. return YES;
  381. }
  382. NS_INLINE BOOL FREInsertProtocolMember(FLEXSQLiteDatabaseManager *db,
  383. id proto, id required, id instance,
  384. id prop, id methSel, id image) {
  385. return ![db executeStatement:kFREInsertProtocolMember arguments:@{
  386. @"$protocol": proto,
  387. @"$required": required,
  388. @"$instance": instance ?: NSNull.null,
  389. @"$property": prop ?: NSNull.null,
  390. @"$method": methSel ?: NSNull.null,
  391. @"$image": image
  392. }].isError;
  393. }
  394. - (BOOL)addProtocols:(void(^)(NSString *status))progress {
  395. progress([NSString stringWithFormat:@"Adding %@ protocols…", @(self.protocols.count)]);
  396. FLEXSQLiteDatabaseManager *database = self.db;
  397. NSDictionary *imageIDs = self.bundlePathsToIDs;
  398. for (FLEXProtocol *proto in self.protocols) {
  399. id imagePath = proto.imagePath ?: NSNull.null;
  400. NSNumber *image = imageIDs[imagePath] ?: NSNull.null;
  401. NSNumber *pid = nil;
  402. // Insert protocol
  403. BOOL failed = [database executeStatement:kFREInsertProtocol arguments:@{
  404. @"$name": proto.name, @"$image": image
  405. }].isError;
  406. // Cache rowid
  407. if (failed) {
  408. return NO;
  409. } else {
  410. self.protocolsToIDs[proto.name] = pid = @(database.lastRowID);
  411. }
  412. // Insert its members //
  413. // Required methods
  414. for (FLEXMethodDescription *method in proto.requiredMethods) {
  415. NSString *selector = NSStringFromSelector(method.selector);
  416. if (!FREInsertProtocolMember(database, pid, @YES, method.instance, nil, selector, image)) {
  417. return NO;
  418. }
  419. }
  420. // Optional methods
  421. for (FLEXMethodDescription *method in proto.optionalMethods) {
  422. NSString *selector = NSStringFromSelector(method.selector);
  423. if (!FREInsertProtocolMember(database, pid, @NO, method.instance, nil, selector, image)) {
  424. return NO;
  425. }
  426. }
  427. if (@available(iOS 10, *)) {
  428. // Required properties
  429. for (FLEXProperty *property in proto.requiredProperties) {
  430. BOOL success = FREInsertProtocolMember(
  431. database, pid, @YES, @(property.isClassProperty), property.name, NSNull.null, image
  432. );
  433. if (!success) return NO;
  434. }
  435. // Optional properties
  436. for (FLEXProperty *property in proto.optionalProperties) {
  437. BOOL success = FREInsertProtocolMember(
  438. database, pid, @NO, @(property.isClassProperty), property.name, NSNull.null, image
  439. );
  440. if (!success) return NO;
  441. }
  442. } else {
  443. // Just... properties.
  444. for (FLEXProperty *property in proto.properties) {
  445. BOOL success = FREInsertProtocolMember(
  446. database, pid, nil, @(property.isClassProperty), property.name, NSNull.null, image
  447. );
  448. if (!success) return NO;
  449. }
  450. }
  451. }
  452. return YES;
  453. }
  454. - (BOOL)addProtocolConformances:(void(^)(NSString *status))progress {
  455. progress(@"Adding protocol-to-protocol conformances…");
  456. FLEXSQLiteDatabaseManager *database = self.db;
  457. NSDictionary *protocolIDs = self.protocolsToIDs;
  458. for (FLEXProtocol *proto in self.protocols) {
  459. id protoID = protocolIDs[proto.name];
  460. for (FLEXProtocol *conform in proto.protocols) {
  461. BOOL failed = [database executeStatement:kFREInsertProtocolConformance arguments:@{
  462. @"$protocol": protoID,
  463. @"$conformance": protocolIDs[conform.name]
  464. }].isError;
  465. if (failed) {
  466. return NO;
  467. }
  468. }
  469. }
  470. return YES;
  471. }
  472. - (BOOL)addClasses:(void(^)(NSString *status))progress {
  473. progress([NSString stringWithFormat:@"Adding %@ classes…", @(self.classes.count)]);
  474. FLEXSQLiteDatabaseManager *database = self.db;
  475. NSDictionary *imageIDs = self.bundlePathsToIDs;
  476. for (Class cls in self.classes) {
  477. const char *imageName = class_getImageName(cls);
  478. id image = imageName ? imageIDs[@(imageName)] : NSNull.null;
  479. image = image ?: NSNull.null;
  480. BOOL failed = [database executeStatement:kFREInsertClass arguments:@{
  481. @"$className": NSStringFromClass(cls),
  482. @"$instanceSize": @(class_getInstanceSize(cls)),
  483. @"$version": @(class_getVersion(cls)),
  484. @"$image": image
  485. }].isError;
  486. if (failed) {
  487. return NO;
  488. } else {
  489. self.classesToIDs[(id)cls] = @(database.lastRowID);
  490. }
  491. }
  492. return YES;
  493. }
  494. - (BOOL)setSuperclasses:(void(^)(NSString *status))progress {
  495. progress(@"Setting superclasses…");
  496. FLEXSQLiteDatabaseManager *database = self.db;
  497. for (Class cls in self.classes) {
  498. // Grab superclass ID
  499. Class superclass = class_getSuperclass(cls);
  500. NSNumber *superclassID = _classesToIDs[class_getSuperclass(cls)];
  501. // ... or add the superclass and cache its ID if the
  502. // superclass does not reside in the target image(s)
  503. if (!superclassID) {
  504. NSDictionary *args = @{ @"$className": NSStringFromClass(superclass) };
  505. BOOL failed = [database executeStatement:kFREInsertClass arguments:args].isError;
  506. if (failed) { return NO; }
  507. _classesToIDs[(id)superclass] = superclassID = @(database.lastRowID);
  508. }
  509. if (superclass) {
  510. BOOL failed = [database executeStatement:kFREUpdateClassSetSuper arguments:@{
  511. @"$super": superclassID, @"$id": _classesToIDs[cls]
  512. }].isError;
  513. if (failed) {
  514. return NO;
  515. }
  516. }
  517. }
  518. return YES;
  519. }
  520. - (BOOL)addClassConformances:(void(^)(NSString *status))progress {
  521. progress(@"Adding class-to-protocol conformances…");
  522. FLEXSQLiteDatabaseManager *database = self.db;
  523. NSDictionary *protocolIDs = self.protocolsToIDs;
  524. NSDictionary *classIDs = self.classesToIDs;
  525. for (Class cls in self.classes) {
  526. id classID = classIDs[(id)cls];
  527. for (FLEXProtocol *conform in FLEXGetConformedProtocols(cls)) {
  528. BOOL failed = [database executeStatement:kFREInsertClassConformance arguments:@{
  529. @"$class": classID,
  530. @"$conformance": protocolIDs[conform.name]
  531. }].isError;
  532. if (failed) {
  533. return NO;
  534. }
  535. }
  536. }
  537. return YES;
  538. }
  539. - (BOOL)addIvars:(void(^)(NSString *status))progress {
  540. progress(@"Adding ivars…");
  541. FLEXSQLiteDatabaseManager *database = self.db;
  542. NSDictionary *imageIDs = self.bundlePathsToIDs;
  543. for (Class cls in self.classes) {
  544. for (FLEXIvar *ivar in FLEXGetAllIvars(cls)) {
  545. // Insert type first
  546. if (![self addTypeEncoding:ivar.typeEncoding size:ivar.size]) {
  547. return NO;
  548. }
  549. id imagePath = ivar.imagePath ?: NSNull.null;
  550. NSNumber *image = imageIDs[imagePath] ?: NSNull.null;
  551. BOOL failed = [database executeStatement:kFREInsertIvar arguments:@{
  552. @"$name": ivar.name,
  553. @"$offset": @(ivar.offset),
  554. @"$type": _typeEncodingsToIDs[ivar.typeEncoding],
  555. @"$class": _classesToIDs[cls],
  556. @"$image": image
  557. }].isError;
  558. if (failed) {
  559. return NO;
  560. }
  561. }
  562. }
  563. return YES;
  564. }
  565. - (BOOL)addMethods:(void(^)(NSString *status))progress {
  566. progress(@"Adding methods…");
  567. FLEXSQLiteDatabaseManager *database = self.db;
  568. NSDictionary *imageIDs = self.bundlePathsToIDs;
  569. // Loop over all classes
  570. for (Class cls in self.classes) {
  571. NSNumber *classID = _classesToIDs[(id)cls];
  572. const char *imageName = class_getImageName(cls);
  573. id image = imageName ? imageIDs[@(imageName)] : NSNull.null;
  574. image = image ?: NSNull.null;
  575. // Block used to process each message
  576. BOOL (^insert)(FLEXMethod *, NSNumber *) = ^BOOL(FLEXMethod *method, NSNumber *instance) {
  577. // Insert selector and signature first
  578. if (![self addSelector:method.selectorString]) {
  579. return NO;
  580. }
  581. if (![self addMethodSignature:method]) {
  582. return NO;
  583. }
  584. return ![database executeStatement:kFREInsertMethod arguments:@{
  585. @"$sel": self->_selectorsToIDs[method.selectorString],
  586. @"$class": classID,
  587. @"$instance": instance,
  588. @"$signature": self->_methodSignaturesToIDs[method.signatureString],
  589. @"$image": image
  590. }].isError;
  591. };
  592. // Loop over all instance and class methods of that class //
  593. for (FLEXMethod *method in FLEXGetAllMethods(cls, YES)) {
  594. if (!insert(method, @YES)) {
  595. return NO;
  596. }
  597. }
  598. for (FLEXMethod *method in FLEXGetAllMethods(object_getClass(cls), NO)) {
  599. if (!insert(method, @NO)) {
  600. return NO;
  601. }
  602. }
  603. }
  604. return YES;
  605. }
  606. - (BOOL)addProperties:(void(^)(NSString *status))progress {
  607. progress(@"Adding properties…");
  608. FLEXSQLiteDatabaseManager *database = self.db;
  609. NSDictionary *imageIDs = self.bundlePathsToIDs;
  610. // Loop over all classes
  611. for (Class cls in self.classes) {
  612. NSNumber *classID = _classesToIDs[(id)cls];
  613. // Block used to process each message
  614. BOOL (^insert)(FLEXProperty *, NSNumber *) = ^BOOL(FLEXProperty *property, NSNumber *instance) {
  615. FLEXPropertyAttributes *attrs = property.attributes;
  616. NSString *customGetter = attrs.customGetterString;
  617. NSString *customSetter = attrs.customSetterString;
  618. // Insert selectors first
  619. if (customGetter) {
  620. if (![self addSelector:customGetter]) {
  621. return NO;
  622. }
  623. }
  624. if (customSetter) {
  625. if (![self addSelector:customSetter]) {
  626. return NO;
  627. }
  628. }
  629. // Insert type encoding first
  630. NSInteger size = [FLEXTypeEncodingParser
  631. sizeForTypeEncoding:attrs.typeEncoding alignment:nil
  632. ];
  633. if (![self addTypeEncoding:attrs.typeEncoding size:size]) {
  634. return NO;
  635. }
  636. id imagePath = property.imagePath ?: NSNull.null;
  637. id image = imageIDs[imagePath] ?: NSNull.null;
  638. return ![database executeStatement:kFREInsertProperty arguments:@{
  639. @"$name": property.name,
  640. @"$class": classID,
  641. @"$instance": instance,
  642. @"$image": image,
  643. @"$attributes": attrs.string,
  644. @"$customGetter": self->_selectorsToIDs[customGetter] ?: NSNull.null,
  645. @"$customSetter": self->_selectorsToIDs[customSetter] ?: NSNull.null,
  646. @"$type": self->_typeEncodingsToIDs[attrs.typeEncoding] ?: NSNull.null,
  647. @"$ivar": attrs.backingIvar ?: NSNull.null,
  648. @"$readonly": @(attrs.isReadOnly),
  649. @"$copy": @(attrs.isCopy),
  650. @"$retained": @(attrs.isRetained),
  651. @"$nonatomic": @(attrs.isNonatomic),
  652. @"$dynamic": @(attrs.isDynamic),
  653. @"$weak": @(attrs.isWeak),
  654. @"$canGC": @(attrs.isGarbageCollectable),
  655. }].isError;
  656. };
  657. // Loop over all instance and class methods of that class //
  658. for (FLEXProperty *property in FLEXGetAllProperties(cls)) {
  659. if (!insert(property, @YES)) {
  660. return NO;
  661. }
  662. }
  663. for (FLEXProperty *property in FLEXGetAllProperties(object_getClass(cls))) {
  664. if (!insert(property, @NO)) {
  665. return NO;
  666. }
  667. }
  668. }
  669. return YES;
  670. }
  671. - (BOOL)addSelector:(NSString *)sel {
  672. return [self executeInsert:kFREInsertSelector args:@{
  673. @"$name": sel
  674. } key:sel cacheResult:_selectorsToIDs];
  675. }
  676. - (BOOL)addTypeEncoding:(NSString *)type size:(NSInteger)size {
  677. return [self executeInsert:kFREInsertTypeEncoding args:@{
  678. @"$type": type, @"$size": @(size)
  679. } key:type cacheResult:_typeEncodingsToIDs];
  680. }
  681. - (BOOL)addMethodSignature:(FLEXMethod *)method {
  682. NSString *signature = method.signatureString;
  683. NSString *returnType = @((char *)method.returnType);
  684. // Insert return type first
  685. if (![self addTypeEncoding:returnType size:method.returnSize]) {
  686. return NO;
  687. }
  688. return [self executeInsert:kFREInsertMethodSignature args:@{
  689. @"$typeEncoding": signature,
  690. @"$returnType": _typeEncodingsToIDs[returnType],
  691. @"$argc": @(method.numberOfArguments),
  692. @"$frameLength": @(method.signature.frameLength)
  693. } key:signature cacheResult:_methodSignaturesToIDs];
  694. }
  695. - (BOOL)executeInsert:(NSString *)statement
  696. args:(NSDictionary *)args
  697. key:(NSString *)cacheKey
  698. cacheResult:(NSMutableDictionary<NSString *, NSNumber *> *)rowids {
  699. // Check if already inserted
  700. if (rowids[cacheKey]) {
  701. return YES;
  702. }
  703. // Insert
  704. FLEXSQLiteDatabaseManager *database = _db;
  705. [database executeStatement:statement arguments:args];
  706. if (database.lastResult.isError) {
  707. return NO;
  708. }
  709. // Cache rowid
  710. rowids[cacheKey] = @(database.lastRowID);
  711. return YES;
  712. }
  713. @end