FLEXRuntimeExporter.m 30 KB

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