Compare commits

...

24 Commits

Author SHA1 Message Date
Tanner Bennett cd695ed106 Bump version 2021-11-14 00:53:41 -06:00
Tanner Bennett 69c1719159 Remove redundant/unused methods in arginputfactory 2021-11-14 00:39:44 -06:00
Tanner Bennett 4019518bf5 Fix #564
entry->log_message.format appears to be garbage on iOS 15, and it doesn't look like it is ever really used in practice anyway, as far as I can tell. Thanks @matrush for pointing this out!
2021-11-14 00:39:44 -06:00
Matt Robinson 3fd8e7c77d Fix CLANG_WARN_STRICT_PROTOTYPES/-Wstrict-prototypes issues
This allows `FLEX` to be linked statically to a binary that has `-Wstrict-prototypes` enabled (for example in CocoaPods without `use_frameworks!`.

Changes:
- Apply `void` to all the empty function/block declarations that don't take arguments.
- Apply `SEL`/`UIImage *` to the others that actually take arguments.
- Remove `CLANG_WARN_STRICT_PROTOTYPES = NO` since the default is enabled.
2021-11-14 00:39:44 -06:00
Tanner Bennett 99c3bcb8c5 Remove init exceptions from flex meta classes 2021-11-14 00:39:44 -06:00
Tanner Bennett b587e96e70 Make FLEXMirror protocol 2021-11-14 00:39:43 -06:00
Tanner Bennett ef8f0a303e Add missing nullability to metadata types 2021-11-14 00:36:52 -06:00
Tanner Bennett cfb1e4caab Add NS_SWIFT_NAME to some enums; does not work
Left it commented out. The enums just disappear for some reason?
2021-11-04 19:11:18 -05:00
daniel 2411c331cd Added a dynamic background color to the WebViewControllers response (Depending on being in Light/Dark mode. See this gist for an example: https://gist.githubusercontent.com/dlevi309/e4d48556836b26125e95cbd82d32a9de/raw/d01112d6e0734db11fa74e2fdb0a1330ddcf82a2/dynamicPage.html) 2021-10-27 13:45:33 -05:00
Tanner Bennett fd4b38f46d Fix bug in tab-close logic 2021-10-26 14:43:20 -05:00
Chaoshuai Lu 269e31894c Remove NSParameterAssert check in closeTab method 2021-10-25 18:24:50 -07:00
Tanner Bennett 2f2da50aed Fix potential crash in FLEXPointerIsReadable
vm_read allocates heap memory, this was intended to be an allocation-free check. Some apps may run out of memory in this code path.
2021-10-20 15:21:26 -05:00
Tanner Bennett d87779212c Add data property to FIRDocumentSnapshot at runtime 2021-10-17 17:33:23 -05:00
Tanner Bennett 5db6a12c6e project.pbxcproj formatting? 2021-10-17 17:33:02 -05:00
Tanner Bennett 6d0f776102 Add a FLEXHeapEnumerator test
Ensure we can spoof an object that is found by FLEXHeapEnumerator and also later prove it isn't a real object
2021-10-17 17:32:09 -05:00
Tanner Bennett 6c83ddc2c7 Refactor FLEXHeapEnumerator and FLEXObjectRef
Move logic that was in FLEXLiveObjectsController into FLEXHeapEnumerator. Also adjust FLEXObjectRef initializers to reflect the type of reference you want to hold to the object. FLEXObjectRef now supports unsafe_unretained and retained references.
2021-10-17 17:31:12 -05:00
Tanner Bennett b510d24e13 Improve FLEXPointerIsValidObjcObject
Compare allocation size to expected instance size
2021-10-17 17:28:59 -05:00
Tanner Bennett 6cdf2e61dc Fix websocket activity not being cleared 2021-10-14 00:28:49 -05:00
Tanner Bennett 9b0ed83ff5 Remove old commented out code 2021-10-13 23:41:13 -05:00
Tanner Bennett dbe1b93f48 Add network observer flag to our NSUserDefaults category 2021-10-13 18:45:52 -05:00
Tanner Bennett 06444f1576 Clean up some comments 2021-10-11 20:21:37 -05:00
Tanner Bennett 9bbf1d0d48 Refactor FLEXNetworkTransaction
Add FLEXURLTransaction to differentiate between NSURL-API-related transactions and other transactions, such as lower level protocols or Firebase ;)
2021-10-11 20:11:04 -05:00
Tanner Bennett 0562f15cd0 Fix incoming WS messages not showing correct details 2021-10-11 20:09:58 -05:00
Tanner Bennett eb63c91481 Send example WS message 2021-10-11 20:09:40 -05:00
53 changed files with 508 additions and 257 deletions
@@ -74,7 +74,7 @@
#pragma mark - Search
- (void)updateSearchResults:(NSString *)newText {
NSArray *(^filter)() = ^NSArray *{
NSArray *(^filter)(void) = ^NSArray *{
self.filterText = newText;
// Sections will adjust data based on this property
@@ -96,7 +96,10 @@
- (void)dismissAnimated {
// Tabs are only closed if the done button is pressed; this
// allows you to leave a tab open by dragging down to dismiss
[FLEXTabList.sharedList closeTab:self];
if ([self.presentingViewController isKindOfClass:[FLEXExplorerViewController class]]) {
[FLEXTabList.sharedList closeTab:self];
}
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
@@ -559,7 +559,7 @@ static UITextField *kDummyTextField = nil;
[self.debounceTimer invalidate];
NSString *text = searchController.searchBar.text;
void (^updateSearchResults)() = ^{
void (^updateSearchResults)(void) = ^{
if (self.searchResultsUpdater) {
[self.searchResultsUpdater updateSearchResults:text];
} else {
@@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXDefaultEditorViewController : FLEXVariableEditorViewController
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)())onCommit;
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit;
+ (BOOL)canEditDefaultWithValue:(nullable id)currentValue;
@@ -21,7 +21,7 @@
@implementation FLEXDefaultEditorViewController
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit {
FLEXDefaultEditorViewController *editor = [self target:defaults data:key commitHandler:onCommit];
editor.title = @"Edit Default";
return editor;
@@ -15,9 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXFieldEditorViewController : FLEXVariableEditorViewController
/// @return nil if the property is readonly or if the type is unsupported
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit;
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit;
/// @return nil if the ivar type is unsupported
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit;
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit;
/// Subclasses can change the button title via the \c title property
@property (nonatomic, readonly) UIBarButtonItem *getterButton;
@@ -30,19 +30,14 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit {
id value = [property getValue:target];
if (![self canEditProperty:property onObject:target currentValue:value]) {
return nil;
}
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:property commitHandler:onCommit];
editor.title = [@"Property: " stringByAppendingString:property.name];
editor.property = property;
return editor;
}
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:ivar commitHandler:onCommit];
editor.title = [@"Ivar: " stringByAppendingString:ivar.name];
editor.ivar = ivar;
@@ -151,14 +146,4 @@
}
}
+ (BOOL)canEditProperty:(FLEXProperty *)property onObject:(id)object currentValue:(id)value {
const FLEXTypeEncoding *typeEncoding = property.attributes.typeEncoding.UTF8String;
BOOL canEditType = [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:value];
return canEditType && [object respondsToSelector:property.likelySetter];
}
+ (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value {
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:ivar_getTypeEncoding(ivar) currentValue:value];
}
@end
@@ -21,17 +21,17 @@ NS_ASSUME_NONNULL_BEGIN
@protected
id _target;
_Nullable id _data;
void (^_Nullable _commitHandler)();
void (^_Nullable _commitHandler)(void);
}
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit;
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit;
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit;
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit;
@property (nonatomic, readonly) id target;
@@ -25,11 +25,11 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit {
return [[self alloc] initWithTarget:target data:data commitHandler:onCommit];
}
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit {
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit {
self = [super init];
if (self) {
_target = target;
@@ -460,17 +460,12 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
initWithTarget:self action:@selector(handleToolbarDetailsTapGesture:)
];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:self.detailsTapGR];
// Swipe gestures for selecting deeper / higher views at a point
UIPanGestureRecognizer *leftSwipe = [[UIPanGestureRecognizer alloc]
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
];
// UIPanGestureRecognizer *rightSwipe = [[UIPanGestureRecognizer alloc]
// initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
// ];
// leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
// rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:leftSwipe];
// [toolbar.selectedViewDescriptionContainer addGestureRecognizer:rightSwipe];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:panGesture];
// Long press gesture to present tabs manager
[toolbar.globalsItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
@@ -72,7 +72,7 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)showRevertOrDismissAlert:(void(^)())revertBlock {
- (void)showRevertOrDismissAlert:(void(^)(void))revertBlock {
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
[self reloadData];
[self.tableView reloadData];
+3 -3
View File
@@ -80,10 +80,10 @@
- (void)closeTab:(UINavigationController *)tab {
NSParameterAssert(tab);
NSParameterAssert([self.openTabs containsObject:tab]);
NSInteger idx = [self.openTabs indexOfObject:tab];
[self closeTabAtIndex:idx];
if (idx != NSNotFound) {
[self closeTabAtIndex:idx];
}
}
- (void)closeTabAtIndex:(NSInteger)idx {
@@ -226,7 +226,10 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *className = self.filteredClassNames[indexPath.row];
UIViewController *instances = [FLEXObjectListViewController instancesOfClassWithName:className];
UIViewController *instances = [FLEXObjectListViewController
instancesOfClassWithName:className
retained:YES
];
[self.navigationController pushViewController:instances animated:YES];
}
@@ -12,8 +12,8 @@
/// This will either return a list of the instances, or take you straight
/// to the explorer itself if there is only one instance.
+ (UIViewController *)instancesOfClassWithName:(NSString *)className;
+ (UIViewController *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain;
+ (instancetype)subclassesOfClassWithName:(NSString *)className;
+ (instancetype)objectsWithReferencesToObject:(id)object;
+ (instancetype)objectsWithReferencesToObject:(id)object retained:(BOOL)retain;
@end
@@ -141,45 +141,33 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
return self;
}
+ (UIViewController *)instancesOfClassWithName:(NSString *)className {
const char *classNameCString = className.UTF8String;
NSMutableArray *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
// Note: objects of certain classes crash when retain is called.
// It is up to the user to avoid tapping into instance lists for these classes.
// Ex. OS_dispatch_queue_specific_queue
// In the future, we could provide some kind of warning for classes that are known to be problematic.
if (malloc_size((__bridge const void *)(object)) > 0) {
[instances addObject:object];
}
}
}];
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
+ (UIViewController *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain {
NSArray<FLEXObjectRef *> *references = [FLEXHeapEnumerator
instancesOfClassWithName:className retained:retain
];
if (references.count == 1) {
return [FLEXObjectExplorerFactory
explorerViewControllerForObject:references.firstObject.object
explorerViewControllerForObject:references.firstObject.object
];
}
FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
controller.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)instances.count];
controller.title = [NSString stringWithFormat:@"%@ (%@)", className, @(references.count)];
return controller;
}
+ (instancetype)subclassesOfClassWithName:(NSString *)className {
NSArray<Class> *classes = FLEXGetAllSubclasses(NSClassFromString(className), NO);
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingClasses:classes];
NSArray<FLEXObjectRef *> *references = [FLEXHeapEnumerator subclassesOfClassWithName:className];
FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%lu)",
className, (unsigned long)classes.count
controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%@)",
className, @(references.count)
];
return controller;
}
+ (instancetype)objectsWithReferencesToObject:(id)object {
+ (instancetype)objectsWithReferencesToObject:(id)object retained:(BOOL)retain {
static Class SwiftObjectClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@@ -189,35 +177,9 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
}
});
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
// Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
// Once we find a match, record it and move on to the next object. There's no reason to find multiple matches within the same object.
Class tryClass = actualClass;
while (tryClass) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
Ivar ivar = ivars[ivarIndex];
NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
if (typeEncoding.flex_typeIsObjectOrClass) {
ptrdiff_t offset = ivar_getOffset(ivar);
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
NSString *ivarName = @(ivar_getName(ivar) ?: "???");
[instances addObject:[FLEXObjectRef referencing:tryObject ivar:ivarName]];
return;
}
}
}
free(ivars);
tryClass = class_getSuperclass(tryClass);
}
}];
NSArray<FLEXObjectRef *> *instances = [FLEXHeapEnumerator
objectsWithReferencesToObject:object retained:retain
];
FLEXObjectListViewController *viewController = [[self alloc]
initWithReferences:instances
+18 -4
View File
@@ -10,10 +10,19 @@
@interface FLEXObjectRef : NSObject
+ (instancetype)referencing:(id)object;
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName;
/// Reference an object without affecting its lifespan or or emitting reference-counting operations.
+ (instancetype)unretained:(__unsafe_unretained id)object;
+ (instancetype)unretained:(__unsafe_unretained id)object ivar:(NSString *)ivarName;
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects;
/// Reference an object and control its lifespan.
+ (instancetype)retained:(id)object;
+ (instancetype)retained:(id)object ivar:(NSString *)ivarName;
/// Reference an object and conditionally choose to retain it or not.
+ (instancetype)referencing:(__unsafe_unretained id)object retained:(BOOL)retain;
+ (instancetype)referencing:(__unsafe_unretained id)object ivar:(NSString *)ivarName retained:(BOOL)retain;
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects retained:(BOOL)retain;
/// Classes do not have a summary, and the reference is just the class name.
+ (NSArray<FLEXObjectRef *> *)referencingClasses:(NSArray<Class> *)classes;
@@ -22,6 +31,11 @@
/// For instances, this is the result of -[FLEXRuntimeUtility summaryForObject:]
/// For classes, there is no summary.
@property (nonatomic, readonly) NSString *summary;
@property (nonatomic, readonly) id object;
@property (nonatomic, readonly, unsafe_unretained) id object;
/// Retains the referenced object if it is not already retained
- (void)retainObject;
/// Releases the referenced object if it is already retained
- (void)releaseObject;
@end
+47 -11
View File
@@ -10,42 +10,68 @@
#import "FLEXRuntimeUtility.h"
#import "NSArray+FLEX.h"
@interface FLEXObjectRef ()
@interface FLEXObjectRef () {
/// Used to retain the object if desired
id _retainer;
}
@property (nonatomic, readonly) BOOL wantsSummary;
@end
@implementation FLEXObjectRef
@synthesize summary = _summary;
+ (instancetype)referencing:(id)object {
return [self referencing:object showSummary:YES];
+ (instancetype)unretained:(__unsafe_unretained id)object {
return [self referencing:object showSummary:YES retained:NO];
}
+ (instancetype)referencing:(id)object showSummary:(BOOL)showSummary {
return [[self alloc] initWithObject:object ivarName:nil showSummary:showSummary];
+ (instancetype)unretained:(__unsafe_unretained id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES retained:NO];
}
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES];
+ (instancetype)retained:(id)object {
return [self referencing:object showSummary:YES retained:YES];
}
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects {
+ (instancetype)retained:(id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES retained:YES];
}
+ (instancetype)referencing:(__unsafe_unretained id)object retained:(BOOL)retain {
return retain ? [self retained:object] : [self unretained:object];
}
+ (instancetype)referencing:(__unsafe_unretained id)object ivar:(NSString *)ivarName retained:(BOOL)retain {
return retain ? [self retained:object ivar:ivarName] : [self unretained:object ivar:ivarName];
}
+ (instancetype)referencing:(__unsafe_unretained id)object showSummary:(BOOL)showSummary retained:(BOOL)retain {
return [[self alloc] initWithObject:object ivarName:nil showSummary:showSummary retained:retain];
}
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects retained:(BOOL)retain {
return [objects flex_mapped:^id(id obj, NSUInteger idx) {
return [self referencing:obj showSummary:YES];
return [self referencing:obj showSummary:YES retained:retain];
}];
}
+ (NSArray<FLEXObjectRef *> *)referencingClasses:(NSArray<Class> *)classes {
return [classes flex_mapped:^id(id obj, NSUInteger idx) {
return [self referencing:obj showSummary:NO];
return [self referencing:obj showSummary:NO retained:NO];
}];
}
- (id)initWithObject:(id)object ivarName:(NSString *)ivar showSummary:(BOOL)showSummary {
- (id)initWithObject:(__unsafe_unretained id)object
ivarName:(NSString *)ivar
showSummary:(BOOL)showSummary
retained:(BOOL)retain {
self = [super init];
if (self) {
_object = object;
_wantsSummary = showSummary;
if (retain) {
_retainer = object;
}
NSString *class = [FLEXRuntimeUtility safeClassNameForObject:object];
if (ivar) {
@@ -73,4 +99,14 @@
}
}
- (void)retainObject {
if (!_retainer) {
_retainer = _object;
}
}
- (void)releaseObject {
_retainer = nil;
}
@end
@@ -38,7 +38,7 @@
self = [self initWithNibName:nil bundle:nil];
if (self) {
self.originalText = text;
NSString *htmlString = [NSString stringWithFormat:@"<head><meta name='viewport' content='initial-scale=1.0'></head><body><pre>%@</pre></body>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
NSString *htmlString = [NSString stringWithFormat:@"<head><style>:root{ color-scheme: light dark; }</style><meta name='viewport' content='initial-scale=1.0'></head><body><pre>%@</pre></body>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
[self.webView loadHTMLString:htmlString baseURL:nil];
}
return self;
@@ -195,7 +195,7 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
"/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
RTLD_LAZY
);
void (*WebKitInitialize)() = dlsym(handle, "WebKitInitialize");
void (*WebKitInitialize)(void) = dlsym(handle, "WebKitInitialize");
if (WebKitInitialize) {
NSAssert(NSThread.isMainThread,
@"WebKitInitialize can only be called on the main thread"
@@ -168,11 +168,10 @@ static uint8_t (*OSLogGetType)(void *);
NSDate *date = [NSDate dateWithTimeIntervalSince1970:log_message->tv_gmt.tv_sec];
// Get log message text
const char *messageText = OSLogCopyFormattedMessage(log_message);
// https://github.com/limneos/oslog/issues/1
if (entry->log_message.format && !(strcmp(entry->log_message.format, messageText))) {
messageText = (char *)entry->log_message.format;
}
// https://github.com/FLEXTool/FLEX/issues/564
const char *messageText = OSLogCopyFormattedMessage(log_message) ?: "";
// move messageText from stack to heap
NSString *msg = [NSString stringWithUTF8String:messageText];
+1 -1
View File
@@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXMITMDataSource<__covariant TransactionType> : NSObject
+ (instancetype)dataSourceWithProvider:(NSArray<TransactionType> *(^)())future;
+ (instancetype)dataSourceWithProvider:(NSArray<TransactionType> *(^)(void))future;
@property (nonatomic, readonly) NSArray<TransactionType> *transactions;
@property (nonatomic, readonly) NSArray<TransactionType> *allTransactions;
+3 -3
View File
@@ -10,13 +10,13 @@
#import "FLEXUtility.h"
@interface FLEXMITMDataSource ()
@property (nonatomic, readonly) NSArray *(^dataProvider)();
@property (nonatomic, readonly) NSArray *(^dataProvider)(void);
@property (nonatomic) NSString *filterString;
@end
@implementation FLEXMITMDataSource
+ (instancetype)dataSourceWithProvider:(NSArray<id> *(^)())future {
+ (instancetype)dataSourceWithProvider:(NSArray<id> *(^)(void))future {
FLEXMITMDataSource *ds = [self new];
ds->_dataProvider = future;
[ds reloadData:nil];
@@ -51,7 +51,7 @@
} else {
[self onBackgroundQueue:^NSArray *{
return [self.allTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) {
return [entry.request.URL.absoluteString localizedCaseInsensitiveContainsString:searchString];
return [entry matchesQuery:searchString];
}];
} thenOnMainQueue:^(NSArray *filteredNetworkTransactions) {
if ([self.filterString isEqual:searchString]) {
+36 -22
View File
@@ -32,6 +32,8 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
@property (nonatomic) BOOL updateInProgress;
@property (nonatomic) BOOL pendingReload;
@property (nonatomic, readonly) FLEXNetworkObserverMode mode;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXNetworkTransaction *> *dataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXHTTPTransaction *> *HTTPDataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXWebsocketTransaction *> *websocketDataSource;
@@ -157,8 +159,12 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
#pragma mark Transactions
- (FLEXNetworkObserverMode)mode {
return self.searchController.searchBar.selectedScopeButtonIndex;
}
- (FLEXMITMDataSource<FLEXNetworkTransaction *> *)dataSource {
switch (self.searchController.searchBar.selectedScopeButtonIndex) {
switch (self.mode) {
case FLEXNetworkObserverModeREST:
return self.HTTPDataSource;
case FLEXNetworkObserverModeWebsockets:
@@ -169,7 +175,7 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
}
}
- (void)updateTransactions:(void(^)())callback {
- (void)updateTransactions:(void(^)(void))callback {
id completion = ^(FLEXMITMDataSource *dataSource) {
// Update byte count
[self updateFirstSectionHeader];
@@ -274,13 +280,13 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
// Get state before update
NSString *currentFilter = self.searchText;
FLEXNetworkObserverMode currentMode = self.searchController.searchBar.selectedScopeButtonIndex;
FLEXNetworkObserverMode currentMode = self.mode;
NSInteger existingRowCount = self.dataSource.transactions.count;
[self updateTransactions:^{
// Compare to state after update
NSString *newFilter = self.searchText;
FLEXNetworkObserverMode newMode = self.searchController.searchBar.selectedScopeButtonIndex;
FLEXNetworkObserverMode newMode = self.mode;
NSInteger newRowCount = self.dataSource.transactions.count;
NSInteger rowCountDiff = newRowCount - existingRowCount;
@@ -392,7 +398,7 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (self.searchController.searchBar.selectedScopeButtonIndex) {
switch (self.mode) {
case FLEXNetworkObserverModeREST: {
FLEXHTTPTransaction *transaction = [self HTTPTransactionAtIndexPath:indexPath];
UIViewController *details = [FLEXHTTPTransactionDetailController withTransaction:transaction];
@@ -434,13 +440,14 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @selector(copy:)) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
UIPasteboard.generalPasteboard.string = [self transactionAtIndexPath:indexPath].copyString;
}
}
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
FLEXNetworkTransaction *transaction = [self transactionAtIndexPath:indexPath];
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
previewProvider:nil
@@ -450,25 +457,32 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
}
];
UIAction *denylist = [UIAction
actionWithTitle:[NSString stringWithFormat:@"Exclude '%@'", request.URL.host]
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
NSMutableArray *denylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist;
[denylist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearExcludedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[self tryUpdateTransactions];
UIPasteboard.generalPasteboard.string = transaction.copyString;
}
];
NSArray *children = @[copy];
if (self.mode == FLEXNetworkObserverModeREST) {
NSURLRequest *request = [self HTTPTransactionAtIndexPath:indexPath].request;
UIAction *denylist = [UIAction
actionWithTitle:[NSString stringWithFormat:@"Exclude '%@'", request.URL.host]
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
NSMutableArray *denylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist;
[denylist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearExcludedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[self tryUpdateTransactions];
}
];
children = [children arrayByAddingObject:denylist];
}
return [UIMenu
menuWithTitle:@"" image:nil identifier:nil
options:UIMenuOptionsDisplayInline
children:@[copy, denylist]
children:children
];
}
];
+1
View File
@@ -96,6 +96,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)clearRecordedActivity {
dispatch_async(self.queue, ^{
[self.restCache removeAllObjects];
[self.orderedWSTransactions removeAllObjects];
[self.orderedHTTPTransactions removeAllObjects];
[self.requestIDsToHTTPTransactions removeAllObjects];
+27 -8
View File
@@ -22,36 +22,55 @@ typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
FLEXWebsocketOutgoing,
};
/// The shared base class for all types of network transactions.
/// Subclasses should implement the descriptions and details properties, and assign a thumbnail.
@interface FLEXNetworkTransaction : NSObject
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime;
+ (instancetype)withStartTime:(NSDate *)startTime;
@property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, readonly) NSDate *startTime;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
@property (nonatomic) NSError *error;
/// Subclasses can override to provide error state based on response data as well
@property (nonatomic, readonly) BOOL displayAsError;
@property (nonatomic, readonly) NSDate *startTime;
@property (nonatomic) FLEXNetworkTransactionState transactionState;
@property (nonatomic) int64_t receivedDataLength;
/// A small thumbnail to preview the type of/the response
@property (nonatomic) UIImage *thumbnail;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
/// Generated by this class using the URL provided by subclasses
/// The most prominent line of the cell. Typically a URL endpoint or other distinguishing attribute.
/// This line turns red when the transaction indicates an error.
@property (nonatomic, readonly) NSString *primaryDescription;
/// Something less important, such as a blob of data or the URL's domain.
@property (nonatomic, readonly) NSString *secondaryDescription;
/// Minor details to display at the bottom of the cell, such as a timestamp, HTTP method, or status.
@property (nonatomic, readonly) NSString *tertiaryDescription;
/// Subclasses should implement for when the transaction is complete
@property (nonatomic, readonly) NSArray<NSString *> *details;
/// The string to copy when the user selects the "copy" action
@property (nonatomic, readonly) NSString *copyString;
/// Whether or not this request should show up when the user searches for a given string
- (BOOL)matchesQuery:(NSString *)filterString;
@end
/// The shared base class for all NSURL-API-related transactions.
/// Descriptions are generated by this class using the URL provided by subclasses.
@interface FLEXURLTransaction : FLEXNetworkTransaction
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime;
@property (nonatomic, readonly) NSURLRequest *request;
@end
@interface FLEXHTTPTransaction : FLEXNetworkTransaction
@interface FLEXHTTPTransaction : FLEXURLTransaction
+ (instancetype)request:(NSURLRequest *)request identifier:(NSString *)requestID;
@@ -68,7 +87,7 @@ typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
@end
@interface FLEXWebsocketTransaction : FLEXNetworkTransaction
@interface FLEXWebsocketTransaction : FLEXURLTransaction
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
+51 -14
View File
@@ -10,14 +10,17 @@
#import "FLEXResources.h"
#import "FLEXUtility.h"
@interface FLEXHTTPTransaction ()
@property (nonatomic, readwrite) NSData *cachedRequestBody;
@interface FLEXNetworkTransaction () {
@protected
NSString *_primaryDescription;
NSString *_secondaryDescription;
NSString *_tertiaryDescription;
}
@end
@implementation FLEXNetworkTransaction
@synthesize primaryDescription = _primaryDescription;
@synthesize secondaryDescription = _secondaryDescription;
@synthesize tertiaryDescription = _tertiaryDescription;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state {
NSString *readableString = nil;
@@ -45,13 +48,39 @@
return readableString;
}
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime {
+ (instancetype)withStartTime:(NSDate *)startTime {
FLEXNetworkTransaction *transaction = [self new];
transaction->_request = request;
transaction->_startTime = startTime;
return transaction;
}
- (BOOL)displayAsError {
return _error != nil;
}
- (NSString *)copyString {
return nil;
}
- (BOOL)matchesQuery:(NSString *)filterString {
return NO;
}
@end
@interface FLEXURLTransaction ()
@end
@implementation FLEXURLTransaction
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime {
FLEXURLTransaction *transaction = [self withStartTime:startTime];
transaction->_request = request;
return transaction;
}
- (NSString *)timestampStringFromRequestDate:(NSDate *)date {
static NSDateFormatter *dateFormatter = nil;
static dispatch_once_t onceToken;
@@ -101,18 +130,18 @@
- (NSString *)tertiaryDescription {
if (!_tertiaryDescription) {
NSMutableArray<NSString *> *detailComponents = [NSMutableArray new];
NSString *timestamp = [self timestampStringFromRequestDate:self.startTime];
if (timestamp.length > 0) {
[detailComponents addObject:timestamp];
}
// Omit method for GET (assumed as default)
NSString *httpMethod = self.request.HTTPMethod;
if (httpMethod.length > 0) {
[detailComponents addObject:httpMethod];
}
if (self.transactionState == FLEXNetworkTransactionStateFinished || self.transactionState == FLEXNetworkTransactionStateFailed) {
[detailComponents addObjectsFromArray:self.details];
} else {
@@ -120,7 +149,7 @@
NSString *state = [self.class readableStringFromTransactionState:self.transactionState];
[detailComponents addObject:state];
}
_tertiaryDescription = [detailComponents componentsJoinedByString:@""];
}
@@ -128,17 +157,24 @@
}
- (void)setTransactionState:(FLEXNetworkTransactionState)transactionState {
_transactionState = transactionState;
super.transactionState = transactionState;
// Reset bottom description
_tertiaryDescription = nil;
}
- (BOOL)displayAsError {
return _error != nil;
- (NSString *)copyString {
return self.request.URL.absoluteString;
}
- (BOOL)matchesQuery:(NSString *)filterString {
return [self.request.URL.absoluteString localizedCaseInsensitiveContainsString:filterString];
}
@end
@interface FLEXHTTPTransaction ()
@property (nonatomic, readwrite) NSData *cachedRequestBody;
@end
@implementation FLEXHTTPTransaction
@@ -225,6 +261,7 @@
// Populate receivedDataLength
if (direction == FLEXWebsocketIncoming) {
wst.receivedDataLength = wst.dataLength;
wst.transactionState = FLEXNetworkTransactionStateFinished;
}
// Populate thumbnail image
@@ -11,10 +11,14 @@
// See the LICENSE file distributed with this work for the terms under
// which Square, Inc. licenses this file to you.
//
// Heavily modified and added to by Tanner Bennett and various other contributors.
// git blame details these modifications.
//
#import "FLEXNetworkObserver.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXUtility.h"
#import "NSUserDefaults+FLEX.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXMethod.h"
@@ -24,7 +28,6 @@
#include <dlfcn.h>
NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification";
static NSString *const kFLEXNetworkObserverEnabledDefaultsKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
typedef void (^NSURLSessionAsyncCompletion)(id fileURLOrData, NSURLResponse *response, NSError *error);
typedef NSURLSessionTask * (^NSURLSessionNewTaskMethod)(NSURLSession *, id, NSURLSessionAsyncCompletion);
@@ -93,7 +96,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
+ (void)setEnabled:(BOOL)enabled {
BOOL previouslyEnabled = [self isEnabled];
[NSUserDefaults.standardUserDefaults setBool:enabled forKey:kFLEXNetworkObserverEnabledDefaultsKey];
NSUserDefaults.standardUserDefaults.flex_networkObserverEnabled = enabled;
if (enabled) {
// Inject if needed. This injection is protected with a dispatch_once, so we're ok calling it multiple times.
@@ -107,7 +110,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
}
+ (BOOL)isEnabled {
return [[NSUserDefaults.standardUserDefaults objectForKey:kFLEXNetworkObserverEnabledDefaultsKey] boolValue];
return NSUserDefaults.standardUserDefaults.flex_networkObserverEnabled;
}
+ (void)load {
@@ -137,9 +140,11 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
#pragma mark Delegate Injection Convenience Methods
/// All swizzled delegate methods should make use of this guard.
/// This will prevent duplicated sniffing when the original implementation calls up to a superclass implementation which we've also swizzled.
/// The superclass implementation (and implementations in classes above that) will be executed without interference if called from the original implementation.
+ (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock {
/// This will prevent duplicated sniffing when the original implementation calls up to a superclass
/// implementation which we've also swizzled. The superclass implementation (and implementations in
/// classes above that) will be executed without interference if called from the original implementation.
+ (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector
sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock {
// If we don't have an object to detect nested calls on, just run the original implementation and bail.
// This case can happen if someone besides the URL loading system calls the delegate methods directly.
// See https://github.com/Flipboard/FLEX/issues/61 for an example.
@@ -183,6 +183,7 @@
referencesSection.selectionAction = ^(UIViewController *host) {
UIViewController *references = [FLEXObjectListViewController
objectsWithReferencesToObject:explorer.object
retained:NO
];
[host.navigationController pushViewController:references animated:YES];
};
@@ -27,6 +27,7 @@
viewer:^UIViewController *(id obj) {
return [FLEXObjectListViewController
instancesOfClassWithName:NSStringFromClass(obj)
retained:NO
];
}
accessoryType:^UITableViewCellAccessoryType(id obj) {
@@ -29,3 +29,5 @@
@interface FLEXShortcutsFactory (WebKit_Safari) @end
@interface FLEXShortcutsFactory (Pasteboard) @end
@interface FLEXShortcutsFactory (FirebaseFirestore) @end
@@ -441,3 +441,16 @@
}];
}
@end
#pragma mark - Firebase Firestore
@implementation FLEXShortcutsFactory (FirebaseFirestore)
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
Class FIRDocumentSnap = NSClassFromString(@"FIRDocumentSnapshot");
if (FIRDocumentSnap) {
FLEXRuntimeUtilityTryAddObjectProperty(2, data, FIRDocumentSnap, NSDictionary, PropertyKey(ReadOnly));
}
}
@end
@@ -154,7 +154,10 @@ FLEXObjectExplorerDefaultsImpl
if (value) {
NSString *title = @"List all references";
[actions addObject:[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
UIViewController *list = [FLEXObjectListViewController objectsWithReferencesToObject:value];
UIViewController *list = [FLEXObjectListViewController
objectsWithReferencesToObject:value
retained:NO
];
[sender.navigationController pushViewController:list animated:YES];
}]];
}
@@ -16,6 +16,7 @@ extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
extern NSString * const kFLEXDefaultsHidePrivateMethodsKey;
extern NSString * const kFLEXDefaultsShowMethodOverridesKey;
extern NSString * const kFLEXDefaultsHideVariablePreviewsKey;
extern NSString * const kFLEXDefaultsNetworkObserverEnabledKey;
extern NSString * const kFLEXDefaultsNetworkHostDenylistKey;
extern NSString * const kFLEXDefaultsDisableOSLogForceASLKey;
extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
@@ -27,6 +28,7 @@ extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
@property (nonatomic) double flex_toolbarTopMargin;
@property (nonatomic) BOOL flex_networkObserverEnabled;
// Not actually stored in defaults, but written to a file
@property (nonatomic) NSArray<NSString *> *flex_networkHostDenylist;
@@ -15,6 +15,7 @@ NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide
NSString * const kFLEXDefaultsHidePrivateMethodsKey = @"com.flipboard.FLEX.hide_private_or_namespaced_methods";
NSString * const kFLEXDefaultsShowMethodOverridesKey = @"com.flipboard.FLEX.show_method_overrides";
NSString * const kFLEXDefaultsHideVariablePreviewsKey = @"com.flipboard.FLEX.hide_variable_previews";
NSString * const kFLEXDefaultsNetworkObserverEnabledKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
NSString * const kFLEXDefaultsNetworkHostDenylistKey = @"com.flipboard.FLEX.network_host_denylist";
NSString * const kFLEXDefaultsDisableOSLogForceASLKey = @"com.flipboard.FLEX.try_disable_os_log";
NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.view_json_as_object";
@@ -62,6 +63,14 @@ NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.vie
[self setDouble:margin forKey:kFLEXDefaultsToolbarTopMarginKey];
}
- (BOOL)flex_networkObserverEnabled {
return [self boolForKey:kFLEXDefaultsNetworkObserverEnabledKey];
}
- (void)setFlex_networkObserverEnabled:(BOOL)enabled {
[self setBool:enabled forKey:kFLEXDefaultsNetworkObserverEnabledKey];
}
- (NSArray<NSString *> *)flex_networkHostDenylist {
return [NSArray arrayWithContentsOfFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostDenylistKey
+8
View File
@@ -7,6 +7,7 @@
//
#import <Foundation/Foundation.h>
@class FLEXObjectRef;
typedef void (^flex_object_enumeration_block_t)(__unsafe_unretained id object, __unsafe_unretained Class actualClass);
@@ -14,4 +15,11 @@ typedef void (^flex_object_enumeration_block_t)(__unsafe_unretained id object, _
+ (void)enumerateLiveObjectsUsingBlock:(flex_object_enumeration_block_t)block;
/// Returned references are not validated beyond containing a valid isa.
/// To validate them yourself, pass each reference's object to \c FLEXPointerIsValidObjcObject
+ (NSArray<FLEXObjectRef *> *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain;
+ (NSArray<FLEXObjectRef *> *)subclassesOfClassWithName:(NSString *)className;
/// Returned references have been validated via \c FLEXPointerIsValidObjcObject
+ (NSArray<FLEXObjectRef *> *)objectsWithReferencesToObject:(id)object retained:(BOOL)retain;
@end
+78
View File
@@ -8,6 +8,9 @@
#import "FLEXHeapEnumerator.h"
#import "FLEXObjcInternal.h"
#import "FLEXObjectRef.h"
#import "NSObject+FLEX_Reflection.h"
#import "NSString+FLEX.h"
#import <malloc/malloc.h>
#import <mach/mach.h>
#import <objc/runtime.h>
@@ -116,4 +119,79 @@ static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_add
free(classes);
}
+ (NSArray<FLEXObjectRef *> *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain {
const char *classNameCString = className.UTF8String;
NSMutableArray *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
// Note: objects of certain classes crash when retain is called.
// It is up to the user to avoid tapping into instance lists for these classes.
// Ex. OS_dispatch_queue_specific_queue
// In the future, we could provide some kind of warning for classes that are known to be problematic.
if (malloc_size((__bridge const void *)(object)) > 0) {
[instances addObject:object];
}
}
}];
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances retained:retain];
return references;
}
+ (NSArray<FLEXObjectRef *> *)subclassesOfClassWithName:(NSString *)className {
NSArray<Class> *classes = FLEXGetAllSubclasses(NSClassFromString(className), NO);
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingClasses:classes];
return references;
}
+ (NSArray<FLEXObjectRef *> *)objectsWithReferencesToObject:(id)object retained:(BOOL)retain {
static Class SwiftObjectClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SwiftObjectClass = NSClassFromString(@"SwiftObject");
if (!SwiftObjectClass) {
SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject");
}
});
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
// Skip known-invalid objects
if (!FLEXPointerIsValidObjcObject((__bridge void *)tryObject)) {
return;
}
// Get all the ivars on the object. Start with the class and and travel up the
// inheritance chain. Once we find a match, record it and move on to the next object.
// There's no reason to find multiple matches within the same object.
Class tryClass = actualClass;
while (tryClass) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
Ivar ivar = ivars[ivarIndex];
NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
if (typeEncoding.flex_typeIsObjectOrClass) {
ptrdiff_t offset = ivar_getOffset(ivar);
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
NSString *ivarName = @(ivar_getName(ivar) ?: "???");
id ref = [FLEXObjectRef referencing:tryObject ivar:ivarName retained:retain];
[instances addObject:ref];
return;
}
}
}
free(ivars);
tryClass = class_getSuperclass(tryClass);
}
}];
return instances;
}
@end
@@ -106,17 +106,18 @@ BOOL FLEXPointerIsReadable(const void *inPtr) {
return NO;
}
// Read the memory
vm_offset_t readMem = 0;
mach_msg_type_number_t size = 0;
#if __arm64e__
address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer);
#else
address = (vm_address_t)inPtr;
#endif
error = vm_read(mach_task_self(), address, sizeof(uintptr_t), &readMem, &size);
// Read the memory
vm_size_t size = 0;
char buf[sizeof(uintptr_t)];
error = vm_read_overwrite(mach_task_self(), address, sizeof(uintptr_t), (vm_address_t)buf, &size);
if (error != KERN_SUCCESS) {
// vm_read returned an error
// vm_read_overwrite returned an error
return NO;
}
@@ -162,22 +163,33 @@ BOOL FLEXPointerIsValidObjcObject(const void *ptr) {
// We check if the returned class is readable because object_getClass
// can return a garbage value when given a non-nil pointer to a non-object
Class cls = object_getClass((__bridge id)ptr);
if (cls && FLEXPointerIsReadable((__bridge void *)cls)) {
// Just because this pointer is readable doesn't mean whatever is at
// it's ISA offset is readable. We need to do the same checks on it's ISA.
// Even this isn't perfect, because once we call object_isClass, we're
// going to dereference a member of the metaclass, which may or may not
// be readable itself. For the time being there is no way to access it
// to check here, and I have yet to hard-code a solution.
Class metaclass = object_getClass(cls);
if (metaclass && FLEXPointerIsReadable((__bridge void *)metaclass)) {
if (object_isClass(cls)) {
return YES;
}
}
if (!cls || !FLEXPointerIsReadable((__bridge void *)cls)) {
return NO;
}
// Just because this pointer is readable doesn't mean whatever is at
// it's ISA offset is readable. We need to do the same checks on it's ISA.
// Even this isn't perfect, because once we call object_isClass, we're
// going to dereference a member of the metaclass, which may or may not
// be readable itself. For the time being there is no way to access it
// to check here, and I have yet to hard-code a solution.
Class metaclass = object_getClass(cls);
if (!metaclass || !FLEXPointerIsReadable((__bridge void *)metaclass)) {
return NO;
}
// Does the class pointer we got appear as a class to the runtime?
if (!object_isClass(cls)) {
return NO;
}
// Is the allocation size at least as large as the expected instance size?
ssize_t instanceSize = class_getInstanceSize(cls);
if (malloc_size(ptr) < instanceSize) {
return NO;
}
return NO;
return YES;
}
@@ -42,7 +42,7 @@ typedef NS_ENUM(NSUInteger, FLEXPropertyAttribute) {
FLEXPropertyAttributeReadOnly = 'R',
FLEXPropertyAttributeRetain = '&',
FLEXPropertyAttributeWeak = 'W'
};
}; //NS_SWIFT_NAME(FLEX.PropertyAttribute);
typedef NS_ENUM(char, FLEXTypeEncoding) {
FLEXTypeEncodingNull = '\0',
@@ -76,4 +76,4 @@ typedef NS_ENUM(char, FLEXTypeEncoding) {
FLEXTypeEncodingBitField = 'b',
FLEXTypeEncodingPointer = '^',
FLEXTypeEncodingConst = 'r'
};
}; //NS_SWIFT_NAME(FLEX.TypeEncoding);
@@ -18,7 +18,7 @@ extern CFSetRef FLEXKnownUnsafeClasses;
static Class cNSObject = nil, cNSProxy = nil;
__attribute__((constructor))
static void FLEXInitKnownRootClasses() {
static void FLEXInitKnownRootClasses(void) {
cNSObject = [NSObject class];
cNSProxy = [NSProxy class];
}
@@ -810,7 +810,7 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
self.scan.scanLocation = scanLocation;
// The return / cleanup code for when the scanned type is already clean
NSString * (^typeIsClean)() = ^NSString * {
NSString * (^typeIsClean)(void) = ^NSString * {
NSString *clean = [self.scan.string
substringWithRange:NSMakeRange(scanLocation, self.scan.scanLocation - scanLocation)
];
@@ -9,6 +9,8 @@
#import "FLEXRuntimeConstants.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXIvar : NSObject
+ (instancetype)ivar:(Ivar)ivar;
@@ -31,17 +33,19 @@
@property (nonatomic, readonly) NSString *details;
/// The full path of the image that contains this ivar definition,
/// or \c nil if this ivar was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
@property (nonatomic, readonly, nullable) NSString *imagePath;
/// For internal use
@property (nonatomic) id tag;
- (id)getValue:(id)target;
- (void)setValue:(id)value onObject:(id)target;
- (nullable id)getValue:(id)target;
- (void)setValue:(nullable id)value onObject:(id)target;
/// Calls into -getValue: and passes that value into
/// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
/// and returns the result
- (id)getPotentiallyUnboxedValue:(id)target;
- (nullable id)getPotentiallyUnboxedValue:(id)target;
@end
NS_ASSUME_NONNULL_END
@@ -24,14 +24,6 @@
#pragma mark Initializers
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
+ (instancetype)ivar:(Ivar)ivar {
return [[self alloc] initWithIvar:ivar];
}
@@ -8,25 +8,17 @@
//
#import <Foundation/Foundation.h>
@class FLEXMethod, FLEXProperty, FLEXIvar, FLEXProtocol;
#import <objc/runtime.h>
@class FLEXMethod, FLEXProperty, FLEXIvar, FLEXProtocol;
NS_ASSUME_NONNULL_BEGIN
#pragma mark FLEXMirror
@interface FLEXMirror : NSObject
#pragma mark FLEXMirror Protocol
NS_SWIFT_NAME(FLEXMirrorProtocol)
@protocol FLEXMirror <NSObject>
/// Reflects an instance of an object or \c Class.
/// @discussion \c FLEXMirror will immediately gather all useful information. Consider using the
/// \c NSObject categories provided if your code will only use a few pieces of information,
/// or if your code needs to run faster.
///
/// If you reflect an instance of a class then \c methods and \c properties will be populated
/// with instance methods and properties. If you reflect a class itself, then \c methods
/// and \c properties will be populated with class methods and properties as you'd expect.
///
/// @param objectOrClass An instance of an objct or a \c Class object.
/// @return An instance of \c FLEXMirror.
+ (instancetype)reflect:(id)objectOrClass;
/// Swift initializer
- (instancetype)initWithSubject:(id)objectOrClass NS_SWIFT_NAME(init(reflecting:));
/// The underlying object or \c Class used to create this \c FLEXMirror instance.
@property (nonatomic, readonly) id value;
@@ -41,7 +33,36 @@
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
/// @return A reflection of \c value.superClass.
@property (nonatomic, readonly) FLEXMirror *superMirror;
@property (nonatomic, readonly, nullable) id<FLEXMirror> superMirror NS_SWIFT_NAME(superMirror);
@end
#pragma mark FLEXMirror Class
@interface FLEXMirror : NSObject <FLEXMirror>
/// Reflects an instance of an object or \c Class.
/// @discussion \c FLEXMirror will immediately gather all useful information. Consider using the
/// \c NSObject categories provided if your code will only use a few pieces of information,
/// or if your code needs to run faster.
///
/// If you reflect an instance of a class then \c methods and \c properties will be populated
/// with instance methods and properties. If you reflect a class itself, then \c methods
/// and \c properties will be populated with class methods and properties as you'd expect.
///
/// @param objectOrClass An instance of an objct or a \c Class object.
/// @return An instance of \c FLEXMirror.
+ (instancetype)reflect:(id)objectOrClass;
@property (nonatomic, readonly) id value;
@property (nonatomic, readonly) BOOL isClass;
@property (nonatomic, readonly) NSString *className;
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
@property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
@property (nonatomic, readonly) NSArray<FLEXMethod *> *methods;
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
@property (nonatomic, readonly, nullable) FLEXMirror *superMirror NS_SWIFT_NAME(superMirror);
@end
@@ -49,12 +70,14 @@
@interface FLEXMirror (ExtendedMirror)
/// @return The method with the given name, or \c nil if one does not exist.
- (FLEXMethod *)methodNamed:(NSString *)name;
- (nullable FLEXMethod *)methodNamed:(nullable NSString *)name;
/// @return The property with the given name, or \c nil if one does not exist.
- (FLEXProperty *)propertyNamed:(NSString *)name;
- (nullable FLEXProperty *)propertyNamed:(nullable NSString *)name;
/// @return The instance variable with the given name, or \c nil if one does not exist.
- (FLEXIvar *)ivarNamed:(NSString *)name;
- (nullable FLEXIvar *)ivarNamed:(nullable NSString *)name;
/// @return The protocol with the given name, or \c nil if one does not exist.
- (FLEXProtocol *)protocolNamed:(NSString *)name;
- (nullable FLEXProtocol *)protocolNamed:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END
@@ -29,15 +29,15 @@
#pragma mark Initialization
+ (instancetype)reflect:(id)objectOrClass {
return [[self alloc] initWithValue:objectOrClass];
return [[self alloc] initWithSubject:objectOrClass];
}
- (id)initWithValue:(id)value {
NSParameterAssert(value);
- (id)initWithSubject:(id)objectOrClass {
NSParameterAssert(objectOrClass);
self = [super init];
if (self) {
_value = value;
_value = objectOrClass;
[self examine];
}
@@ -96,7 +96,7 @@
// Return the given selector if the class responds to it
Class cls = _cls;
SEL (^selectorIfValid)() = ^SEL(SEL sel) {
SEL (^selectorIfValid)(SEL) = ^SEL(SEL sel) {
if (!sel || !cls) return nil;
return [cls instancesRespondToSelector:sel] ? sel : nil;
};
@@ -10,6 +10,8 @@
#import "FLEXRuntimeConstants.h"
@class FLEXProperty, FLEXMethodDescription;
NS_ASSUME_NONNULL_BEGIN
#pragma mark FLEXProtocol
@interface FLEXProtocol : NSObject
@@ -23,22 +25,22 @@
/// The name of the protocol.
@property (nonatomic, readonly) NSString *name;
/// The required methods of the protocol, if any. This includes property getters and setters.
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *requiredMethods;
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *requiredMethods;
/// The optional methods of the protocol, if any. This includes property getters and setters.
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *optionalMethods;
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *optionalMethods;
/// All protocols that this protocol conforms to, if any.
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
/// The full path of the image that contains this protocol definition,
/// or \c nil if this protocol was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
@property (nonatomic, readonly, nullable) NSString *imagePath;
/// The properties in the protocol, if any. \c nil on iOS 10+
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties API_DEPRECATED("Use the more specific accessors below", ios(2.0, 10.0));
@property (nonatomic, readonly, nullable) NSArray<FLEXProperty *> *properties API_DEPRECATED("Use the more specific accessors below", ios(2.0, 10.0));
/// The required properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *requiredProperties API_AVAILABLE(ios(10.0));
@property (nonatomic, readonly) NSArray<FLEXProperty *> *requiredProperties API_AVAILABLE(ios(10.0));
/// The optional properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *optionalProperties API_AVAILABLE(ios(10.0));
@property (nonatomic, readonly) NSArray<FLEXProperty *> *optionalProperties API_AVAILABLE(ios(10.0));
/// For internal use
@property (nonatomic) id tag;
@@ -67,3 +69,5 @@
/// \c YES if this is an instance method, \c NO if it is a class method, or \c nil if unspecified
@property (nonatomic, readonly) NSNumber *instance;
@end
NS_ASSUME_NONNULL_END
@@ -15,14 +15,6 @@
@implementation FLEXProtocol
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
#pragma mark Initializers
+ (NSArray *)allProtocols {
@@ -160,6 +152,9 @@
return [FLEXProperty property:objcproperties[i]];
}];
_requiredProperties = @[];
_optionalProperties = @[];
free(objcproperties);
}
}
@@ -37,7 +37,7 @@
- (void)initSubviews {
self.userInteractionEnabled = YES;
UIImageView * (^newSubviewImageView)() = ^UIImageView *(UIImage *image) {
UIImageView * (^newSubviewImageView)(UIImage *) = ^UIImageView *(UIImage *image) {
UIImageView *iv = [UIImageView new];
iv.image = image;
// iv.userInteractionEnabled = YES;
@@ -122,7 +122,7 @@
/// Recursively hides all views that may be obscuring the given view and collects them
/// in the given array. You should unhide them all when you are done.
+ (void)hideViewsCoveringView:(UIView *)view doWhileHidden:(void(^)())block {
+ (void)hideViewsCoveringView:(UIView *)view doWhileHidden:(void(^)(void))block {
NSMutableArray *viewsToUnhide = [NSMutableArray new];
if ([self _hideViewsCoveringView:view root:view.window hiddenViews:viewsToUnhide]) {
block();
+6
View File
@@ -163,6 +163,12 @@
[task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage *message, NSError *error) {
if (!error) {
NSLog(@"Received WS message: %@", message.string);
id message = [[NSURLSessionWebSocketMessage alloc] initWithString:@"Hello"];
[task sendMessage:message completionHandler:^(NSError *error) {
if (error) {
NSLog(@"Error sending WS message: %@", error.localizedDescription);
}
}];
}
}];
}
+2 -2
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "FLEX"
spec.version = "4.6.0"
spec.version = "4.6.1"
spec.summary = "A set of in-app debugging and exploration tools for iOS"
spec.description = <<-DESC
- Inspect and modify views in the hierarchy.
@@ -35,7 +35,7 @@ Pod::Spec.new do |spec|
spec.frameworks = [ "Foundation", "UIKit", "CoreGraphics", "ImageIO", "QuartzCore", "WebKit", "Security", "SceneKit" ]
spec.libraries = [ "z", "sqlite3" ]
spec.requires_arc = true
spec.compiler_flags = "-Wno-unsupported-availability-guard -Wno-strict-prototypes"
spec.compiler_flags = "-Wno-unsupported-availability-guard"
spec.public_header_files = [ "Classes/*.h", "Classes/Manager/*.h", "Classes/Toolbar/*.h",
"Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h",
"Classes/Core/**/*.h", "Classes/Utility/Runtime/Objc/**/*.h",
+4 -6
View File
@@ -2108,7 +2108,6 @@
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
@@ -2117,13 +2116,13 @@
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Classes/Info.plist;
INSTALL_PATH = "@rpath";
INSTALL_PATH = "@rpath";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
@@ -2145,7 +2144,6 @@
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
@@ -2154,13 +2152,13 @@
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Classes/Info.plist;
INSTALL_PATH = "@rpath";
INSTALL_PATH = "@rpath";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
+27
View File
@@ -9,6 +9,9 @@
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import "NSObject+FLEX_Reflection.h"
#import "FLEXObjcInternal.h"
#import "FLEXHeapEnumerator.h"
#import "FLEXObjectRef.h"
#import "NSArray+FLEX.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXProperty.h"
@@ -25,6 +28,12 @@
@end
@implementation Subclass @end
@interface NeverCreated : Subclass {
NSInteger a, b, c;
}
@end
@implementation NeverCreated @end
@interface FLEXTests : XCTestCase
@property (nonatomic, setter=setMyFoo:) id foo;
@end
@@ -140,4 +149,22 @@
XCTAssertEqualObjects(@"FLEXNewRootClass", className);
}
- (void)testInvalidObjectFinding {
// Create something that looks like an objc object
uintptr_t *pointer = (uintptr_t *)calloc(1, sizeof(uintptr_t));
object_setClass((__bridge id)(void *)pointer, [NeverCreated class]);
// Find that one object and assert that it is the object we just created
NSArray<FLEXObjectRef *> *refs = [FLEXHeapEnumerator
instancesOfClassWithName:@"NeverCreated" retained:NO
];
XCTAssertEqual(refs.count, 1);
XCTAssertEqual((__bridge void *)refs.firstObject.object, pointer);
// Find that the object isn't really an object
XCTAssertFalse(FLEXPointerIsValidObjcObject(pointer));
free(pointer);
}
@end