// RUN: %clang_cl_asan -O0 %p/dll_host.cc -Fe%t
// RUN: %clang_cl_asan -LD -O2 %s -Fe%t.dll
// RUNX: %run %t %t.dll 2>&1 | FileCheck %s

// Check that ASan does not CHECK fail when SEH is used around a crash from a
// thread injected by control C.

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

static void __declspec(noinline) CrashOnProcessDetach() {
  printf("CrashOnProcessDetach\n");
  fflush(stdout);
  *static_cast<volatile int *>(0) = 0x356;
}

bool g_is_child = false;

BOOL WINAPI DllMain(PVOID h, DWORD reason, PVOID reserved) {
  if (reason == DLL_PROCESS_DETACH && g_is_child) {
    printf("in DllMain DLL_PROCESS_DETACH\n");
    fflush(stdout);
    __try {
      CrashOnProcessDetach();
    } __except (1) {
      printf("caught crash\n");
      fflush(stdout);
    }
  }
  return true;
}

static void run_child() {
  // Send this process group Ctrl+C. That should only be this process.
  printf("GenerateConsoleCtrlEvent\n");
  fflush(stdout);
  GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
  Sleep(10 * 1000); // Wait 10 seconds, and the process should die.
  printf("unexpected execution after interrupt\n");
  fflush(stdout);
  exit(0x42);
}

static int WINAPI ignore_control_c(DWORD ctrl_type) {
  // Don't interrupt the parent.
  return ctrl_type == CTRL_C_EVENT;
}

static int run_parent() {
  // Set an environment variable to tell the child process to interrupt itself.
  if (!SetEnvironmentVariableW(L"DO_CONTROL_C", L"1")) {
    printf("SetEnvironmentVariableW failed (0x%8lx).\n", GetLastError());
    fflush(stdout);
    return 2;
  }

  // Launch a new process using the current executable with a new console.
  // Ctrl-C events are console-wide, so we need a new console.
  STARTUPINFOW si;
  memset(&si, 0, sizeof(si));
  si.cb = sizeof(si);
  // Hides the new console window that we are creating.
  si.dwFlags |= STARTF_USESHOWWINDOW;
  si.wShowWindow = SW_HIDE;
  // Ensures that stdout still goes to the parent despite the new console.
  si.dwFlags |= STARTF_USESTDHANDLES;
  si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
  si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  si.hStdError = GetStdHandle(STD_ERROR_HANDLE);

  PROCESS_INFORMATION pi;
  memset(&pi, 0, sizeof(pi));
  int flags = CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE;
  if (!CreateProcessW(nullptr,           // No module name (use command line)
                      GetCommandLineW(), // Command line
                      nullptr,           // Process handle not inheritable
                      nullptr,           // Thread handle not inheritable
                      TRUE,              // Set handle inheritance to TRUE
                      flags,             // Flags to give the child a console
                      nullptr,           // Use parent's environment block
                      nullptr,           // Use parent's starting directory
                      &si, &pi)) {
    printf("CreateProcess failed (0x%08lx).\n", GetLastError());
    fflush(stdout);
    return 2;
  }

  // Wait until child process exits.
  if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) {
    printf("WaitForSingleObject failed (0x%08lx).\n", GetLastError());
    fflush(stdout);
    return 2;
  }

  // Get the exit code. It should be the one for ctrl-c events.
  DWORD rc;
  if (!GetExitCodeProcess(pi.hProcess, &rc)) {
    printf("GetExitCodeProcess failed (0x%08lx).\n", GetLastError());
    fflush(stdout);
    return 2;
  }
  if (rc == STATUS_CONTROL_C_EXIT)
    printf("child quit with STATUS_CONTROL_C_EXIT\n");
  else
    printf("unexpected exit code: 0x%08lx\n", rc);
  fflush(stdout);

  // Close process and thread handles.
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
  return 0;
}

// CHECK: in DllMain DLL_PROCESS_DETACH
// CHECK: CrashOnProcessDetach
// CHECK: caught crash
// CHECK: child quit with STATUS_CONTROL_C_EXIT

extern "C" int __declspec(dllexport) test_function() {
  wchar_t buf[260];
  int len = GetEnvironmentVariableW(L"DO_CONTROL_C", buf, 260);
  if (len > 0) {
    g_is_child = true;
    run_child();
  } else {
    exit(run_parent());
  }
  return 0;
}
