Roll leveldb to r76.
[chromium-blink-merge.git] / chrome_frame / test / exception_barrier_unittest.cc
blob3274318cdd7e5b0aabb05118ace372f26479b6d9
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "gtest/gtest.h"
6 #include "chrome_frame/exception_barrier.h"
8 namespace {
10 // retrieves the top SEH registration record
11 __declspec(naked) EXCEPTION_REGISTRATION* GetTopRegistration() {
12 __asm {
13 mov eax, FS:0
14 ret
18 // This function walks the SEH chain and attempts to ascertain whether it's
19 // sane, or rather tests it for any obvious signs of insanity.
20 // The signs it's capable of looking for are:
21 // # Is each exception registration in bounds of our stack
22 // # Is the registration DWORD aligned
23 // # Does each exception handler point to a module, as opposed to
24 // e.g. into the stack or never-never land.
25 // # Do successive entries in the exception chain increase
26 // monotonically in address
27 void TestSEHChainSane() {
28 // get the skinny on our stack segment
29 MEMORY_BASIC_INFORMATION info = { 0 };
30 // Note that we pass the address of the info struct just as a handy
31 // moniker to anything at all inside our stack allocation
32 ASSERT_NE(0u, ::VirtualQuery(&info, &info, sizeof(info)));
34 // The lower bound of our stack.
35 // We use the address of info as a lower bound, this assumes that if this
36 // function has an SEH handler, it'll be higher up in our invocation
37 // record.
38 EXCEPTION_REGISTRATION* limit =
39 reinterpret_cast<EXCEPTION_REGISTRATION*>(&info);
40 // the very top of our stack segment
41 EXCEPTION_REGISTRATION* top =
42 reinterpret_cast<EXCEPTION_REGISTRATION*>(
43 reinterpret_cast<char*>(info.BaseAddress) + info.RegionSize);
45 EXCEPTION_REGISTRATION* curr = GetTopRegistration();
46 // there MUST be at least one registration
47 ASSERT_TRUE(NULL != curr);
49 EXCEPTION_REGISTRATION* prev = NULL;
50 const EXCEPTION_REGISTRATION* kSentinel =
51 reinterpret_cast<EXCEPTION_REGISTRATION*>(0xFFFFFFFF);
52 for (; kSentinel != curr; prev = curr, curr = curr->prev) {
53 // registrations must increase monotonically
54 ASSERT_TRUE(curr > prev);
55 // Check it's in bounds
56 ASSERT_GE(top, curr);
57 ASSERT_LT(limit, curr);
59 // check for DWORD alignment
60 ASSERT_EQ(0, (reinterpret_cast<UINT_PTR>(prev) & 0x00000003));
62 // find the module hosting the handler
63 ASSERT_NE(0u, ::VirtualQuery(curr->handler, &info, sizeof(info)));
64 wchar_t module_filename[MAX_PATH];
65 ASSERT_NE(0u, ::GetModuleFileName(
66 reinterpret_cast<HMODULE>(info.AllocationBase),
67 module_filename, ARRAYSIZE(module_filename)));
71 void AccessViolationCrash() {
72 volatile char* null = NULL;
73 *null = '\0';
76 // A simple crash over the exception barrier
77 void CrashOverExceptionBarrier() {
78 ExceptionBarrierCustomHandler barrier;
80 TestSEHChainSane();
82 AccessViolationCrash();
84 TestSEHChainSane();
87 #pragma warning(push)
88 // Inline asm assigning to 'FS:0' : handler not registered as safe handler
89 // This warning is in error (the compiler can't know that we register the
90 // handler as a safe SEH handler in an .asm file)
91 #pragma warning(disable:4733)
92 // Hand-generate an SEH frame implicating the ExceptionBarrierCallCustomHandler,
93 // then crash to invoke it.
94 __declspec(naked) void CrashOnManualSEHBarrierHandler() {
95 __asm {
96 push ExceptionBarrierCallCustomHandler
97 push FS:0
98 mov FS:0, esp
99 call AccessViolationCrash
103 #pragma warning(pop)
106 class ExceptionBarrierTest: public testing::Test {
107 public:
108 ExceptionBarrierTest() {
111 // Install an exception handler for the ExceptionBarrier, and
112 // set the handled flag to false. This allows us to see whether
113 // the ExceptionBarrier gets to handle the exception
114 virtual void SetUp() {
115 ExceptionBarrierConfig::set_enabled(true);
116 ExceptionBarrierCustomHandler::set_custom_handler(&ExceptionHandler);
117 s_handled_ = false;
119 TestSEHChainSane();
122 virtual void TearDown() {
123 TestSEHChainSane();
124 ExceptionBarrierCustomHandler::set_custom_handler(NULL);
125 ExceptionBarrierConfig::set_enabled(false);
128 // The exception notification callback, sets the handled flag.
129 static void CALLBACK ExceptionHandler(EXCEPTION_POINTERS* ptrs) {
130 TestSEHChainSane();
131 s_handled_ = true;
134 // Flag is set by handler
135 static bool s_handled_;
138 bool ExceptionBarrierTest::s_handled_ = false;
140 bool TestExceptionExceptionBarrierHandler() {
141 TestSEHChainSane();
142 __try {
143 CrashOnManualSEHBarrierHandler();
144 return false; // not reached
145 } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ?
146 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
147 TestSEHChainSane();
148 return true;
151 return false; // not reached
154 typedef EXCEPTION_DISPOSITION
155 (__cdecl* ExceptionBarrierHandlerFunc)(
156 struct _EXCEPTION_RECORD* exception_record,
157 void* establisher_frame,
158 struct _CONTEXT* context,
159 void* reserved);
161 TEST_F(ExceptionBarrierTest, RegisterUnregister) {
162 // Assert that registration modifies the chain
163 // and the registered record as expected
164 EXCEPTION_REGISTRATION* top = GetTopRegistration();
165 ExceptionBarrierHandlerFunc handler = top->handler;
166 EXCEPTION_REGISTRATION* prev = top->prev;
168 EXCEPTION_REGISTRATION registration;
169 ::RegisterExceptionRecord(&registration, ExceptionBarrierHandler);
170 EXPECT_EQ(GetTopRegistration(), &registration);
171 EXPECT_EQ(&ExceptionBarrierHandler, registration.handler);
172 EXPECT_EQ(top, registration.prev);
174 // test the whole chain for good measure
175 TestSEHChainSane();
177 // Assert that unregistration restores
178 // everything as expected
179 ::UnregisterExceptionRecord(&registration);
180 EXPECT_EQ(top, GetTopRegistration());
181 EXPECT_EQ(handler, top->handler);
182 EXPECT_EQ(prev, top->prev);
184 // and again test the whole chain for good measure
185 TestSEHChainSane();
189 TEST_F(ExceptionBarrierTest, ExceptionBarrierHandler) {
190 EXPECT_TRUE(TestExceptionExceptionBarrierHandler());
191 EXPECT_TRUE(s_handled_);
194 bool TestExceptionBarrier() {
195 __try {
196 CrashOverExceptionBarrier();
197 } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ?
198 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
199 TestSEHChainSane();
200 return true;
203 return false;
206 TEST_F(ExceptionBarrierTest, HandlesAccessViolationException) {
207 TestExceptionBarrier();
208 EXPECT_TRUE(s_handled_);
211 void RecurseAndCrash(int depth) {
212 __try {
213 __try {
214 if (0 == depth)
215 AccessViolationCrash();
216 else
217 RecurseAndCrash(depth - 1);
219 TestSEHChainSane();
220 } __except(EXCEPTION_CONTINUE_SEARCH) {
221 TestSEHChainSane();
223 } __finally {
224 TestSEHChainSane();
228 // This test exists only for comparison with TestExceptionBarrierChaining, and
229 // to "document" how the SEH chain is manipulated under our compiler.
230 // The two tests are expected to both fail if the particulars of the compiler's
231 // SEH implementation happens to change.
232 bool TestRegularChaining(EXCEPTION_REGISTRATION* top) {
233 // This test relies on compiler-dependent details, notably we rely on the
234 // compiler to generate a single SEH frame for the entire function, as
235 // opposed to e.g. generating a separate SEH frame for each __try __except
236 // statement.
237 EXCEPTION_REGISTRATION* my_top = GetTopRegistration();
238 if (my_top == top)
239 return false;
241 __try {
242 // we should have the new entry in effect here still
243 if (GetTopRegistration() != my_top)
244 return false;
245 } __except(EXCEPTION_EXECUTE_HANDLER) {
246 return false;
249 __try {
250 AccessViolationCrash();
251 return false; // not reached
252 } __except(EXCEPTION_EXECUTE_HANDLER) {
253 // and here
254 if (GetTopRegistration() != my_top)
255 return false;
258 __try {
259 RecurseAndCrash(10);
260 return false; // not reached
261 } __except(EXCEPTION_EXECUTE_HANDLER) {
262 // we should have unrolled to our frame by now
263 if (GetTopRegistration() != my_top)
264 return false;
267 return true;
270 void RecurseAndCrashOverBarrier(int depth, bool crash) {
271 ExceptionBarrierCustomHandler barrier;
273 if (0 == depth) {
274 if (crash)
275 AccessViolationCrash();
276 } else {
277 RecurseAndCrashOverBarrier(depth - 1, crash);
281 // Test that ExceptionBarrier doesn't molest the SEH chain, neither
282 // for regular unwinding, nor on exception unwinding cases.
284 // Note that while this test shows the ExceptionBarrier leaves the chain
285 // sane on both those cases, it's not clear that it does the right thing
286 // during first-chance exception handling. I can't think of a way to test
287 // that though, because first-chance exception handling is very difficult
288 // to hook into and to observe.
289 static bool TestExceptionBarrierChaining(EXCEPTION_REGISTRATION* top) {
290 TestSEHChainSane();
292 // This test relies on compiler-dependent details, notably we rely on the
293 // compiler to generate a single SEH frame for the entire function, as
294 // opposed to e.g. generating a separate SEH frame for each __try __except
295 // statement.
296 // Unfortunately we can't use ASSERT macros here, because they create
297 // temporary objects and the compiler doesn't grok non POD objects
298 // intermingled with __try and other SEH constructs.
299 EXCEPTION_REGISTRATION* my_top = GetTopRegistration();
300 if (my_top == top)
301 return false;
303 __try {
304 // we should have the new entry in effect here still
305 if (GetTopRegistration() != my_top)
306 return false;
307 } __except(EXCEPTION_EXECUTE_HANDLER) {
308 return false; // Not reached
311 __try {
312 CrashOverExceptionBarrier();
313 return false; // Not reached
314 } __except(EXCEPTION_EXECUTE_HANDLER) {
315 // and here
316 if (GetTopRegistration() != my_top)
317 return false;
319 TestSEHChainSane();
321 __try {
322 RecurseAndCrashOverBarrier(10, true);
323 return false; // not reached
324 } __except(EXCEPTION_EXECUTE_HANDLER) {
325 // we should have unrolled to our frame by now
326 if (GetTopRegistration() != my_top)
327 return false;
329 TestSEHChainSane();
331 __try {
332 RecurseAndCrashOverBarrier(10, false);
334 // we should have unrolled to our frame by now
335 if (GetTopRegistration() != my_top)
336 return false;
337 } __except(EXCEPTION_EXECUTE_HANDLER) {
338 return false; // not reached
340 TestSEHChainSane();
342 // success.
343 return true;
346 static bool TestChaining() {
347 EXCEPTION_REGISTRATION* top = GetTopRegistration();
349 return TestRegularChaining(top) && TestExceptionBarrierChaining(top);
352 // Test that the SEH chain is unmolested by exception barrier, both under
353 // regular unroll, and under exception unroll.
354 TEST_F(ExceptionBarrierTest, SEHChainIsSaneAfterException) {
355 EXPECT_TRUE(TestChaining());
358 } // namespace