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

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

mercurial