Win32: offer to restart application on fatal errors.

This changes the assertion failure behavior to be the same in debug
and release builds: to show the complete failure message, and
to offer to restart the application or defer to Windows Error
Reporting to generate a backtrace. Contrary to popular belief,
WER is not useless, and since SolveSpace publishes pdb files,
WER-generated reports can be symbolized.

This commit also addresses the long-standing problem where showing
a dialog on fatal error would re-enter the application code, thus
causing another error or a crash that is more fatal than the current
one.
pull/307/merge
whitequark 2018-07-14 10:35:29 +00:00
parent 8192c965ea
commit 975b49f520
6 changed files with 64 additions and 25 deletions

View File

@ -9,6 +9,13 @@
namespace Platform {
// Handling fatal errors.
#if defined(__GNUC__)
__attribute__((noreturn))
#endif
void FatalError(std::string message);
extern bool handlingFatalError;
// UTF-8 ⟷ UTF-16 conversion, for Windows.
#if defined(WIN32)
std::string Narrow(const wchar_t *s);

View File

@ -27,10 +27,10 @@ void dbp(const char *str, ...)
fputc('\n', stderr);
}
void assert_failure(const char *file, unsigned line, const char *function,
const char *condition, const char *message) {
fprintf(stderr, "File %s, line %u, function %s:\n", file, line, function);
fprintf(stderr, "Assertion '%s' failed: ((%s) == false).\n", message, condition);
namespace Platform {
void FatalError(std::string message) {
fprintf(stderr, "%s", message.c_str());
#if !defined(LIBRARY) && defined(HAVE_BACKTRACE)
static void *ptrs[1024] = {};
@ -54,6 +54,8 @@ void assert_failure(const char *file, unsigned line, const char *function,
abort();
}
}
//-----------------------------------------------------------------------------
// A separate heap, on which we allocate expressions. Maybe a bit faster,
// since fragmentation is less of a concern, and it also makes it possible

View File

@ -559,6 +559,8 @@ static void MouseWheel(int thisDelta) {
LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if(Platform::handlingFatalError) return 1;
switch (msg) {
case WM_ERASEBKGND:
break;
@ -955,6 +957,8 @@ bool SolveSpace::GraphicsEditControlIsVisible()
LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam)
{
if(Platform::handlingFatalError) return 1;
switch (msg) {
case WM_ERASEBKGND:
break;

View File

@ -35,15 +35,33 @@ void dbp(const char *str, ...)
#endif
}
void assert_failure(const char *file, unsigned line, const char *function,
const char *condition, const char *message) {
dbp("File %s, line %u, function %s:", file, line, function);
dbp("Assertion '%s' failed: ((%s) == false).", message, condition);
#ifdef NDEBUG
_exit(1);
#else
abort();
#endif
namespace Platform {
bool handlingFatalError = false;
void FatalError(std::string message) {
// Indicate that we're handling a fatal error, to avoid re-entering application code
// and potentially crashing even harder.
handlingFatalError = true;
message += "\nGenerate debug report?";
switch(MessageBoxW(NULL, Platform::Widen(message).c_str(),
L"Fatal error — SolveSpace",
MB_ICONERROR|MB_TASKMODAL|MB_SETFOREGROUND|MB_TOPMOST|
MB_OKCANCEL|MB_DEFBUTTON2)) {
case IDOK:
abort();
case IDCANCEL:
default: {
WCHAR appPath[MAX_PATH] = {};
GetModuleFileNameW(NULL, appPath, sizeof(appPath));
ShellExecuteW(NULL, L"open", appPath, NULL, NULL, SW_SHOW);
_exit(1);
}
}
}
}
//-----------------------------------------------------------------------------
@ -85,15 +103,9 @@ void vl() {
}
std::vector<std::string> InitPlatform(int argc, char **argv) {
// Create the heap used for long-lived stuff (that gets freed piecewise).
PermHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0);
// Create the heap that we use to store Exprs and other temp stuff.
FreeAllTemporary();
#if !defined(LIBRARY) && defined(_MSC_VER)
// Don't display the abort message; it is aggravating in CLI binaries
// and results in infinite WndProc recursion in GUI binaries.
_set_abort_behavior(0, _WRITE_ABORT_MSG);
// We display our own message on abort; just call ReportFault.
_set_abort_behavior(_CALL_REPORTFAULT, _WRITE_ABORT_MSG|_CALL_REPORTFAULT);
int crtReportTypes[] = {_CRT_WARN, _CRT_ERROR, _CRT_ASSERT};
for(int crtReportType : crtReportTypes) {
_CrtSetReportMode(crtReportType, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
@ -101,6 +113,11 @@ std::vector<std::string> InitPlatform(int argc, char **argv) {
}
#endif
// Create the heap used for long-lived stuff (that gets freed piecewise).
PermHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0);
// Create the heap that we use to store Exprs and other temp stuff.
FreeAllTemporary();
// Extract the command-line arguments; the ones from main() are ignored,
// since they are in the OEM encoding.
int argcW;

View File

@ -56,7 +56,7 @@ typedef struct _cairo cairo_t;
#define ssassert(condition, message) \
do { \
if(__builtin_expect((condition), true) == false) { \
SolveSpace::assert_failure(__FILE__, __LINE__, __func__, #condition, message); \
SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \
__builtin_unreachable(); \
} \
} while(0)
@ -64,7 +64,7 @@ typedef struct _cairo cairo_t;
#define ssassert(condition, message) \
do { \
if((condition) == false) { \
SolveSpace::assert_failure(__FILE__, __LINE__, __func__, #condition, message); \
SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \
abort(); \
} \
} while(0)
@ -83,8 +83,8 @@ using std::swap;
#if defined(__GNUC__)
__attribute__((noreturn))
#endif
void assert_failure(const char *file, unsigned line, const char *function,
const char *condition, const char *message);
void AssertFailure(const char *file, unsigned line, const char *function,
const char *condition, const char *message);
#if defined(__GNUC__)
__attribute__((__format__ (__printf__, 1, 2)))

View File

@ -6,6 +6,15 @@
//-----------------------------------------------------------------------------
#include "solvespace.h"
void SolveSpace::AssertFailure(const char *file, unsigned line, const char *function,
const char *condition, const char *message) {
std::string formattedMsg;
formattedMsg += ssprintf("File %s, line %u, function %s:\n", file, line, function);
formattedMsg += ssprintf("Assertion failed: %s.\n", condition);
formattedMsg += ssprintf("Message: %s.\n", message);
SolveSpace::Platform::FatalError(formattedMsg);
}
std::string SolveSpace::ssprintf(const char *fmt, ...)
{
va_list va;