added files via upload

This commit is contained in:
Balackburn
2023-06-27 09:54:41 +02:00
commit 2ff6aac218
1420 changed files with 88898 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
//
// FLEXRuntimeClient.h
// FLEX
//
// Created by Tanner on 3/22/17.
// Copyright © 2017 Tanner Bennett. All rights reserved.
//
#import "FLEXSearchToken.h"
@class FLEXMethod;
/// Accepts runtime queries given a token.
@interface FLEXRuntimeClient : NSObject
@property (nonatomic, readonly, class) FLEXRuntimeClient *runtime;
/// Called automatically when \c FLEXRuntime is first used.
/// You may call it again when you think a library has
/// been loaded since this method was first called.
- (void)reloadLibrariesList;
/// You must call this method on the main thread
/// before you attempt to call \c copySafeClassList.
+ (void)initializeWebKitLegacy;
/// Do not call unless you absolutely need all classes. This will cause
/// every class in the runtime to initialize itself, which is not common.
/// Before you call this method, call \c initializeWebKitLegacy on the main thread.
- (NSArray<Class> *)copySafeClassList;
- (NSArray<Protocol *> *)copyProtocolList;
/// An array of strings representing the currently loaded libraries.
@property (nonatomic, readonly) NSArray<NSString *> *imageDisplayNames;
/// "Image name" is the path of the bundle
- (NSString *)shortNameForImageName:(NSString *)imageName;
/// "Image name" is the path of the bundle
- (NSString *)imageNameForShortName:(NSString *)imageName;
/// @return Bundle names for the UI
- (NSMutableArray<NSString *> *)bundleNamesForToken:(FLEXSearchToken *)token;
/// @return Bundle paths for more queries
- (NSMutableArray<NSString *> *)bundlePathsForToken:(FLEXSearchToken *)token;
/// @return Class names
- (NSMutableArray<NSString *> *)classesForToken:(FLEXSearchToken *)token
inBundles:(NSMutableArray<NSString *> *)bundlePaths;
/// @return A list of lists of \c FLEXMethods where
/// each list corresponds to one of the given classes
- (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForToken:(FLEXSearchToken *)token
instance:(NSNumber *)onlyInstanceMethods
inClasses:(NSArray<NSString *> *)classes;
@end

View File

@@ -0,0 +1,416 @@
//
// FLEXRuntimeClient.m
// FLEX
//
// Created by Tanner on 3/22/17.
// Copyright © 2017 Tanner Bennett. All rights reserved.
//
#import "FLEXRuntimeClient.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXMethod.h"
#import "NSArray+FLEX.h"
#import "FLEXRuntimeSafety.h"
#include <dlfcn.h>
#define Equals(a, b) ([a compare:b options:NSCaseInsensitiveSearch] == NSOrderedSame)
#define Contains(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location != NSNotFound)
#define HasPrefix(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location == 0)
#define HasSuffix(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location == (a.length - b.length))
@interface FLEXRuntimeClient () {
NSMutableArray<NSString *> *_imageDisplayNames;
}
@property (nonatomic) NSMutableDictionary *bundles_pathToShort;
@property (nonatomic) NSMutableDictionary *bundles_shortToPath;
@property (nonatomic) NSCache *bundles_pathToClassNames;
@property (nonatomic) NSMutableArray<NSString *> *imagePaths;
@end
/// @return success if the map passes.
static inline NSString * TBWildcardMap_(NSString *token, NSString *candidate, NSString *success, TBWildcardOptions options) {
switch (options) {
case TBWildcardOptionsNone:
// Only "if equals"
if (Equals(candidate, token)) {
return success;
}
default: {
// Only "if contains"
if (options & TBWildcardOptionsPrefix &&
options & TBWildcardOptionsSuffix) {
if (Contains(candidate, token)) {
return success;
}
}
// Only "if candidate ends with with token"
else if (options & TBWildcardOptionsPrefix) {
if (HasSuffix(candidate, token)) {
return success;
}
}
// Only "if candidate starts with with token"
else if (options & TBWildcardOptionsSuffix) {
// Case like "Bundle." where we want "" to match anything
if (!token.length) {
return success;
}
if (HasPrefix(candidate, token)) {
return success;
}
}
}
}
return nil;
}
/// @return candidate if the map passes.
static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBWildcardOptions options) {
return TBWildcardMap_(token, candidate, candidate, options);
}
@implementation FLEXRuntimeClient
#pragma mark - Initialization
+ (instancetype)runtime {
static FLEXRuntimeClient *runtime;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
runtime = [self new];
[runtime reloadLibrariesList];
});
return runtime;
}
- (id)init {
self = [super init];
if (self) {
_imagePaths = [NSMutableArray new];
_bundles_pathToShort = [NSMutableDictionary new];
_bundles_shortToPath = [NSMutableDictionary new];
_bundles_pathToClassNames = [NSCache new];
}
return self;
}
#pragma mark - Private
- (void)reloadLibrariesList {
unsigned int imageCount = 0;
const char **imageNames = objc_copyImageNames(&imageCount);
if (imageNames) {
NSMutableArray *imageNameStrings = [NSMutableArray flex_forEachUpTo:imageCount map:^NSString *(NSUInteger i) {
return @(imageNames[i]);
}];
self.imagePaths = imageNameStrings;
free(imageNames);
// Sort alphabetically
[imageNameStrings sortUsingComparator:^NSComparisonResult(NSString *name1, NSString *name2) {
NSString *shortName1 = [self shortNameForImageName:name1];
NSString *shortName2 = [self shortNameForImageName:name2];
return [shortName1 caseInsensitiveCompare:shortName2];
}];
// Cache image display names
_imageDisplayNames = [imageNameStrings flex_mapped:^id(NSString *path, NSUInteger idx) {
return [self shortNameForImageName:path];
}];
}
}
- (NSString *)shortNameForImageName:(NSString *)imageName {
// Cache
NSString *shortName = _bundles_pathToShort[imageName];
if (shortName) {
return shortName;
}
NSArray *components = [imageName componentsSeparatedByString:@"/"];
if (components.count >= 2) {
NSString *parentDir = components[components.count - 2];
if ([parentDir hasSuffix:@".framework"] || [parentDir hasSuffix:@".axbundle"]) {
if ([imageName hasSuffix:@".dylib"]) {
shortName = imageName.lastPathComponent;
} else {
shortName = parentDir;
}
}
}
if (!shortName) {
shortName = imageName.lastPathComponent;
}
_bundles_pathToShort[imageName] = shortName;
_bundles_shortToPath[shortName] = imageName;
return shortName;
}
- (NSString *)imageNameForShortName:(NSString *)imageName {
return _bundles_shortToPath[imageName];
}
- (NSMutableArray<NSString *> *)classNamesInImageAtPath:(NSString *)path {
// Check cache
NSMutableArray *classNameStrings = [_bundles_pathToClassNames objectForKey:path];
if (classNameStrings) {
return classNameStrings.mutableCopy;
}
unsigned int classCount = 0;
const char **classNames = objc_copyClassNamesForImage(path.UTF8String, &classCount);
if (classNames) {
classNameStrings = [NSMutableArray flex_forEachUpTo:classCount map:^id(NSUInteger i) {
return @(classNames[i]);
}];
free(classNames);
[classNameStrings sortUsingSelector:@selector(caseInsensitiveCompare:)];
[_bundles_pathToClassNames setObject:classNameStrings forKey:path];
return classNameStrings.mutableCopy;
}
return [NSMutableArray new];
}
#pragma mark - Public
+ (void)initializeWebKitLegacy {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void *handle = dlopen(
"/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
RTLD_LAZY
);
void (*WebKitInitialize)(void) = dlsym(handle, "WebKitInitialize");
if (WebKitInitialize) {
NSAssert(NSThread.isMainThread,
@"WebKitInitialize can only be called on the main thread"
);
WebKitInitialize();
}
});
}
- (NSArray<Class> *)copySafeClassList {
unsigned int count = 0;
Class *classes = objc_copyClassList(&count);
return [NSArray flex_forEachUpTo:count map:^id(NSUInteger i) {
Class cls = classes[i];
return FLEXClassIsSafe(cls) ? cls : nil;
}];
}
- (NSArray<Protocol *> *)copyProtocolList {
unsigned int count = 0;
Protocol *__unsafe_unretained *protocols = objc_copyProtocolList(&count);
return [NSArray arrayWithObjects:protocols count:count];
}
- (NSMutableArray<NSString *> *)bundleNamesForToken:(FLEXSearchToken *)token {
if (self.imagePaths.count) {
TBWildcardOptions options = token.options;
NSString *query = token.string;
// Optimization, avoid a loop
if (options == TBWildcardOptionsAny) {
return _imageDisplayNames;
}
// No dot syntax because imageDisplayNames is only mutable internally
return [_imageDisplayNames flex_mapped:^id(NSString *binary, NSUInteger idx) {
// NSString *UIName = [self shortNameForImageName:binary];
return TBWildcardMap(query, binary, options);
}];
}
return [NSMutableArray new];
}
- (NSMutableArray<NSString *> *)bundlePathsForToken:(FLEXSearchToken *)token {
if (self.imagePaths.count) {
TBWildcardOptions options = token.options;
NSString *query = token.string;
// Optimization, avoid a loop
if (options == TBWildcardOptionsAny) {
return self.imagePaths;
}
return [self.imagePaths flex_mapped:^id(NSString *binary, NSUInteger idx) {
NSString *UIName = [self shortNameForImageName:binary];
// If query == UIName, -> binary
return TBWildcardMap_(query, UIName, binary, options);
}];
}
return [NSMutableArray new];
}
- (NSMutableArray<NSString *> *)classesForToken:(FLEXSearchToken *)token inBundles:(NSMutableArray<NSString *> *)bundles {
// Edge case where token is the class we want already; return superclasses
if (token.isAbsolute) {
if (FLEXClassIsSafe(NSClassFromString(token.string))) {
return [NSMutableArray arrayWithObject:token.string];
}
return [NSMutableArray new];
}
if (bundles.count) {
// Get class names, remove unsafe classes
NSMutableArray<NSString *> *names = [self _classesForToken:token inBundles:bundles];
return [names flex_mapped:^NSString *(NSString *name, NSUInteger idx) {
Class cls = NSClassFromString(name);
BOOL safe = FLEXClassIsSafe(cls);
return safe ? name : nil;
}];
}
return [NSMutableArray new];
}
- (NSMutableArray<NSString *> *)_classesForToken:(FLEXSearchToken *)token inBundles:(NSMutableArray<NSString *> *)bundles {
TBWildcardOptions options = token.options;
NSString *query = token.string;
// Optimization, avoid unnecessary sorting
if (bundles.count == 1) {
// Optimization, avoid a loop
if (options == TBWildcardOptionsAny) {
return [self classNamesInImageAtPath:bundles.firstObject];
}
return [[self classNamesInImageAtPath:bundles.firstObject] flex_mapped:^id(NSString *className, NSUInteger idx) {
return TBWildcardMap(query, className, options);
}];
}
else {
// Optimization, avoid a loop
if (options == TBWildcardOptionsAny) {
return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
return [self classNamesInImageAtPath:bundlePath];
}] flex_sortedUsingSelector:@selector(caseInsensitiveCompare:)];
}
return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
return [[self classNamesInImageAtPath:bundlePath] flex_mapped:^id(NSString *className, NSUInteger idx) {
return TBWildcardMap(query, className, options);
}];
}] flex_sortedUsingSelector:@selector(caseInsensitiveCompare:)];
}
}
- (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForToken:(FLEXSearchToken *)token
instance:(NSNumber *)checkInstance
inClasses:(NSArray<NSString *> *)classes {
if (classes.count) {
TBWildcardOptions options = token.options;
BOOL instance = checkInstance.boolValue;
NSString *selector = token.string;
switch (options) {
// In practice I don't think this case is ever used with methods,
// since they will always have a suffix wildcard at the end
case TBWildcardOptionsNone: {
SEL sel = (SEL)selector.UTF8String;
return @[[classes flex_mapped:^id(NSString *name, NSUInteger idx) {
Class cls = NSClassFromString(name);
// Use metaclass if not instance
if (!instance) {
cls = object_getClass(cls);
}
// Method is absolute
return [FLEXMethod selector:sel class:cls];
}]];
}
case TBWildcardOptionsAny: {
return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
// Any means `instance` was not specified
Class cls = NSClassFromString(name);
return [cls flex_allMethods];
}];
}
default: {
// Only "if contains"
if (options & TBWildcardOptionsPrefix &&
options & TBWildcardOptionsSuffix) {
return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
Class cls = NSClassFromString(name);
return [[cls flex_allMethods] flex_mapped:^id(FLEXMethod *method, NSUInteger idx) {
// Method is a prefix-suffix wildcard
if (Contains(method.selectorString, selector)) {
return method;
}
return nil;
}];
}];
}
// Only "if method ends with with selector"
else if (options & TBWildcardOptionsPrefix) {
return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
Class cls = NSClassFromString(name);
return [[cls flex_allMethods] flex_mapped:^id(FLEXMethod *method, NSUInteger idx) {
// Method is a prefix wildcard
if (HasSuffix(method.selectorString, selector)) {
return method;
}
return nil;
}];
}];
}
// Only "if method starts with with selector"
else if (options & TBWildcardOptionsSuffix) {
assert(checkInstance);
return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
Class cls = NSClassFromString(name);
// Case like "Bundle.class.-" where we want "-" to match anything
if (!selector.length) {
if (instance) {
return [cls flex_allInstanceMethods];
} else {
return [cls flex_allClassMethods];
}
}
id mapping = ^id(FLEXMethod *method) {
// Method is a suffix wildcard
if (HasPrefix(method.selectorString, selector)) {
return method;
}
return nil;
};
if (instance) {
return [[cls flex_allInstanceMethods] flex_mapped:mapping];
} else {
return [[cls flex_allClassMethods] flex_mapped:mapping];
}
}];
}
}
}
}
return [NSMutableArray new];
}
@end

View File

@@ -0,0 +1,36 @@
//
// FLEXRuntimeController.h
// FLEX
//
// Created by Tanner on 3/23/17.
// Copyright © 2017 Tanner Bennett. All rights reserved.
//
#import "FLEXRuntimeKeyPath.h"
/// Wraps FLEXRuntimeClient and provides extra caching mechanisms
@interface FLEXRuntimeController : NSObject
/// @return An array of strings if the key path only evaluates
/// to a class or bundle; otherwise, a list of lists of FLEXMethods.
+ (NSArray *)dataForKeyPath:(FLEXRuntimeKeyPath *)keyPath;
/// Useful when you need to specify which classes to search in.
/// \c dataForKeyPath: will only search classes matching the class key.
/// We use this elsewhere when we need to search a class hierarchy.
+ (NSArray<NSArray<FLEXMethod *> *> *)methodsForToken:(FLEXSearchToken *)token
instance:(NSNumber *)onlyInstanceMethods
inClasses:(NSArray<NSString*> *)classes;
/// Useful when you need the classes that are associated with the
/// double list of methods returned from \c dataForKeyPath
+ (NSMutableArray<NSString *> *)classesForKeyPath:(FLEXRuntimeKeyPath *)keyPath;
+ (NSString *)shortBundleNameForClass:(NSString *)name;
+ (NSString *)imagePathWithShortName:(NSString *)suffix;
/// Gives back short names. For example, "Foundation.framework"
+ (NSArray<NSString*> *)allBundleNames;
@end

View File

@@ -0,0 +1,192 @@
//
// FLEXRuntimeController.m
// FLEX
//
// Created by Tanner on 3/23/17.
// Copyright © 2017 Tanner Bennett. All rights reserved.
//
#import "FLEXRuntimeController.h"
#import "FLEXRuntimeClient.h"
#import "FLEXMethod.h"
@interface FLEXRuntimeController ()
@property (nonatomic, readonly) NSCache *bundlePathsCache;
@property (nonatomic, readonly) NSCache *bundleNamesCache;
@property (nonatomic, readonly) NSCache *classNamesCache;
@property (nonatomic, readonly) NSCache *methodsCache;
@end
@implementation FLEXRuntimeController
#pragma mark Initialization
static FLEXRuntimeController *controller = nil;
+ (instancetype)shared {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
controller = [self new];
});
return controller;
}
- (id)init {
self = [super init];
if (self) {
_bundlePathsCache = [NSCache new];
_bundleNamesCache = [NSCache new];
_classNamesCache = [NSCache new];
_methodsCache = [NSCache new];
}
return self;
}
#pragma mark Public
+ (NSArray *)dataForKeyPath:(FLEXRuntimeKeyPath *)keyPath {
if (keyPath.bundleKey) {
if (keyPath.classKey) {
if (keyPath.methodKey) {
return [[self shared] methodsForKeyPath:keyPath];
} else {
return [[self shared] classesForKeyPath:keyPath];
}
} else {
return [[self shared] bundleNamesForToken:keyPath.bundleKey];
}
} else {
return @[];
}
}
+ (NSArray<NSArray<FLEXMethod *> *> *)methodsForToken:(FLEXSearchToken *)token
instance:(NSNumber *)inst
inClasses:(NSArray<NSString*> *)classes {
return [FLEXRuntimeClient.runtime
methodsForToken:token
instance:inst
inClasses:classes
];
}
+ (NSMutableArray<NSString *> *)classesForKeyPath:(FLEXRuntimeKeyPath *)keyPath {
return [[self shared] classesForKeyPath:keyPath];
}
+ (NSString *)shortBundleNameForClass:(NSString *)name {
const char *imageName = class_getImageName(NSClassFromString(name));
if (!imageName) {
return @"(unspecified)";
}
return [FLEXRuntimeClient.runtime shortNameForImageName:@(imageName)];
}
+ (NSString *)imagePathWithShortName:(NSString *)suffix {
return [FLEXRuntimeClient.runtime imageNameForShortName:suffix];
}
+ (NSArray *)allBundleNames {
return FLEXRuntimeClient.runtime.imageDisplayNames;
}
#pragma mark Private
- (NSMutableArray *)bundlePathsForToken:(FLEXSearchToken *)token {
// Only cache if no wildcard
BOOL shouldCache = token == TBWildcardOptionsNone;
if (shouldCache) {
NSMutableArray<NSString*> *cached = [self.bundlePathsCache objectForKey:token];
if (cached) {
return cached;
}
NSMutableArray<NSString*> *bundles = [FLEXRuntimeClient.runtime bundlePathsForToken:token];
[self.bundlePathsCache setObject:bundles forKey:token];
return bundles;
}
else {
return [FLEXRuntimeClient.runtime bundlePathsForToken:token];
}
}
- (NSMutableArray<NSString *> *)bundleNamesForToken:(FLEXSearchToken *)token {
// Only cache if no wildcard
BOOL shouldCache = token == TBWildcardOptionsNone;
if (shouldCache) {
NSMutableArray<NSString*> *cached = [self.bundleNamesCache objectForKey:token];
if (cached) {
return cached;
}
NSMutableArray<NSString*> *bundles = [FLEXRuntimeClient.runtime bundleNamesForToken:token];
[self.bundleNamesCache setObject:bundles forKey:token];
return bundles;
}
else {
return [FLEXRuntimeClient.runtime bundleNamesForToken:token];
}
}
- (NSMutableArray<NSString *> *)classesForKeyPath:(FLEXRuntimeKeyPath *)keyPath {
FLEXSearchToken *classToken = keyPath.classKey;
FLEXSearchToken *bundleToken = keyPath.bundleKey;
// Only cache if no wildcard
BOOL shouldCache = bundleToken.options == 0 && classToken.options == 0;
NSString *key = nil;
if (shouldCache) {
key = [@[bundleToken.description, classToken.description] componentsJoinedByString:@"+"];
NSMutableArray<NSString *> *cached = [self.classNamesCache objectForKey:key];
if (cached) {
return cached;
}
}
NSMutableArray<NSString *> *bundles = [self bundlePathsForToken:bundleToken];
NSMutableArray<NSString *> *classes = [FLEXRuntimeClient.runtime
classesForToken:classToken inBundles:bundles
];
if (shouldCache) {
[self.classNamesCache setObject:classes forKey:key];
}
return classes;
}
- (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForKeyPath:(FLEXRuntimeKeyPath *)keyPath {
// Only cache if no wildcard, but check cache anyway bc I'm lazy
NSArray<NSMutableArray *> *cached = [self.methodsCache objectForKey:keyPath];
if (cached) {
return cached;
}
NSArray<NSString *> *classes = [self classesForKeyPath:keyPath];
NSArray<NSMutableArray<FLEXMethod *> *> *methodLists = [FLEXRuntimeClient.runtime
methodsForToken:keyPath.methodKey
instance:keyPath.instanceMethods
inClasses:classes
];
for (NSMutableArray<FLEXMethod *> *methods in methodLists) {
[methods sortUsingComparator:^NSComparisonResult(FLEXMethod *m1, FLEXMethod *m2) {
return [m1.description caseInsensitiveCompare:m2.description];
}];
}
// Only cache if no wildcard, otherwise the cache could grow very large
if (keyPath.bundleKey.isAbsolute &&
keyPath.classKey.isAbsolute) {
[self.methodsCache setObject:methodLists forKey:keyPath];
}
return methodLists;
}
@end

View File

@@ -0,0 +1,29 @@
//
// FLEXRuntimeExporter.h
// FLEX
//
// Created by Tanner Bennett on 3/26/20.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// A class for exporting all runtime metadata to an SQLite database.
//API_AVAILABLE(ios(10.0))
@interface FLEXRuntimeExporter : NSObject
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
progressHandler:(void(^)(NSString *status))progress
completion:(void(^)(NSString *_Nullable error))completion;
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
forImages:(nullable NSArray<NSString *> *)images
progressHandler:(void(^)(NSString *status))progress
completion:(void(^)(NSString *_Nullable error))completion;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,875 @@
//
// FLEXRuntimeExporter.m
// FLEX
//
// Created by Tanner Bennett on 3/26/20.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntimeExporter.h"
#import "FLEXSQLiteDatabaseManager.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXRuntimeController.h"
#import "FLEXRuntimeClient.h"
#import "NSArray+FLEX.h"
#import "FLEXTypeEncodingParser.h"
#import <sqlite3.h>
#import "FLEXProtocol.h"
#import "FLEXProperty.h"
#import "FLEXIvar.h"
#import "FLEXMethodBase.h"
#import "FLEXMethod.h"
#import "FLEXPropertyAttributes.h"
NSString * const kFREEnableForeignKeys = @"PRAGMA foreign_keys = ON;";
/// Loaded images
NSString * const kFRECreateTableMachOCommand = @"CREATE TABLE MachO( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"shortName TEXT, "
"imagePath TEXT, "
"bundleID TEXT "
");";
NSString * const kFREInsertImage = @"INSERT INTO MachO ( "
"shortName, imagePath, bundleID "
") VALUES ( "
"$shortName, $imagePath, $bundleID "
");";
/// Objc classes
NSString * const kFRECreateTableClassCommand = @"CREATE TABLE Class( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"className TEXT, "
"superclass INTEGER, "
"instanceSize INTEGER, "
"version INTEGER, "
"image INTEGER, "
"FOREIGN KEY(superclass) REFERENCES Class(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertClass = @"INSERT INTO Class ( "
"className, instanceSize, version, image "
") VALUES ( "
"$className, $instanceSize, $version, $image "
");";
NSString * const kFREUpdateClassSetSuper = @"UPDATE Class SET superclass = $super WHERE id = $id;";
/// Unique objc selectors
NSString * const kFRECreateTableSelectorCommand = @"CREATE TABLE Selector( "
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
"name text NOT NULL UNIQUE "
");";
NSString * const kFREInsertSelector = @"INSERT OR IGNORE INTO Selector (name) VALUES ($name);";
/// Unique objc type encodings
NSString * const kFRECreateTableTypeEncodingCommand = @"CREATE TABLE TypeEncoding( "
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
"string text NOT NULL UNIQUE, "
"size integer "
");";
NSString * const kFREInsertTypeEncoding = @"INSERT OR IGNORE INTO TypeEncoding "
"(string, size) VALUES ($type, $size);";
/// Unique objc type signatures
NSString * const kFRECreateTableTypeSignatureCommand = @"CREATE TABLE TypeSignature( "
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
"string text NOT NULL UNIQUE "
");";
NSString * const kFREInsertTypeSignature = @"INSERT OR IGNORE INTO TypeSignature "
"(string) VALUES ($type);";
NSString * const kFRECreateTableMethodSignatureCommand = @"CREATE TABLE MethodSignature( "
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
"typeEncoding TEXT, "
"argc INTEGER, "
"returnType INTEGER, "
"frameLength INTEGER, "
"FOREIGN KEY(returnType) REFERENCES TypeEncoding(id) "
");";
NSString * const kFREInsertMethodSignature = @"INSERT INTO MethodSignature ( "
"typeEncoding, argc, returnType, frameLength "
") VALUES ( "
"$typeEncoding, $argc, $returnType, $frameLength "
");";
NSString * const kFRECreateTableMethodCommand = @"CREATE TABLE Method( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"sel INTEGER, "
"class INTEGER, "
"instance INTEGER, " // 0 if class method, 1 if instance method
"signature INTEGER, "
"image INTEGER, "
"FOREIGN KEY(sel) REFERENCES Selector(id), "
"FOREIGN KEY(class) REFERENCES Class(id), "
"FOREIGN KEY(signature) REFERENCES MethodSignature(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertMethod = @"INSERT INTO Method ( "
"sel, class, instance, signature, image "
") VALUES ( "
"$sel, $class, $instance, $signature, $image "
");";
NSString * const kFRECreateTablePropertyCommand = @"CREATE TABLE Property( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"name TEXT, "
"class INTEGER, "
"instance INTEGER, " // 0 if class prop, 1 if instance prop
"image INTEGER, "
"attributes TEXT, "
"customGetter INTEGER, "
"customSetter INTEGER, "
"type INTEGER, "
"ivar TEXT, "
"readonly INTEGER, "
"copy INTEGER, "
"retained INTEGER, "
"nonatomic INTEGER, "
"dynamic INTEGER, "
"weak INTEGER, "
"canGC INTEGER, "
"FOREIGN KEY(class) REFERENCES Class(id), "
"FOREIGN KEY(customGetter) REFERENCES Selector(id), "
"FOREIGN KEY(customSetter) REFERENCES Selector(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertProperty = @"INSERT INTO Property ( "
"name, class, instance, attributes, image, "
"customGetter, customSetter, type, ivar, readonly, "
"copy, retained, nonatomic, dynamic, weak, canGC "
") VALUES ( "
"$name, $class, $instance, $attributes, $image, "
"$customGetter, $customSetter, $type, $ivar, $readonly, "
"$copy, $retained, $nonatomic, $dynamic, $weak, $canGC "
");";
NSString * const kFRECreateTableIvarCommand = @"CREATE TABLE Ivar( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"name TEXT, "
"offset INTEGER, "
"type INTEGER, "
"class INTEGER, "
"image INTEGER, "
"FOREIGN KEY(type) REFERENCES TypeEncoding(id), "
"FOREIGN KEY(class) REFERENCES Class(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertIvar = @"INSERT INTO Ivar ( "
"name, offset, type, class, image "
") VALUES ( "
"$name, $offset, $type, $class, $image "
");";
NSString * const kFRECreateTableProtocolCommand = @"CREATE TABLE Protocol( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"name TEXT, "
"image INTEGER, "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertProtocol = @"INSERT INTO Protocol "
"(name, image) VALUES ($name, $image);";
NSString * const kFRECreateTableProtocolPropertyCommand = @"CREATE TABLE ProtocolMember( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"protocol INTEGER, "
"required INTEGER, "
"instance INTEGER, " // 0 if class member, 1 if instance member
// Only of the two below is used
"property TEXT, "
"method TEXT, "
"image INTEGER, "
"FOREIGN KEY(protocol) REFERENCES Protocol(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertProtocolMember = @"INSERT INTO ProtocolMember ( "
"protocol, required, instance, property, method, image "
") VALUES ( "
"$protocol, $required, $instance, $property, $method, $image "
");";
/// For protocols conforming to other protocols
NSString * const kFRECreateTableProtocolConformanceCommand = @"CREATE TABLE ProtocolConformance( "
"protocol INTEGER, "
"conformance INTEGER, "
"FOREIGN KEY(protocol) REFERENCES Protocol(id), "
"FOREIGN KEY(conformance) REFERENCES Protocol(id) "
");";
NSString * const kFREInsertProtocolConformance = @"INSERT INTO ProtocolConformance "
"(protocol, conformance) VALUES ($protocol, $conformance);";
/// For classes conforming to protocols
NSString * const kFRECreateTableClassConformanceCommand = @"CREATE TABLE ClassConformance( "
"class INTEGER, "
"conformance INTEGER, "
"FOREIGN KEY(class) REFERENCES Class(id), "
"FOREIGN KEY(conformance) REFERENCES Protocol(id) "
");";
NSString * const kFREInsertClassConformance = @"INSERT INTO ClassConformance "
"(class, conformance) VALUES ($class, $conformance);";
@interface FLEXRuntimeExporter ()
@property (nonatomic, readonly) FLEXSQLiteDatabaseManager *db;
@property (nonatomic, copy) NSArray<NSString *> *loadedShortBundleNames;
@property (nonatomic, copy) NSArray<NSString *> *loadedBundlePaths;
@property (nonatomic, copy) NSArray<FLEXProtocol *> *protocols;
@property (nonatomic, copy) NSArray<Class> *classes;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *bundlePathsToIDs;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *protocolsToIDs;
@property (nonatomic) NSMutableDictionary<Class, NSNumber *> *classesToIDs;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *typeEncodingsToIDs;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *methodSignaturesToIDs;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *selectorsToIDs;
@end
@implementation FLEXRuntimeExporter
+ (NSString *)tempFilename {
NSString *temp = NSTemporaryDirectory();
NSString *uuid = [NSUUID.UUID.UUIDString substringToIndex:8];
NSString *filename = [NSString stringWithFormat:@"FLEXRuntimeDatabase-%@.db", uuid];
return [temp stringByAppendingPathComponent:filename];
}
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
progressHandler:(void(^)(NSString *status))progress
completion:(void (^)(NSString *))completion {
[self createRuntimeDatabaseAtPath:path forImages:nil progressHandler:progress completion:completion];
}
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
forImages:(NSArray<NSString *> *)images
progressHandler:(void(^)(NSString *status))progress
completion:(void(^)(NSString *_Nullable error))completion {
__typeof(completion) callback = ^(NSString *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
};
// This must be called on the main thread first
if (NSThread.isMainThread) {
[FLEXRuntimeClient initializeWebKitLegacy];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[FLEXRuntimeClient initializeWebKitLegacy];
});
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSError *error = nil;
NSString *errorMessage = nil;
// Get unused temp filename, remove existing database if any
NSString *tempPath = [self tempFilename];
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
[NSFileManager.defaultManager removeItemAtPath:tempPath error:&error];
if (error) {
callback(error.localizedDescription);
return;
}
}
// Attempt to create and populate the database, abort if we fail
FLEXRuntimeExporter *exporter = [self new];
exporter.loadedBundlePaths = images;
if (![exporter createAndPopulateDatabaseAtPath:tempPath
progressHandler:progress
error:&errorMessage]) {
// Remove temp database if it was not moved
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
[NSFileManager.defaultManager removeItemAtPath:tempPath error:nil];
}
callback(errorMessage);
return;
}
// Remove old database at given path
if ([NSFileManager.defaultManager fileExistsAtPath:path]) {
[NSFileManager.defaultManager removeItemAtPath:path error:&error];
if (error) {
callback(error.localizedDescription);
return;
}
}
// Move new database to desired path
[NSFileManager.defaultManager moveItemAtPath:tempPath toPath:path error:&error];
if (error) {
callback(error.localizedDescription);
}
// Remove temp database if it was not moved
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
[NSFileManager.defaultManager removeItemAtPath:tempPath error:nil];
}
callback(nil);
});
}
- (id)init {
self = [super init];
if (self) {
_bundlePathsToIDs = [NSMutableDictionary new];
_protocolsToIDs = [NSMutableDictionary new];
_classesToIDs = [NSMutableDictionary new];
_typeEncodingsToIDs = [NSMutableDictionary new];
_methodSignaturesToIDs = [NSMutableDictionary new];
_selectorsToIDs = [NSMutableDictionary new];
_bundlePathsToIDs[NSNull.null] = (id)NSNull.null;
}
return self;
}
- (BOOL)createAndPopulateDatabaseAtPath:(NSString *)path
progressHandler:(void(^)(NSString *status))step
error:(NSString **)error {
_db = [FLEXSQLiteDatabaseManager managerForDatabase:path];
[self loadMetadata:step];
if ([self createTables] && [self addImages:step] && [self addProtocols:step] &&
[self addClasses:step] && [self setSuperclasses:step] &&
[self addProtocolConformances:step] && [self addClassConformances:step] &&
[self addIvars:step] && [self addMethods:step] && [self addProperties:step]) {
_db = nil; // Close the database
return YES;
}
*error = self.db.lastResult.message;
return NO;
}
- (void)loadMetadata:(void(^)(NSString *status))progress {
progress(@"Loading metadata…");
FLEXRuntimeClient *runtime = FLEXRuntimeClient.runtime;
// Only load metadata for the existing paths if any
if (self.loadedBundlePaths) {
// Images
self.loadedShortBundleNames = [self.loadedBundlePaths flex_mapped:^id(NSString *path, NSUInteger idx) {
return [runtime shortNameForImageName:path];
}];
// Classes
self.classes = [[runtime classesForToken:FLEXSearchToken.any
inBundles:self.loadedBundlePaths.mutableCopy
] flex_mapped:^id(NSString *cls, NSUInteger idx) {
return NSClassFromString(cls);
}];
} else {
// Images
self.loadedShortBundleNames = runtime.imageDisplayNames;
self.loadedBundlePaths = [self.loadedShortBundleNames flex_mapped:^id(NSString *name, NSUInteger idx) {
return [runtime imageNameForShortName:name];
}];
// Classes
self.classes = [runtime copySafeClassList];
}
// ...except protocols, because there's not a lot of them
// and there's no way load the protocols for a given image
self.protocols = [[runtime copyProtocolList] flex_mapped:^id(Protocol *proto, NSUInteger idx) {
return [FLEXProtocol protocol:proto];
}];
}
- (BOOL)createTables {
NSArray<NSString *> *commands = @[
kFREEnableForeignKeys,
kFRECreateTableMachOCommand,
kFRECreateTableClassCommand,
kFRECreateTableSelectorCommand,
kFRECreateTableTypeEncodingCommand,
kFRECreateTableTypeSignatureCommand,
kFRECreateTableMethodSignatureCommand,
kFRECreateTableMethodCommand,
kFRECreateTablePropertyCommand,
kFRECreateTableIvarCommand,
kFRECreateTableProtocolCommand,
kFRECreateTableProtocolPropertyCommand,
kFRECreateTableProtocolConformanceCommand,
kFRECreateTableClassConformanceCommand
];
for (NSString *command in commands) {
if (![self.db executeStatement:command]) {
return NO;
}
}
return YES;
}
- (BOOL)addImages:(void(^)(NSString *status))progress {
progress(@"Adding loaded images…");
FLEXSQLiteDatabaseManager *database = self.db;
NSArray *shortNames = self.loadedShortBundleNames;
NSArray *fullPaths = self.loadedBundlePaths;
NSParameterAssert(shortNames.count == fullPaths.count);
NSInteger count = shortNames.count;
for (NSInteger i = 0; i < count; i++) {
// Grab bundle ID
NSString *bundleID = [NSBundle
bundleWithPath:fullPaths[i]
].bundleIdentifier;
[database executeStatement:kFREInsertImage arguments:@{
@"$shortName": shortNames[i],
@"$imagePath": fullPaths[i],
@"$bundleID": bundleID ?: NSNull.null
}];
if (database.lastResult.isError) {
return NO;
} else {
self.bundlePathsToIDs[fullPaths[i]] = @(database.lastRowID);
}
}
return YES;
}
NS_INLINE BOOL FREInsertProtocolMember(FLEXSQLiteDatabaseManager *db,
id proto, id required, id instance,
id prop, id methSel, id image) {
return ![db executeStatement:kFREInsertProtocolMember arguments:@{
@"$protocol": proto,
@"$required": required,
@"$instance": instance ?: NSNull.null,
@"$property": prop ?: NSNull.null,
@"$method": methSel ?: NSNull.null,
@"$image": image
}].isError;
}
- (BOOL)addProtocols:(void(^)(NSString *status))progress {
progress([NSString stringWithFormat:@"Adding %@ protocols…", @(self.protocols.count)]);
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
for (FLEXProtocol *proto in self.protocols) {
id imagePath = proto.imagePath ?: NSNull.null;
NSNumber *image = imageIDs[imagePath] ?: NSNull.null;
NSNumber *pid = nil;
// Insert protocol
BOOL failed = [database executeStatement:kFREInsertProtocol arguments:@{
@"$name": proto.name, @"$image": image
}].isError;
// Cache rowid
if (failed) {
return NO;
} else {
self.protocolsToIDs[proto.name] = pid = @(database.lastRowID);
}
// Insert its members //
// Required methods
for (FLEXMethodDescription *method in proto.requiredMethods) {
NSString *selector = NSStringFromSelector(method.selector);
if (!FREInsertProtocolMember(database, pid, @YES, method.instance, nil, selector, image)) {
return NO;
}
}
// Optional methods
for (FLEXMethodDescription *method in proto.optionalMethods) {
NSString *selector = NSStringFromSelector(method.selector);
if (!FREInsertProtocolMember(database, pid, @NO, method.instance, nil, selector, image)) {
return NO;
}
}
if (@available(iOS 10, *)) {
// Required properties
for (FLEXProperty *property in proto.requiredProperties) {
BOOL success = FREInsertProtocolMember(
database, pid, @YES, @(property.isClassProperty), property.name, NSNull.null, image
);
if (!success) return NO;
}
// Optional properties
for (FLEXProperty *property in proto.optionalProperties) {
BOOL success = FREInsertProtocolMember(
database, pid, @NO, @(property.isClassProperty), property.name, NSNull.null, image
);
if (!success) return NO;
}
} else {
// Just... properties.
for (FLEXProperty *property in proto.properties) {
BOOL success = FREInsertProtocolMember(
database, pid, nil, @(property.isClassProperty), property.name, NSNull.null, image
);
if (!success) return NO;
}
}
}
return YES;
}
- (BOOL)addProtocolConformances:(void(^)(NSString *status))progress {
progress(@"Adding protocol-to-protocol conformances…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *protocolIDs = self.protocolsToIDs;
for (FLEXProtocol *proto in self.protocols) {
id protoID = protocolIDs[proto.name];
for (FLEXProtocol *conform in proto.protocols) {
BOOL failed = [database executeStatement:kFREInsertProtocolConformance arguments:@{
@"$protocol": protoID,
@"$conformance": protocolIDs[conform.name]
}].isError;
if (failed) {
return NO;
}
}
}
return YES;
}
- (BOOL)addClasses:(void(^)(NSString *status))progress {
progress([NSString stringWithFormat:@"Adding %@ classes…", @(self.classes.count)]);
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
for (Class cls in self.classes) {
const char *imageName = class_getImageName(cls);
id image = imageName ? imageIDs[@(imageName)] : NSNull.null;
image = image ?: NSNull.null;
BOOL failed = [database executeStatement:kFREInsertClass arguments:@{
@"$className": NSStringFromClass(cls),
@"$instanceSize": @(class_getInstanceSize(cls)),
@"$version": @(class_getVersion(cls)),
@"$image": image
}].isError;
if (failed) {
return NO;
} else {
self.classesToIDs[(id)cls] = @(database.lastRowID);
}
}
return YES;
}
- (BOOL)setSuperclasses:(void(^)(NSString *status))progress {
progress(@"Setting superclasses…");
FLEXSQLiteDatabaseManager *database = self.db;
for (Class cls in self.classes) {
// Grab superclass ID
Class superclass = class_getSuperclass(cls);
NSNumber *superclassID = _classesToIDs[class_getSuperclass(cls)];
// ... or add the superclass and cache its ID if the
// superclass does not reside in the target image(s)
if (!superclassID) {
NSDictionary *args = @{ @"$className": NSStringFromClass(superclass) };
BOOL failed = [database executeStatement:kFREInsertClass arguments:args].isError;
if (failed) { return NO; }
_classesToIDs[(id)superclass] = superclassID = @(database.lastRowID);
}
if (superclass) {
BOOL failed = [database executeStatement:kFREUpdateClassSetSuper arguments:@{
@"$super": superclassID, @"$id": _classesToIDs[cls]
}].isError;
if (failed) {
return NO;
}
}
}
return YES;
}
- (BOOL)addClassConformances:(void(^)(NSString *status))progress {
progress(@"Adding class-to-protocol conformances…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *protocolIDs = self.protocolsToIDs;
NSDictionary *classIDs = self.classesToIDs;
for (Class cls in self.classes) {
id classID = classIDs[(id)cls];
for (FLEXProtocol *conform in FLEXGetConformedProtocols(cls)) {
BOOL failed = [database executeStatement:kFREInsertClassConformance arguments:@{
@"$class": classID,
@"$conformance": protocolIDs[conform.name]
}].isError;
if (failed) {
return NO;
}
}
}
return YES;
}
- (BOOL)addIvars:(void(^)(NSString *status))progress {
progress(@"Adding ivars…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
for (Class cls in self.classes) {
for (FLEXIvar *ivar in FLEXGetAllIvars(cls)) {
// Insert type first
if (![self addTypeEncoding:ivar.typeEncoding size:ivar.size]) {
return NO;
}
id imagePath = ivar.imagePath ?: NSNull.null;
NSNumber *image = imageIDs[imagePath] ?: NSNull.null;
BOOL failed = [database executeStatement:kFREInsertIvar arguments:@{
@"$name": ivar.name,
@"$offset": @(ivar.offset),
@"$type": _typeEncodingsToIDs[ivar.typeEncoding],
@"$class": _classesToIDs[cls],
@"$image": image
}].isError;
if (failed) {
return NO;
}
}
}
return YES;
}
- (BOOL)addMethods:(void(^)(NSString *status))progress {
progress(@"Adding methods…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
// Loop over all classes
for (Class cls in self.classes) {
NSNumber *classID = _classesToIDs[(id)cls];
const char *imageName = class_getImageName(cls);
id image = imageName ? imageIDs[@(imageName)] : NSNull.null;
image = image ?: NSNull.null;
// Block used to process each message
BOOL (^insert)(FLEXMethod *, NSNumber *) = ^BOOL(FLEXMethod *method, NSNumber *instance) {
// Insert selector and signature first
if (![self addSelector:method.selectorString]) {
return NO;
}
if (![self addMethodSignature:method]) {
return NO;
}
return ![database executeStatement:kFREInsertMethod arguments:@{
@"$sel": self->_selectorsToIDs[method.selectorString],
@"$class": classID,
@"$instance": instance,
@"$signature": self->_methodSignaturesToIDs[method.signatureString],
@"$image": image
}].isError;
};
// Loop over all instance and class methods of that class //
for (FLEXMethod *method in FLEXGetAllMethods(cls, YES)) {
if (!insert(method, @YES)) {
return NO;
}
}
for (FLEXMethod *method in FLEXGetAllMethods(object_getClass(cls), NO)) {
if (!insert(method, @NO)) {
return NO;
}
}
}
return YES;
}
- (BOOL)addProperties:(void(^)(NSString *status))progress {
progress(@"Adding properties…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
// Loop over all classes
for (Class cls in self.classes) {
NSNumber *classID = _classesToIDs[(id)cls];
// Block used to process each message
BOOL (^insert)(FLEXProperty *, NSNumber *) = ^BOOL(FLEXProperty *property, NSNumber *instance) {
FLEXPropertyAttributes *attrs = property.attributes;
NSString *customGetter = attrs.customGetterString;
NSString *customSetter = attrs.customSetterString;
// Insert selectors first
if (customGetter) {
if (![self addSelector:customGetter]) {
return NO;
}
}
if (customSetter) {
if (![self addSelector:customSetter]) {
return NO;
}
}
// Insert type encoding first
NSInteger size = [FLEXTypeEncodingParser
sizeForTypeEncoding:attrs.typeEncoding alignment:nil
];
if (![self addTypeEncoding:attrs.typeEncoding size:size]) {
return NO;
}
id imagePath = property.imagePath ?: NSNull.null;
id image = imageIDs[imagePath] ?: NSNull.null;
return ![database executeStatement:kFREInsertProperty arguments:@{
@"$name": property.name,
@"$class": classID,
@"$instance": instance,
@"$image": image,
@"$attributes": attrs.string,
@"$customGetter": self->_selectorsToIDs[customGetter] ?: NSNull.null,
@"$customSetter": self->_selectorsToIDs[customSetter] ?: NSNull.null,
@"$type": self->_typeEncodingsToIDs[attrs.typeEncoding] ?: NSNull.null,
@"$ivar": attrs.backingIvar ?: NSNull.null,
@"$readonly": @(attrs.isReadOnly),
@"$copy": @(attrs.isCopy),
@"$retained": @(attrs.isRetained),
@"$nonatomic": @(attrs.isNonatomic),
@"$dynamic": @(attrs.isDynamic),
@"$weak": @(attrs.isWeak),
@"$canGC": @(attrs.isGarbageCollectable),
}].isError;
};
// Loop over all instance and class methods of that class //
for (FLEXProperty *property in FLEXGetAllProperties(cls)) {
if (!insert(property, @YES)) {
return NO;
}
}
for (FLEXProperty *property in FLEXGetAllProperties(object_getClass(cls))) {
if (!insert(property, @NO)) {
return NO;
}
}
}
return YES;
}
- (BOOL)addSelector:(NSString *)sel {
return [self executeInsert:kFREInsertSelector args:@{
@"$name": sel
} key:sel cacheResult:_selectorsToIDs];
}
- (BOOL)addTypeEncoding:(NSString *)type size:(NSInteger)size {
return [self executeInsert:kFREInsertTypeEncoding args:@{
@"$type": type, @"$size": @(size)
} key:type cacheResult:_typeEncodingsToIDs];
}
- (BOOL)addMethodSignature:(FLEXMethod *)method {
NSString *signature = method.signatureString;
NSString *returnType = @((char *)method.returnType);
// Insert return type first
if (![self addTypeEncoding:returnType size:method.returnSize]) {
return NO;
}
return [self executeInsert:kFREInsertMethodSignature args:@{
@"$typeEncoding": signature,
@"$returnType": _typeEncodingsToIDs[returnType],
@"$argc": @(method.numberOfArguments),
@"$frameLength": @(method.signature.frameLength)
} key:signature cacheResult:_methodSignaturesToIDs];
}
- (BOOL)executeInsert:(NSString *)statement
args:(NSDictionary *)args
key:(NSString *)cacheKey
cacheResult:(NSMutableDictionary<NSString *, NSNumber *> *)rowids {
// Check if already inserted
if (rowids[cacheKey]) {
return YES;
}
// Insert
FLEXSQLiteDatabaseManager *database = _db;
[database executeStatement:statement arguments:args];
if (database.lastResult.isError) {
return NO;
}
// Cache rowid
rowids[cacheKey] = @(database.lastRowID);
return YES;
}
@end