// Compile with "cl /MT /EHa exittest.cpp"
// Copyright (c) David Drysdale 2006

#include <windows.h>
#include <process.h>

#include <exception>
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <csignal>
using namespace std; // So sue me
ofstream ferr;

#define TID "["<<hex<<GetCurrentThreadId()<<"] "

// Error cases
typedef void (*errorfn)();
void do_nothing()
{
}
void null_dereference()
{
  ferr<<TID<<"About to dereference null pointer" << endl;
  char c = *(char*)0; 
  ferr<<TID<<"After dereference of null pointer " << c << endl;
}
void do_abort()
{
  ferr<<TID<<"About to abort()" << endl;
  abort();
  ferr<<TID<<"After abort()" << endl;
}
void do_exit()
{
  ferr<<TID<<"About to exit()" << endl;
  exit(0);
  ferr<<TID<<"After exit()" << endl;
} 
void divide_by_zero()
{
  ferr<<TID<<"About to divide by zero" << endl;
  double x = 0.0; x = 1.0/x;
  int y = 0; y = 1/y;
  ferr<<TID<<"Divided by zero, result=" << x << endl;
}
void cpp_exception_raise()
{
  ferr<<TID<<"About to raise C++ exception" << endl;
  throw string("This is a C++ exception object");
  ferr<<TID<<"After C++ exception raised" << endl;
}
void w32_exception_raise()
{
  ferr<<TID<<"About to raise non-continuable W32 exception" << endl;
  RaiseException(0xE01234656, EXCEPTION_NONCONTINUABLE, 0, NULL);
  ferr<<TID<<"After non-continuable W32 exception raised" << endl;
}
void w32_continuable_exception_raise()
{
  ferr<<TID<<"About to raise continuable W32 exception" << endl;
  RaiseException(0xE01234656, 0, 0, NULL);
  ferr<<TID<<"After continuable W32 exception raised" << endl;
}
void sigint_raise()
{
  ferr<<TID<<"About to raise SIGINT signal" << endl;
  raise(SIGINT);
  ferr<<TID<<"After signal raised" << endl;
}
void sigsegv_raise()
{
  ferr<<TID<<"About to raise SIGSEGV signal" << endl;
  raise(SIGSEGV);
  ferr<<TID<<"After signal raised" << endl;
}

// Interception cases
typedef void (*catcher)(errorfn f);
void cpp_exception_catch(errorfn f)
{
  try {
    f();
  } catch (string& s) {
    ferr<<TID<<"Caught C++ string exception: " << s << endl;
  } catch (...) {
    ferr<<TID<<"Caught unknown C++ exception" << endl;
  }
}
void w32_exception_catch(errorfn f)
{
  __try {
    f();
  }
  __except (EXCEPTION_EXECUTE_HANDLER) {
    ferr<<TID<<"Caught W32 exception "<<hex<<GetExceptionCode()<<endl;
  }
}
void both_exception_catch(errorfn f)
{
  try {
    w32_exception_catch(f);
  } catch (string& s) {
    ferr<<TID<<"Caught C++ string exception: " << s << endl;
  } catch (...) {
    ferr<<TID<<"Caught unknown C++ exception" << endl;
  }
}
void htob_exception_catch(errorfn f)
{
  __try {
    cpp_exception_catch(f);
  }
  __except (EXCEPTION_EXECUTE_HANDLER) {
    ferr<<TID<<"Caught W32 exception "<<hex<<GetExceptionCode()<<endl;
  }
}
void w32_exception_continue(errorfn f)
{
  __try {
    f();
  }
  __except (EXCEPTION_CONTINUE_EXECUTION) { }
  ferr<<TID<<"Processing after continue execution clause" << endl;
}
void nothing_catch(errorfn f)
{
  f();
}
void signal_handler(int v)
{
  string s((v==SIGSEGV)?"(SIGSEGV)":
           (v==SIGINT)?"(SIGINT)":
           (v==SIGABRT)?"(SIGABRT)":
           (v==SIGFPE)?"(SIGFPE)": "(unknown)");
  ferr<<TID<<"In signal_handler, signal="<<v<<s<< endl; 
}
void atexit_handler()
{
  ferr<<TID<<"In atexit_handler"<<endl;
}
void unhandled_cpp_exception()
{
  ferr<<TID<<"In unhandled_cpp_exception, calling exit()"<<endl;
  exit(0);
}
LONG WINAPI unhandled_w32_exception(struct _EXCEPTION_POINTERS* exceptionInfo)
{
  LONG rc = EXCEPTION_EXECUTE_HANDLER;
  ferr<<TID<<"In unhandled_w32_exception"<<endl;
  return rc;
}

// Global variables indicating what actions are required
errorfn f = do_nothing;
catcher c = nothing_catch;

unsigned int __stdcall thread_function(void *)
{
  ferr<<TID<<"In second thread" << endl;
  c(f);
  ferr<<TID<<"Ending second thread" << endl;
  return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  ferr.open("exittest.out", ios::trunc); // Open up an output file
  bool run_in_thread=false;
  int ii=0;
  while (lpCmdLine[ii] != '\0') {
    switch (lpCmdLine[ii]) {
      // Determine what error condition to generate
      case 'A': f = do_abort; break; // call abort
      case 'X': f = do_exit; break; // call exit
      case 'F': f = divide_by_zero; break; // divide by zero
      case 'C': f = cpp_exception_raise; break; // throw C++ exception
      case 'W': f = w32_exception_raise; break; // throw W32 exception
      case 'V': f = w32_continuable_exception_raise; break; // throw continuable W32 exception
      case 'I': f = sigint_raise; break; // raise SIGINT signal
      case 'S': f = sigsegv_raise; break; // raise SIGSEGV signal
      case '0': f = null_dereference; break; // null pointer deference
      // Determine what protection to incorporate from the remainder of the command line
      case 'a': signal(SIGABRT, signal_handler); break; // catch SIGABRT signal
      case 'f': signal(SIGFPE, signal_handler); break; // catch SIGFPE signal
      case 's': signal(SIGSEGV, signal_handler); break; // catch SIGSEGV signal
      case 'i': signal(SIGINT, signal_handler); break; // catch SIGINT signal
      case 'e': atexit(atexit_handler); break; // atexit handler
      case 'z': set_terminate(unhandled_cpp_exception); break; // unhandled C++ exception handler
      case 'y': SetUnhandledExceptionFilter(unhandled_w32_exception); break; // unhandled W32 exception handler
      case 'c': c = cpp_exception_catch; break; // C++ exception handler
      case 'w': c = w32_exception_catch; break; // W32 exception handler
      case 'v': c = w32_exception_continue; break; // W32 exception handler that continues
      case 'b': c = both_exception_catch; break; // W32 inside C++ handler
      case 'd': c = htob_exception_catch; break; // C++ inside W32 handler
      // Specials
      case '1': run_in_thread=true; break; // Run in a separate thread
    }
    ii++;
  }

  // Run the test
  if (run_in_thread) {
    ferr<<TID<<"Starting second thread for test" << endl;
    unsigned int threadID;
    HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, thread_function, NULL, 0, &threadID);
    ferr<<TID<<"Waiting for second thread completion" << endl;
    WaitForSingleObject(hThread, INFINITE);
  } else {
    c(f);
  }

  ferr<<TID<<"Normal exit of main thread" << endl;
  return 0;
}  
