diff options
Diffstat (limited to 'toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm')
-rw-r--r-- | toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm b/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm new file mode 100644 index 000000000..dd71cff68 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm @@ -0,0 +1,354 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "BreakpadController.h" + +#import <UIKit/UIKit.h> +#include <asl.h> +#include <execinfo.h> +#include <signal.h> +#include <unistd.h> +#include <sys/sysctl.h> + +#include <common/scoped_ptr.h> + +#pragma mark - +#pragma mark Private Methods + +@interface BreakpadController () + +// Init the singleton instance. +- (id)initSingleton; + +// Load a crash report and send it to the server. +- (void)sendStoredCrashReports; + +// Returns when a report can be sent. |-1| means never, |0| means that a report +// can be sent immediately, a positive number is the number of seconds to wait +// before being allowed to upload a report. +- (int)sendDelay; + +// Notifies that a report will be sent, and update the last sending time +// accordingly. +- (void)reportWillBeSent; + +@end + +#pragma mark - +#pragma mark Anonymous namespace + +namespace { + +// The name of the user defaults key for the last submission to the crash +// server. +NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission"; + +// Returns a NSString describing the current platform. +NSString* GetPlatform() { + // Name of the system call for getting the platform. + static const char kHwMachineSysctlName[] = "hw.machine"; + + NSString* result = nil; + + size_t size = 0; + if (sysctlbyname(kHwMachineSysctlName, NULL, &size, NULL, 0) || size == 0) + return nil; + google_breakpad::scoped_array<char> machine(new char[size]); + if (sysctlbyname(kHwMachineSysctlName, machine.get(), &size, NULL, 0) == 0) + result = [NSString stringWithUTF8String:machine.get()]; + return result; +} + +} // namespace + +#pragma mark - +#pragma mark BreakpadController Implementation + +@implementation BreakpadController + ++ (BreakpadController*)sharedInstance { + @synchronized(self) { + static BreakpadController* sharedInstance_ = + [[BreakpadController alloc] initSingleton]; + return sharedInstance_; + } +} + +- (id)init { + return nil; +} + +- (id)initSingleton { + self = [super init]; + if (self) { + queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL); + enableUploads_ = NO; + started_ = NO; + [self resetConfiguration]; + } + return self; +} + +// Since this class is a singleton, this method is not expected to be called. +- (void)dealloc { + assert(!breakpadRef_); + dispatch_release(queue_); + [configuration_ release]; + [uploadTimeParameters_ release]; + [super dealloc]; +} + +#pragma mark - + +- (void)start:(BOOL)onCurrentThread { + if (started_) + return; + started_ = YES; + void(^startBlock)() = ^{ + assert(!breakpadRef_); + breakpadRef_ = BreakpadCreate(configuration_); + if (breakpadRef_) { + BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform()); + } + }; + if (onCurrentThread) + startBlock(); + else + dispatch_async(queue_, startBlock); +} + +- (void)stop { + if (!started_) + return; + started_ = NO; + dispatch_sync(queue_, ^{ + if (breakpadRef_) { + BreakpadRelease(breakpadRef_); + breakpadRef_ = NULL; + } + }); +} + +// This method must be called from the breakpad queue. +- (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration + withBreakpadRef:(BreakpadRef)ref { + NSAssert(started_, @"The controller must be started before " + "threadUnsafeSendReportWithConfiguration is called"); + if (breakpadRef_) { + BreakpadUploadReportWithParametersAndConfiguration(breakpadRef_, + uploadTimeParameters_, + configuration); + } +} + +- (void)setUploadingEnabled:(BOOL)enabled { + NSAssert(started_, + @"The controller must be started before setUploadingEnabled is called"); + dispatch_async(queue_, ^{ + if (enabled == enableUploads_) + return; + if (enabled) { + // Set this before calling doSendStoredCrashReport, because that + // calls sendDelay, which in turn checks this flag. + enableUploads_ = YES; + [self sendStoredCrashReports]; + } else { + enableUploads_ = NO; + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(sendStoredCrashReports) + object:nil]; + } + }); +} + +- (void)updateConfiguration:(NSDictionary*)configuration { + NSAssert(!started_, + @"The controller must not be started when updateConfiguration is called"); + [configuration_ addEntriesFromDictionary:configuration]; + NSString* uploadInterval = + [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; + if (uploadInterval) + [self setUploadInterval:[uploadInterval intValue]]; +} + +- (void)resetConfiguration { + NSAssert(!started_, + @"The controller must not be started when resetConfiguration is called"); + [configuration_ autorelease]; + configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy]; + NSString* uploadInterval = + [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; + [self setUploadInterval:[uploadInterval intValue]]; + [self setParametersToAddAtUploadTime:nil]; +} + +- (void)setUploadingURL:(NSString*)url { + NSAssert(!started_, + @"The controller must not be started when setUploadingURL is called"); + [configuration_ setValue:url forKey:@BREAKPAD_URL]; +} + +- (void)setUploadInterval:(int)intervalInSeconds { + NSAssert(!started_, + @"The controller must not be started when setUploadInterval is called"); + [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL]; + uploadIntervalInSeconds_ = intervalInSeconds; + if (uploadIntervalInSeconds_ < 0) + uploadIntervalInSeconds_ = 0; +} + +- (void)setParametersToAddAtUploadTime:(NSDictionary*)uploadTimeParameters { + NSAssert(!started_, @"The controller must not be started when " + "setParametersToAddAtUploadTime is called"); + [uploadTimeParameters_ autorelease]; + uploadTimeParameters_ = [uploadTimeParameters copy]; +} + +- (void)addUploadParameter:(NSString*)value forKey:(NSString*)key { + NSAssert(started_, + @"The controller must be started before addUploadParameter is called"); + dispatch_async(queue_, ^{ + if (breakpadRef_) + BreakpadAddUploadParameter(breakpadRef_, key, value); + }); +} + +- (void)removeUploadParameterForKey:(NSString*)key { + NSAssert(started_, @"The controller must be started before " + "removeUploadParameterForKey is called"); + dispatch_async(queue_, ^{ + if (breakpadRef_) + BreakpadRemoveUploadParameter(breakpadRef_, key); + }); +} + +- (void)withBreakpadRef:(void(^)(BreakpadRef))callback { + NSAssert(started_, + @"The controller must be started before withBreakpadRef is called"); + dispatch_async(queue_, ^{ + callback(breakpadRef_); + }); +} + +- (void)hasReportToUpload:(void(^)(BOOL))callback { + NSAssert(started_, @"The controller must be started before " + "hasReportToUpload is called"); + dispatch_async(queue_, ^{ + callback(breakpadRef_ && (BreakpadGetCrashReportCount(breakpadRef_) > 0)); + }); +} + +- (void)getCrashReportCount:(void(^)(int))callback { + NSAssert(started_, @"The controller must be started before " + "getCrashReportCount is called"); + dispatch_async(queue_, ^{ + callback(breakpadRef_ ? BreakpadGetCrashReportCount(breakpadRef_) : 0); + }); +} + +- (void)getNextReportConfigurationOrSendDelay: + (void(^)(NSDictionary*, int))callback { + NSAssert(started_, @"The controller must be started before " + "getNextReportConfigurationOrSendDelay is called"); + dispatch_async(queue_, ^{ + if (!breakpadRef_) { + callback(nil, -1); + return; + } + int delay = [self sendDelay]; + if (delay != 0) { + callback(nil, delay); + return; + } + [self reportWillBeSent]; + callback(BreakpadGetNextReportConfiguration(breakpadRef_), 0); + }); +} + +#pragma mark - + +- (int)sendDelay { + if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_) + return -1; + + // To prevent overloading the crash server, crashes are not sent than one + // report every |uploadIntervalInSeconds_|. A value in the user defaults is + // used to keep the time of the last upload. + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission]; + NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0; + NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime; + + if (spanSeconds >= uploadIntervalInSeconds_) + return 0; + return uploadIntervalInSeconds_ - static_cast<int>(spanSeconds); +} + +- (void)reportWillBeSent { + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()] + forKey:kLastSubmission]; + [userDefaults synchronize]; +} + +- (void)sendStoredCrashReports { + dispatch_async(queue_, ^{ + if (BreakpadGetCrashReportCount(breakpadRef_) == 0) + return; + + int timeToWait = [self sendDelay]; + + // Unable to ever send report. + if (timeToWait == -1) + return; + + // A report can be sent now. + if (timeToWait == 0) { + [self reportWillBeSent]; + BreakpadUploadNextReportWithParameters(breakpadRef_, + uploadTimeParameters_); + + // If more reports must be sent, make sure this method is called again. + if (BreakpadGetCrashReportCount(breakpadRef_) > 0) + timeToWait = uploadIntervalInSeconds_; + } + + // A report must be sent later. + if (timeToWait > 0) { + // performSelector: doesn't work on queue_ + dispatch_async(dispatch_get_main_queue(), ^{ + [self performSelector:@selector(sendStoredCrashReports) + withObject:nil + afterDelay:timeToWait]; + }); + } + }); +} + +@end |