Some cleanup regarding Siggi's comments on http://codereview.chromium.org/1733021/show

BUG=42660
TEST=none


Review URL: http://codereview.chromium.org/1703015

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@45859 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome_frame/test/exception_barrier_unittest.cc b/chrome_frame/test/exception_barrier_unittest.cc
new file mode 100644
index 0000000..515f4a1
--- /dev/null
+++ b/chrome_frame/test/exception_barrier_unittest.cc
@@ -0,0 +1,361 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gtest/gtest.h"
+#include "chrome_frame/exception_barrier.h"
+
+namespace {
+
+// retrieves the top SEH registration record
+__declspec(naked) EXCEPTION_REGISTRATION* GetTopRegistration() {
+  __asm {
+    mov eax, FS:0
+    ret
+  }
+}
+
+// This function walks the SEH chain and attempts to ascertain whether it's
+// sane, or rather tests it for any obvious signs of insanity.
+// The signs it's capable of looking for are:
+//   # Is each exception registration in bounds of our stack
+//   # Is the registration DWORD aligned
+//   # Does each exception handler point to a module, as opposed to
+//      e.g. into the stack or never-never land.
+//   # Do successive entries in the exception chain increase
+//      monotonically in address
+void TestSEHChainSane() {
+  // get the skinny on our stack segment
+  MEMORY_BASIC_INFORMATION info = { 0 };
+  // Note that we pass the address of the info struct just as a handy
+  // moniker to anything at all inside our stack allocation
+  ASSERT_NE(0, ::VirtualQuery(&info, &info, sizeof(info)));
+
+  // The lower bound of our stack.
+  // We use the address of info as a lower bound, this assumes that if this
+  // function has an SEH handler, it'll be higher up in our invocation
+  // record.
+  EXCEPTION_REGISTRATION* limit =
+          reinterpret_cast<EXCEPTION_REGISTRATION*>(&info);
+  // the very top of our stack segment
+  EXCEPTION_REGISTRATION* top =
+          reinterpret_cast<EXCEPTION_REGISTRATION*>(
+              reinterpret_cast<char*>(info.BaseAddress) + info.RegionSize);
+
+  EXCEPTION_REGISTRATION* curr = GetTopRegistration();
+  // there MUST be at least one registration
+  ASSERT_TRUE(NULL != curr);
+
+  EXCEPTION_REGISTRATION* prev = NULL;
+  const EXCEPTION_REGISTRATION* kSentinel =
+          reinterpret_cast<EXCEPTION_REGISTRATION*>(0xFFFFFFFF);
+  for (; kSentinel != curr; prev = curr, curr = curr->prev) {
+    // registrations must increase monotonically
+    ASSERT_TRUE(curr > prev);
+    // Check it's in bounds
+    ASSERT_GE(top, curr);
+    ASSERT_LT(limit, curr);
+
+    // check for DWORD alignment
+    ASSERT_EQ(0, (reinterpret_cast<UINT_PTR>(prev) & 0x00000003));
+
+    // find the module hosting the handler
+    ASSERT_NE(0, ::VirtualQuery(curr->handler, &info, sizeof(info)));
+    wchar_t module_filename[MAX_PATH];
+    ASSERT_NE(0, ::GetModuleFileName(
+                    reinterpret_cast<HMODULE>(info.AllocationBase),
+                    module_filename, ARRAYSIZE(module_filename)));
+  }
+}
+
+void AccessViolationCrash() {
+  volatile char* null = NULL;
+  *null = '\0';
+}
+
+// A simple crash over the exception barrier
+void CrashOverExceptionBarrier() {
+  ExceptionBarrier barrier;
+
+  TestSEHChainSane();
+
+  AccessViolationCrash();
+
+  TestSEHChainSane();
+}
+
+#pragma warning(push)
+ // Inline asm assigning to 'FS:0' : handler not registered as safe handler
+ // This warning is in error (the compiler can't know that we register the
+ // handler as a safe SEH handler in an .asm file)
+ #pragma warning(disable:4733)
+// Hand-generate an SEH frame implicating the ExceptionBarrierHandler,
+// then crash to invoke it.
+__declspec(naked) void CrashOnManualSEHBarrierHandler() {
+  __asm {
+    push  ExceptionBarrierHandler
+    push  FS:0
+    mov   FS:0, esp
+    call  AccessViolationCrash
+    ret
+  }
+}
+#pragma warning(pop)
+
+class ExceptionBarrierTest: public testing::Test {
+public:
+  ExceptionBarrierTest() : old_handler_(NULL) {
+  }
+
+  // Install an exception handler for the ExceptionBarrier, and
+  // set the handled flag to false. This allows us to see whether
+  // the ExceptionBarrier gets to handle the exception
+  virtual void SetUp() {
+    old_handler_ = ExceptionBarrier::handler();
+    ExceptionBarrier::set_handler(ExceptionHandler);
+    s_handled_ = false;
+
+    TestSEHChainSane();
+  }
+
+  virtual void TearDown() {
+    ExceptionBarrier::set_handler(old_handler_);
+
+    TestSEHChainSane();
+  }
+
+  // The exception notification callback, sets the handled flag.
+  static void CALLBACK ExceptionHandler(EXCEPTION_POINTERS* ptrs) {
+    TestSEHChainSane();
+
+    s_handled_ = true;
+  }
+
+  // Old handler to restore on TearDown
+  ExceptionBarrier::ExceptionHandler old_handler_;
+
+  // Flag is set by handler
+  static bool s_handled_;
+};
+
+bool ExceptionBarrierTest::s_handled_;
+
+bool TestExceptionExceptionBarrierHandler() {
+  TestSEHChainSane();
+  __try {
+    CrashOnManualSEHBarrierHandler();
+    return false;  // not reached
+  } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ?
+                      EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
+    TestSEHChainSane();
+    return true;
+  }
+
+  return false;  // not reached
+}
+
+typedef EXCEPTION_DISPOSITION
+(__cdecl* ExceptionBarrierHandlerFunc)(
+    struct _EXCEPTION_RECORD* exception_record,
+    void* establisher_frame,
+    struct _CONTEXT* context,
+    void* reserved);
+
+TEST_F(ExceptionBarrierTest, RegisterUnregister) {
+  // Assert that registration modifies the chain
+  // and the registered record as expected
+  EXCEPTION_REGISTRATION* top = GetTopRegistration();
+  ExceptionBarrierHandlerFunc handler = top->handler;
+  EXCEPTION_REGISTRATION* prev = top->prev;
+
+  EXCEPTION_REGISTRATION registration;
+  ::RegisterExceptionRecord(&registration, ExceptionBarrierHandler);
+  EXPECT_EQ(GetTopRegistration(), &registration);
+  EXPECT_EQ(ExceptionBarrierHandler, registration.handler);
+  EXPECT_EQ(top, registration.prev);
+
+  // test the whole chain for good measure
+  TestSEHChainSane();
+
+  // Assert that unregistration restores
+  // everything as expected
+  ::UnregisterExceptionRecord(&registration);
+  EXPECT_EQ(top, GetTopRegistration());
+  EXPECT_EQ(handler, top->handler);
+  EXPECT_EQ(prev, top->prev);
+
+  // and again test the whole chain for good measure
+  TestSEHChainSane();
+}
+
+
+TEST_F(ExceptionBarrierTest, ExceptionBarrierHandler) {
+  EXPECT_TRUE(TestExceptionExceptionBarrierHandler());
+  EXPECT_TRUE(s_handled_);
+}
+
+bool TestExceptionBarrier() {
+  __try {
+    CrashOverExceptionBarrier();
+  } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ?
+                      EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
+    TestSEHChainSane();
+    return true;
+  }
+
+  return false;
+}
+
+TEST_F(ExceptionBarrierTest, HandlesAccessViolationException) {
+  TestExceptionBarrier();
+  EXPECT_TRUE(s_handled_);
+}
+
+void RecurseAndCrash(int depth) {
+  __try {
+    __try {
+      if (0 == depth)
+        AccessViolationCrash();
+      else
+        RecurseAndCrash(depth - 1);
+
+      TestSEHChainSane();
+    } __except(EXCEPTION_CONTINUE_SEARCH) {
+      TestSEHChainSane();
+    }
+  } __finally {
+    TestSEHChainSane();
+  }
+}
+
+// This test exists only for comparison with TestExceptionBarrierChaining, and
+// to "document" how the SEH chain is manipulated under our compiler.
+// The two tests are expected to both fail if the particulars of the compiler's
+// SEH implementation happens to change.
+bool TestRegularChaining(EXCEPTION_REGISTRATION* top) {
+  // This test relies on compiler-dependent details, notably we rely on the
+  // compiler to generate a single SEH frame for the entire function, as
+  // opposed to e.g. generating a separate SEH frame for each __try __except
+  // statement.
+  EXCEPTION_REGISTRATION* my_top = GetTopRegistration();
+  if (my_top == top)
+    return false;
+
+  __try {
+    // we should have the new entry in effect here still
+    if (GetTopRegistration() != my_top)
+      return false;
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    return false;
+  }
+
+  __try {
+    AccessViolationCrash();
+    return false;  // not reached
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    // and here
+    if (GetTopRegistration() != my_top)
+      return false;
+  }
+
+  __try {
+    RecurseAndCrash(10);
+    return false;  // not reached
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    // we should have unrolled to our frame by now
+    if (GetTopRegistration() != my_top)
+      return false;
+  }
+
+  return true;
+}
+
+void RecurseAndCrashOverBarrier(int depth, bool crash) {
+  ExceptionBarrier barrier;
+
+  if (0 == depth) {
+    if (crash)
+      AccessViolationCrash();
+  } else {
+    RecurseAndCrashOverBarrier(depth - 1, crash);
+  }
+}
+
+// Test that ExceptionBarrier doesn't molest the SEH chain, neither
+// for regular unwinding, nor on exception unwinding cases.
+//
+// Note that while this test shows the ExceptionBarrier leaves the chain
+// sane on both those cases, it's not clear that it does the right thing
+// during first-chance exception handling. I can't think of a way to test
+// that though, because first-chance exception handling is very difficult
+// to hook into and to observe.
+static bool TestExceptionBarrierChaining(EXCEPTION_REGISTRATION* top) {
+  TestSEHChainSane();
+
+  // This test relies on compiler-dependent details, notably we rely on the
+  // compiler to generate a single SEH frame for the entire function, as
+  // opposed to e.g. generating a separate SEH frame for each __try __except
+  // statement.
+  // Unfortunately we can't use ASSERT macros here, because they create
+  // temporary objects and the compiler doesn't grok non POD objects
+  // intermingled with __try and other SEH constructs.
+  EXCEPTION_REGISTRATION* my_top = GetTopRegistration();
+  if (my_top == top)
+    return false;
+
+  __try {
+    // we should have the new entry in effect here still
+    if (GetTopRegistration() != my_top)
+      return false;
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    return false;  // Not reached
+  }
+
+  __try {
+    CrashOverExceptionBarrier();
+    return false;  // Not reached
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    // and here
+    if (GetTopRegistration() != my_top)
+      return false;
+  }
+  TestSEHChainSane();
+
+  __try {
+    RecurseAndCrashOverBarrier(10, true);
+    return false;  // not reached
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    // we should have unrolled to our frame by now
+    if (GetTopRegistration() != my_top)
+      return false;
+  }
+  TestSEHChainSane();
+
+  __try {
+    RecurseAndCrashOverBarrier(10, false);
+
+    // we should have unrolled to our frame by now
+    if (GetTopRegistration() != my_top)
+      return false;
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    return false;  // not reached
+  }
+  TestSEHChainSane();
+
+  // success.
+  return true;
+}
+
+static bool TestChaining() {
+  EXCEPTION_REGISTRATION* top = GetTopRegistration();
+
+  return TestRegularChaining(top) && TestExceptionBarrierChaining(top);
+}
+
+// Test that the SEH chain is unmolested by exception barrier, both under
+// regular unroll, and under exception unroll.
+TEST_F(ExceptionBarrierTest, SEHChainIsSaneAfterException) {
+  EXPECT_TRUE(TestChaining());
+}
+
+}  // namespace
\ No newline at end of file