1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
// 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 "chrome/browser/mac/exception_processor.h"
#import <Cocoa/Cocoa.h>
#include <stddef.h>
#include <sys/wait.h>
#include "base/mac/os_crash_dumps.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
class ExceptionProcessorTest : public testing::Test {
public:
ExceptionProcessorTest() {
features_.InitWithFeatures({kForceCrashOnExceptions}, {});
}
protected:
base::test::ScopedFeatureList features_;
};
void RaiseExceptionInRunLoop() {
CFRunLoopRef run_loop = CFRunLoopGetCurrent();
CFRunLoopPerformBlock(run_loop, kCFRunLoopCommonModes, ^{
[NSException raise:@"ThrowExceptionInRunLoop" format:@""];
});
CFRunLoopPerformBlock(run_loop, kCFRunLoopCommonModes, ^{
CFRunLoopStop(run_loop);
});
CFRunLoopRun();
}
void ThrowExceptionInRunLoop() {
base::mac::DisableOSCrashDumps();
InstallObjcExceptionPreprocessor();
RaiseExceptionInRunLoop();
fprintf(stderr, "TEST FAILED\n");
exit(1);
}
// Tests that when the preprocessor is installed, exceptions thrown from
// a runloop callout are made fatal, so that the stack trace is useful.
TEST_F(ExceptionProcessorTest, ThrowExceptionInRunLoop) {
GTEST_FLAG_SET(death_test_style, "threadsafe");
EXPECT_DEATH(ThrowExceptionInRunLoop(),
".*FATAL:.*exception_processor\\.mm.*"
"Terminating from Objective-C exception:.*");
}
void ThrowAndCatchExceptionInRunLoop() {
base::mac::DisableOSCrashDumps();
InstallObjcExceptionPreprocessor();
CFRunLoopRef run_loop = CFRunLoopGetCurrent();
CFRunLoopPerformBlock(run_loop, kCFRunLoopCommonModes, ^{
@try {
[NSException raise:@"ObjcExceptionPreprocessCaught" format:@""];
} @catch (id exception) {
}
});
CFRunLoopPerformBlock(run_loop, kCFRunLoopCommonModes, ^{
CFRunLoopStop(run_loop);
});
CFRunLoopRun();
fprintf(stderr, "TEST PASS\n");
exit(0);
}
// Tests that exceptions can still be caught when the preprocessor is enabled.
TEST_F(ExceptionProcessorTest, ThrowAndCatchExceptionInRunLoop) {
GTEST_FLAG_SET(death_test_style, "threadsafe");
EXPECT_EXIT(ThrowAndCatchExceptionInRunLoop(),
[](int exit_code) -> bool {
return WEXITSTATUS(exit_code) == 0;
},
".*TEST PASS.*");
}
void ThrowExceptionFromSelector() {
base::mac::DisableOSCrashDumps();
InstallObjcExceptionPreprocessor();
NSException* exception = [NSException exceptionWithName:@"ThrowFromSelector"
reason:@""
userInfo:nil];
[exception performSelector:@selector(raise) withObject:nil afterDelay:0.1];
[[NSRunLoop currentRunLoop] runUntilDate:
[NSDate dateWithTimeIntervalSinceNow:10]];
fprintf(stderr, "TEST FAILED\n");
exit(1);
}
TEST_F(ExceptionProcessorTest, ThrowExceptionFromSelector) {
GTEST_FLAG_SET(death_test_style, "threadsafe");
EXPECT_DEATH(ThrowExceptionFromSelector(),
".*FATAL:.*exception_processor\\.mm.*"
"Terminating from Objective-C exception:.*");
}
void ThrowInNotificationObserver() {
base::mac::DisableOSCrashDumps();
InstallObjcExceptionPreprocessor();
NSNotification* notification =
[NSNotification notificationWithName:@"TestExceptionInObserver"
object:nil];
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserverForName:[notification name]
object:nil
queue:nil
usingBlock:^(NSNotification*) {
[NSException raise:@"ThrowInNotificationObserver"
format:@""];
}];
[center performSelector:@selector(postNotification:)
withObject:notification
afterDelay:0];
[[NSRunLoop currentRunLoop] runUntilDate:
[NSDate dateWithTimeIntervalSinceNow:10]];
fprintf(stderr, "TEST FAILED\n");
exit(1);
}
TEST_F(ExceptionProcessorTest, ThrowInNotificationObserver) {
GTEST_FLAG_SET(death_test_style, "threadsafe");
EXPECT_DEATH(ThrowInNotificationObserver(),
".*FATAL:.*exception_processor\\.mm.*"
"Terminating from Objective-C exception:.*");
}
void ThrowExceptionInRunLoopWithoutProcessor() {
base::mac::DisableOSCrashDumps();
UninstallObjcExceptionPreprocessor();
@try {
RaiseExceptionInRunLoop();
} @catch (id exception) {
fprintf(stderr, "TEST PASS\n");
exit(0);
}
fprintf(stderr, "TEST FAILED\n");
exit(1);
}
// Under LSAN this dies from leaking the run loop instead of how we expect it to
// die, so the exit code is wrong.
#if defined(LEAK_SANITIZER)
#define MAYBE_ThrowExceptionInRunLoopWithoutProcessor \
DISABLED_ThrowExceptionInRunLoopWithoutProcessor
#else
#define MAYBE_ThrowExceptionInRunLoopWithoutProcessor \
ThrowExceptionInRunLoopWithoutProcessor
#endif
// Tests basic exception handling when the preprocessor is disabled.
TEST_F(ExceptionProcessorTest, MAYBE_ThrowExceptionInRunLoopWithoutProcessor) {
GTEST_FLAG_SET(death_test_style, "threadsafe");
EXPECT_EXIT(ThrowExceptionInRunLoopWithoutProcessor(),
[](int exit_code) -> bool {
return WEXITSTATUS(exit_code) == 0;
},
".*TEST PASS.*");
}
|