// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/app/startup/setup_debugging.h"
#import <objc/runtime.h>
#import "base/check.h"
#import "base/logging.h"
#import "base/strings/sys_string_conversions.h"
#import "build/build_config.h"
#import "components/crash/core/common/objc_zombie.h"
namespace {
#if !defined(NDEBUG)
// Swizzles [UIColor colorNamed:] to trigger a DCHECK if an invalid color is
// attempted to be loaded.
void SwizzleUIColorColorNamed() {
// The original implementation of [UIColor colorNamed:].
// Called by the new implementation.
static IMP originalImp;
IMP* originalImpPtr = &originalImp;
id swizzleBlock = ^(id self, NSString* colorName) {
// Call the original [UIColor colorNamed:] method.
UIColor* (*imp)(id, SEL, id) =
(UIColor * (*)(id, SEL, id)) * originalImpPtr;
Class aClass = objc_getClass("UIColor");
UIColor* color = imp(aClass, @selector(colorNamed:), colorName);
DCHECK(color) << "Missing color: " << base::SysNSStringToUTF8(colorName);
return color;
};
Method method = class_getClassMethod([UIColor class], @selector(colorNamed:));
DCHECK(method);
IMP blockImp = imp_implementationWithBlock(swizzleBlock);
originalImp = method_setImplementation(method, blockImp);
}
// Swizzles [UIImage imageNamed:] to trigger a DCHECK if an invalid image is
// attempted to be loaded.
void SwizzleUIImageImageNamed() {
// Retained by the swizzle block.
// A set of image names that are exceptions to the 'missing image' check.
NSMutableSet* exceptions = [NSMutableSet set];
// TODO(crbug.com/41318097): Add missing image.
[exceptions addObject:@"card_close_button_pressed_incognito"];
// TODO(crbug.com/41318110): Add missing image.
[exceptions addObject:@"find_close_pressed_incognito"];
// TODO(crbug.com/40519792): Add missing images.
[exceptions addObject:@"glif-mic-to-dots-small_37"];
[exceptions addObject:@"glif-mic-to-dots-large_37"];
[exceptions addObject:@"glif-google-to-dots_28"];
// TODO(crbug.com/41318906): Add missing image.
[exceptions addObject:@"voice_icon_keyboard_accessory"];
// The original implementation of [UIImage imageNamed:].
// Called by the new implementation.
static IMP originalImp;
IMP* originalImpPtr = &originalImp;
id swizzleBlock = ^(id self, NSString* imageName) {
// Call the original [UIImage imageNamed:] method.
UIImage* (*imp)(id, SEL, id) = (UIImage*(*)(id,SEL,id))*originalImpPtr;
Class aClass = objc_getClass("UIImage");
UIImage* image = imp(aClass, @selector(imageNamed:), imageName);
if (![exceptions containsObject:imageName] &&
![imageName containsString:@".FAUXBUNDLEID."]) {
// TODO(crbug.com/40225445): Temporarily turn off DCHECK while bootstrapping
// Catalyst. Log the error to the console instead.
#if BUILDFLAG(IS_IOS_MACCATALYST)
DLOG(ERROR) << "Missing image: " << base::SysNSStringToUTF8(imageName);
#else
DCHECK(image) << "Missing image: " << base::SysNSStringToUTF8(imageName);
#endif
}
return image;
};
Method method = class_getClassMethod([UIImage class], @selector(imageNamed:));
DCHECK(method);
IMP blockImp = imp_implementationWithBlock(swizzleBlock);
originalImp = method_setImplementation(method, blockImp);
}
// Swizzles +[UIImage imageWithContentsOfFile:] to trigger a DCHECK if an
// invalid image is attempted to be loaded.
void SwizzleUIImageImageWithContentsOfFile() {
// The original implementation of [UIImage imageWithContentsOfFile:].
// Called by the new implementation.
static IMP original_imp;
IMP* original_imp_ptr = &original_imp;
id swizzle_block = ^(id self, NSString* path) {
// Call the original [UIImage imageWithContentsOfFile:] method.
UIImage* (*imp)(id, SEL, id) =
reinterpret_cast<UIImage* (*)(id, SEL, id)>(*original_imp_ptr);
Class class_object = objc_getClass("UIImage");
UIImage* image =
imp(class_object, @selector(imageWithContentsOfFile:), path);
DCHECK(image) << "Missing image at path: " << base::SysNSStringToUTF8(path);
return image;
};
Method method = class_getClassMethod([UIImage class],
@selector(imageWithContentsOfFile:));
DCHECK(method);
IMP block_imp = imp_implementationWithBlock(swizzle_block);
original_imp = method_setImplementation(method, block_imp);
}
// Swizzles +[NSData dataWithContentsOfFile:] to trigger a DCHECK if an invalid
// image is attempted to be loaded.
void SwizzleNSDataDataWithContentsOfFile() {
// The original implementation of [NSData dataWithContentsOfFile:].
// Called by the new implementation.
static IMP original_imp;
IMP* original_imp_ptr = &original_imp;
// Retained by the swizzle block.
// A set of file extensions that are exceptions to the 'missing data' check.
NSMutableSet* exceptions = [NSMutableSet set];
[exceptions addObject:@"plist"];
[exceptions addObject:@"png"];
[exceptions addObject:@"jpg"];
// Can have no path extension.
[exceptions addObject:@""];
id swizzle_block = ^(id self, NSString* path) {
// Call the original [NSData dataWithContentsOfFile:] method.
NSData* (*imp)(id, SEL, id) =
reinterpret_cast<NSData* (*)(id, SEL, id)>(*original_imp_ptr);
Class class_object = objc_getClass("NSData");
NSData* data = imp(class_object, @selector(dataWithContentsOfFile:), path);
if (![exceptions containsObject:[path pathExtension]] &&
[path pathExtension]) {
DCHECK(data) << "Missing data at path: " << base::SysNSStringToUTF8(path);
}
return data;
};
Method method =
class_getClassMethod([NSData class], @selector(dataWithContentsOfFile:));
DCHECK(method);
IMP block_imp = imp_implementationWithBlock(swizzle_block);
original_imp = method_setImplementation(method, block_imp);
}
#endif // !defined(NDEBUG)
} // namespace
@implementation SetupDebugging
+ (void)setUpDebuggingOptions {
// Enable the zombie treadmill on simulator builds.
// TODO(crbug.com/40492640): Consider enabling this on device builds too.
#if TARGET_IPHONE_SIMULATOR
DCHECK(ObjcEvilDoers::ZombieEnable(true, 10000));
#endif
#if !defined(NDEBUG)
// Enable the detection of missing assets.
SwizzleUIColorColorNamed();
SwizzleUIImageImageNamed();
SwizzleUIImageImageWithContentsOfFile();
SwizzleNSDataDataWithContentsOfFile();
#endif
}
@end