toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 // Copyright (c) 2012, Google Inc.
     2 // All rights reserved.
     3 //
     4 // Redistribution and use in source and binary forms, with or without
     5 // modification, are permitted provided that the following conditions are
     6 // met:
     7 //
     8 //     * Redistributions of source code must retain the above copyright
     9 // notice, this list of conditions and the following disclaimer.
    10 //     * Redistributions in binary form must reproduce the above
    11 // copyright notice, this list of conditions and the following disclaimer
    12 // in the documentation and/or other materials provided with the
    13 // distribution.
    14 //     * Neither the name of Google Inc. nor the names of its
    15 // contributors may be used to endorse or promote products derived from
    16 // this software without specific prior written permission.
    17 //
    18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    30 #import "BreakpadController.h"
    32 #import <UIKit/UIKit.h>
    33 #include <asl.h>
    34 #include <execinfo.h>
    35 #include <signal.h>
    36 #include <unistd.h>
    37 #include <sys/sysctl.h>
    39 #include <common/scoped_ptr.h>
    41 #pragma mark -
    42 #pragma mark Private Methods
    44 @interface BreakpadController ()
    46 // Init the singleton instance.
    47 - (id)initSingleton;
    49 // Load a crash report and send it to the server.
    50 - (void)sendStoredCrashReports;
    52 // Returns when a report can be sent. |-1| means never, |0| means that a report
    53 // can be sent immediately, a positive number is the number of seconds to wait
    54 // before being allowed to upload a report.
    55 - (int)sendDelay;
    57 // Notifies that a report will be sent, and update the last sending time
    58 // accordingly.
    59 - (void)reportWillBeSent;
    61 @end
    63 #pragma mark -
    64 #pragma mark Anonymous namespace
    66 namespace {
    68 // The name of the user defaults key for the last submission to the crash
    69 // server.
    70 NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission";
    72 // Returns a NSString describing the current platform.
    73 NSString* GetPlatform() {
    74   // Name of the system call for getting the platform.
    75   static const char kHwMachineSysctlName[] = "hw.machine";
    77   NSString* result = nil;
    79   size_t size = 0;
    80   if (sysctlbyname(kHwMachineSysctlName, NULL, &size, NULL, 0) || size == 0)
    81     return nil;
    82   google_breakpad::scoped_array<char> machine(new char[size]);
    83   if (sysctlbyname(kHwMachineSysctlName, machine.get(), &size, NULL, 0) == 0)
    84     result = [NSString stringWithUTF8String:machine.get()];
    85   return result;
    86 }
    88 }  // namespace
    90 #pragma mark -
    91 #pragma mark BreakpadController Implementation
    93 @implementation BreakpadController
    95 + (BreakpadController*)sharedInstance {
    96   @synchronized(self) {
    97     static BreakpadController* sharedInstance_ =
    98         [[BreakpadController alloc] initSingleton];
    99     return sharedInstance_;
   100   }
   101 }
   103 - (id)init {
   104   return nil;
   105 }
   107 - (id)initSingleton {
   108   self = [super init];
   109   if (self) {
   110     queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL);
   111     configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy];
   112     enableUploads_ = NO;
   113     started_ = NO;
   114     NSString* uploadInterval =
   115         [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
   116     [self setUploadInterval:[uploadInterval intValue]];
   117   }
   118   return self;
   119 }
   121 // Since this class is a singleton, this method is not expected to be called.
   122 - (void)dealloc {
   123   assert(!breakpadRef_);
   124   dispatch_release(queue_);
   125   [configuration_ release];
   126   [super dealloc];
   127 }
   129 #pragma mark -
   131 - (void)start:(BOOL)onCurrentThread {
   132   if (started_)
   133     return;
   134   started_ = YES;
   135   void(^startBlock)() = ^{
   136       assert(!breakpadRef_);
   137       breakpadRef_ = BreakpadCreate(configuration_);
   138       if (breakpadRef_) {
   139         BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform());
   140       }
   141   };
   142   if (onCurrentThread)
   143     startBlock();
   144   else
   145     dispatch_async(queue_, startBlock);
   146 }
   148 - (void)stop {
   149   if (!started_)
   150     return;
   151   started_ = NO;
   152   dispatch_sync(queue_, ^{
   153       if (breakpadRef_) {
   154         BreakpadRelease(breakpadRef_);
   155         breakpadRef_ = NULL;
   156       }
   157   });
   158 }
   160 - (void)setUploadingEnabled:(BOOL)enabled {
   161   NSAssert(started_,
   162       @"The controller must be started before setUploadingEnabled is called");
   163   dispatch_async(queue_, ^{
   164       if (enabled == enableUploads_)
   165         return;
   166       if (enabled) {
   167         // Set this before calling doSendStoredCrashReport, because that
   168         // calls sendDelay, which in turn checks this flag.
   169         enableUploads_ = YES;
   170         [self sendStoredCrashReports];
   171       } else {
   172         enableUploads_ = NO;
   173         [NSObject cancelPreviousPerformRequestsWithTarget:self
   174             selector:@selector(sendStoredCrashReports)
   175             object:nil];
   176       }
   177   });
   178 }
   180 - (void)updateConfiguration:(NSDictionary*)configuration {
   181   NSAssert(!started_,
   182       @"The controller must not be started when updateConfiguration is called");
   183   [configuration_ addEntriesFromDictionary:configuration];
   184   NSString* uploadInterval =
   185       [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
   186   if (uploadInterval)
   187     [self setUploadInterval:[uploadInterval intValue]];
   188 }
   190 - (void)setUploadingURL:(NSString*)url {
   191   NSAssert(!started_,
   192       @"The controller must not be started when setUploadingURL is called");
   193   [configuration_ setValue:url forKey:@BREAKPAD_URL];
   194 }
   196 - (void)setUploadInterval:(int)intervalInSeconds {
   197   NSAssert(!started_,
   198       @"The controller must not be started when setUploadInterval is called");
   199   [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL];
   200   uploadIntervalInSeconds_ = intervalInSeconds;
   201   if (uploadIntervalInSeconds_ < 0)
   202     uploadIntervalInSeconds_ = 0;
   203 }
   205 - (void)addUploadParameter:(NSString*)value forKey:(NSString*)key {
   206   NSAssert(started_,
   207       @"The controller must be started before addUploadParameter is called");
   208   dispatch_async(queue_, ^{
   209       if (breakpadRef_)
   210         BreakpadAddUploadParameter(breakpadRef_, key, value);
   211   });
   212 }
   214 - (void)removeUploadParameterForKey:(NSString*)key {
   215   NSAssert(started_, @"The controller must be started before "
   216                      "removeUploadParameterForKey is called");
   217   dispatch_async(queue_, ^{
   218       if (breakpadRef_)
   219         BreakpadRemoveUploadParameter(breakpadRef_, key);
   220   });
   221 }
   223 - (void)withBreakpadRef:(void(^)(BreakpadRef))callback {
   224   NSAssert(started_,
   225       @"The controller must be started before withBreakpadRef is called");
   226   dispatch_async(queue_, ^{
   227       callback(breakpadRef_);
   228   });
   229 }
   231 - (void)hasReportToUpload:(void(^)(BOOL))callback {
   232   NSAssert(started_, @"The controller must be started before "
   233                      "hasReportToUpload is called");
   234   dispatch_async(queue_, ^{
   235       callback(breakpadRef_ && BreakpadHasCrashReportToUpload(breakpadRef_));
   236   });
   237 }
   239 #pragma mark -
   241 - (int)sendDelay {
   242   if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_)
   243     return -1;
   245   // To prevent overloading the crash server, crashes are not sent than one
   246   // report every |uploadIntervalInSeconds_|. A value in the user defaults is
   247   // used to keep the time of the last upload.
   248   NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
   249   NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission];
   250   NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
   251   NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime;
   253   if (spanSeconds >= uploadIntervalInSeconds_)
   254     return 0;
   255   return uploadIntervalInSeconds_ - static_cast<int>(spanSeconds);
   256 }
   258 - (void)reportWillBeSent {
   259   NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
   260   [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()]
   261                    forKey:kLastSubmission];
   262   [userDefaults synchronize];
   263 }
   265 - (void)sendStoredCrashReports {
   266   dispatch_async(queue_, ^{
   267       if (!BreakpadHasCrashReportToUpload(breakpadRef_))
   268         return;
   270       int timeToWait = [self sendDelay];
   272       // Unable to ever send report.
   273       if (timeToWait == -1)
   274         return;
   276       // A report can be sent now.
   277       if (timeToWait == 0) {
   278         [self reportWillBeSent];
   279         BreakpadUploadNextReport(breakpadRef_);
   281         // If more reports must be sent, make sure this method is called again.
   282         if (BreakpadHasCrashReportToUpload(breakpadRef_))
   283           timeToWait = uploadIntervalInSeconds_;
   284       }
   286       // A report must be sent later.
   287       if (timeToWait > 0)
   288         [self performSelector:@selector(sendStoredCrashReports)
   289                    withObject:nil
   290                    afterDelay:timeToWait];
   291   });
   292 }
   294 @end

mercurial