Skip to content

Instantly share code, notes, and snippets.

@JohnCoates
Created May 19, 2017 15:28
Show Gist options
  • Save JohnCoates/605319ab31c0aa05c77973481aeae4b2 to your computer and use it in GitHub Desktop.
Save JohnCoates/605319ab31c0aa05c77973481aeae4b2 to your computer and use it in GitHub Desktop.
Fixes UI code coverage on builds run from xcode build
@import ObjectiveC.runtime;
@import MachO.dyld;
@import Foundation;
@interface IDELaunchParametersSnapshot : NSObject
+ (id)launchParametersWithSchemeIdentifier:(id)schemeIdentifier
launcherIdentifier:(id)launcherIdentifier
debuggerIdentifier:(id)debuggerIdentifier
launchStyle:(int)launchStyle
runnableLocation:(id)runnableLocation
debugProcessAsUID:(unsigned int)debugProcessAsUID
workingDirectory:(id)workingDirectory
commandLineArgs:(id)commandLineArgs
environmentVariables:(id)environmentVariables
architecture:(id)architecture
platformIdentifier:(id)platformIdentifier
buildConfiguration:(id)buildConfiguration
buildableProduct:(id)buildableProduct
deviceAppDataPackage:(id)deviceAppDataPackage
allowLocationSimulation:(BOOL)allowLocationSimulation
locationScenarioReference:(id)locationScenarioReference
showNonLocalizedStrings:(BOOL)showNonLocalizedStrings
language:(id)language
region:(id)region
routingCoverageFileReference:(id)routingCoverageFileReference
enableGPUFrameCaptureMode:(int)enableGPUFrameCaptureMode
enableGPUValidationMode:(int)enableGPUValidationMode
debugXPCServices:(BOOL)debugXPCServices
debugAppExtensions:(BOOL)debugAppExtensions
internalIOSLaunchStyle:(int)internalIOSLaunchStyle
internalIOSSubstitutionApp:(id)internalIOSSubstitutionApp
launchAutomaticallySubstyle:(unsigned long long)launchAutomaticallySubstyle;
@end
@interface IDELaunchParametersSnapshot (FixUICoverage)
@end
extern NSString * const IDEDefaultLauncherIdentifier;
extern NSString * const IDEDefaultDebuggerIdentifier;
@implementation IDELaunchParametersSnapshot (FixUICoverage)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(launchParametersWithSchemeIdentifier:launcherIdentifier:debuggerIdentifier:launchStyle:runnableLocation:debugProcessAsUID:workingDirectory:commandLineArgs:environmentVariables:architecture:platformIdentifier:buildConfiguration:buildableProduct:deviceAppDataPackage:allowLocationSimulation:locationScenarioReference:showNonLocalizedStrings:language:region:routingCoverageFileReference:enableGPUFrameCaptureMode:enableGPUValidationMode:debugXPCServices:debugAppExtensions:internalIOSLaunchStyle:internalIOSSubstitutionApp:launchAutomaticallySubstyle:);
SEL hookSelector = @selector(hook_launchParametersWithSchemeIdentifier:launcherIdentifier:debuggerIdentifier:launchStyle:runnableLocation:debugProcessAsUID:workingDirectory:commandLineArgs:environmentVariables:architecture:platformIdentifier:buildConfiguration:buildableProduct:deviceAppDataPackage:allowLocationSimulation:locationScenarioReference:showNonLocalizedStrings:language:region:routingCoverageFileReference:enableGPUFrameCaptureMode:enableGPUValidationMode:debugXPCServices:debugAppExtensions:internalIOSLaunchStyle:internalIOSSubstitutionApp:launchAutomaticallySubstyle:);
SEL storedOriginalSelector = @selector(original_launchParametersWithSchemeIdentifier:launcherIdentifier:debuggerIdentifier:launchStyle:runnableLocation:debugProcessAsUID:workingDirectory:commandLineArgs:environmentVariables:architecture:platformIdentifier:buildConfiguration:buildableProduct:deviceAppDataPackage:allowLocationSimulation:locationScenarioReference:showNonLocalizedStrings:language:region:routingCoverageFileReference:enableGPUFrameCaptureMode:enableGPUValidationMode:debugXPCServices:debugAppExtensions:internalIOSLaunchStyle:internalIOSSubstitutionApp:launchAutomaticallySubstyle:);
Class instanceClass = self.class;
Class metaClass = objc_getMetaClass(object_getClassName(instanceClass));
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(metaClass, &methodCount);
const char *targetSelectorName = sel_getName(originalSelector);
BOOL foundSelector = FALSE;
for (int i=0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
const char *name = sel_getName(selector);
if (strcmp(name, targetSelectorName) == 0) {
foundSelector = TRUE;
break;
}
}
if (!foundSelector) {
NSLog(@"ERROR: Not implementing UI Coverage fix, couldn't find target selector %s", targetSelectorName);
return;
}
Method originalMethod = class_getInstanceMethod(metaClass, originalSelector);
Method hookMethod = class_getInstanceMethod(metaClass, hookSelector);
const char *originalTypeEncoding = method_getTypeEncoding(originalMethod);
const char *hookedTypeEncoding = method_getTypeEncoding(hookMethod);
if (strcmp(originalTypeEncoding, hookedTypeEncoding) != 0) {
NSLog(@"ERROR: Not implementing UI Coverage fix, target type encoding %s doesn't match hook type encoding: %s",
originalTypeEncoding, hookedTypeEncoding);
return;
}
IMP originalImplementation = method_getImplementation(originalMethod);
IMP hookImplementation = method_getImplementation(hookMethod);
class_replaceMethod(metaClass, storedOriginalSelector, originalImplementation, originalTypeEncoding);
class_replaceMethod(metaClass, originalSelector, hookImplementation, hookedTypeEncoding);
NSLog(@"Implemented UI Coverage fix");
});
}
// MARK: - Hook
+ (id)hook_launchParametersWithSchemeIdentifier:(id)schemeIdentifier
launcherIdentifier:(id)launcherIdentifier
debuggerIdentifier:(id)debuggerIdentifier
launchStyle:(int)launchStyle
runnableLocation:(id)runnableLocation
debugProcessAsUID:(unsigned int)debugProcessAsUID
workingDirectory:(id)workingDirectory
commandLineArgs:(id)commandLineArgs
environmentVariables:(id)environmentVariables
architecture:(id)architecture
platformIdentifier:(id)platformIdentifier
buildConfiguration:(id)buildConfiguration
buildableProduct:(id)buildableProduct
deviceAppDataPackage:(id)deviceAppDataPackage
allowLocationSimulation:(BOOL)allowLocationSimulation
locationScenarioReference:(id)locationScenarioReference
showNonLocalizedStrings:(BOOL)showNonLocalizedStrings
language:(id)language
region:(id)region
routingCoverageFileReference:(id)routingCoverageFileReference
enableGPUFrameCaptureMode:(int)enableGPUFrameCaptureMode
enableGPUValidationMode:(int)enableGPUValidationMode
debugXPCServices:(BOOL)debugXPCServices
debugAppExtensions:(BOOL)debugAppExtensions
internalIOSLaunchStyle:(int)internalIOSLaunchStyle
internalIOSSubstitutionApp:(id)internalIOSSubstitutionApp
launchAutomaticallySubstyle:(unsigned long long)launchAutomaticallySubstyle {
NSLog(@"UI Coverage Fix: Using LLDB Launcher & Debugger");
id newLauncher = IDEDefaultLauncherIdentifier;
id newDebugger = IDEDefaultDebuggerIdentifier;
NSLog(@"launcherIdentifier original: %@, new: %@", launcherIdentifier, newLauncher);
NSLog(@"debuggerIdentifier original: %@, new: %@", debuggerIdentifier, newDebugger);
return [self original_launchParametersWithSchemeIdentifier:schemeIdentifier
launcherIdentifier:newLauncher
debuggerIdentifier:newDebugger
launchStyle:launchStyle
runnableLocation:runnableLocation
debugProcessAsUID:debugProcessAsUID
workingDirectory:workingDirectory
commandLineArgs:commandLineArgs
environmentVariables:environmentVariables
architecture:architecture
platformIdentifier:platformIdentifier
buildConfiguration:buildConfiguration
buildableProduct:buildableProduct
deviceAppDataPackage:deviceAppDataPackage
allowLocationSimulation:allowLocationSimulation
locationScenarioReference:locationScenarioReference
showNonLocalizedStrings:showNonLocalizedStrings
language:language
region:region
routingCoverageFileReference:routingCoverageFileReference
enableGPUFrameCaptureMode:enableGPUFrameCaptureMode
enableGPUValidationMode:enableGPUValidationMode
debugXPCServices:debugXPCServices
debugAppExtensions:debugAppExtensions
internalIOSLaunchStyle:internalIOSLaunchStyle
internalIOSSubstitutionApp:internalIOSSubstitutionApp
launchAutomaticallySubstyle:launchAutomaticallySubstyle];
}
// MARK: - Original gets stored here
+ (id)original_launchParametersWithSchemeIdentifier:(id)schemeIdentifier
launcherIdentifier:(id)launcherIdentifier
debuggerIdentifier:(id)debuggerIdentifier
launchStyle:(int)launchStyle
runnableLocation:(id)runnableLocation
debugProcessAsUID:(unsigned int)debugProcessAsUID
workingDirectory:(id)workingDirectory
commandLineArgs:(id)commandLineArgs
environmentVariables:(id)environmentVariables
architecture:(id)architecture
platformIdentifier:(id)platformIdentifier
buildConfiguration:(id)buildConfiguration
buildableProduct:(id)buildableProduct
deviceAppDataPackage:(id)deviceAppDataPackage
allowLocationSimulation:(BOOL)allowLocationSimulation
locationScenarioReference:(id)locationScenarioReference
showNonLocalizedStrings:(BOOL)showNonLocalizedStrings
language:(id)language
region:(id)region
routingCoverageFileReference:(id)routingCoverageFileReference
enableGPUFrameCaptureMode:(int)enableGPUFrameCaptureMode
enableGPUValidationMode:(int)enableGPUValidationMode
debugXPCServices:(BOOL)debugXPCServices
debugAppExtensions:(BOOL)debugAppExtensions
internalIOSLaunchStyle:(int)internalIOSLaunchStyle
internalIOSSubstitutionApp:(id)internalIOSSubstitutionApp
launchAutomaticallySubstyle:(unsigned long long)launchAutomaticallySubstyle {
[NSException raise:@"FailedHook" format:@"Hook failed: This method should never be run."];
return nil;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment