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.
This commit is contained in:
parent
8192c965ea
commit
975b49f520
@ -9,6 +9,13 @@
|
|||||||
|
|
||||||
namespace Platform {
|
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.
|
// UTF-8 ⟷ UTF-16 conversion, for Windows.
|
||||||
#if defined(WIN32)
|
#if defined(WIN32)
|
||||||
std::string Narrow(const wchar_t *s);
|
std::string Narrow(const wchar_t *s);
|
||||||
|
@ -27,10 +27,10 @@ void dbp(const char *str, ...)
|
|||||||
fputc('\n', stderr);
|
fputc('\n', stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void assert_failure(const char *file, unsigned line, const char *function,
|
namespace Platform {
|
||||||
const char *condition, const char *message) {
|
|
||||||
fprintf(stderr, "File %s, line %u, function %s:\n", file, line, function);
|
void FatalError(std::string message) {
|
||||||
fprintf(stderr, "Assertion '%s' failed: ((%s) == false).\n", message, condition);
|
fprintf(stderr, "%s", message.c_str());
|
||||||
|
|
||||||
#if !defined(LIBRARY) && defined(HAVE_BACKTRACE)
|
#if !defined(LIBRARY) && defined(HAVE_BACKTRACE)
|
||||||
static void *ptrs[1024] = {};
|
static void *ptrs[1024] = {};
|
||||||
@ -54,6 +54,8 @@ void assert_failure(const char *file, unsigned line, const char *function,
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// A separate heap, on which we allocate expressions. Maybe a bit faster,
|
// 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
|
// since fragmentation is less of a concern, and it also makes it possible
|
||||||
|
@ -559,6 +559,8 @@ static void MouseWheel(int thisDelta) {
|
|||||||
|
|
||||||
LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
|
if(Platform::handlingFatalError) return 1;
|
||||||
|
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case WM_ERASEBKGND:
|
case WM_ERASEBKGND:
|
||||||
break;
|
break;
|
||||||
@ -955,6 +957,8 @@ bool SolveSpace::GraphicsEditControlIsVisible()
|
|||||||
LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||||
LPARAM lParam)
|
LPARAM lParam)
|
||||||
{
|
{
|
||||||
|
if(Platform::handlingFatalError) return 1;
|
||||||
|
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case WM_ERASEBKGND:
|
case WM_ERASEBKGND:
|
||||||
break;
|
break;
|
||||||
|
@ -35,15 +35,33 @@ void dbp(const char *str, ...)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void assert_failure(const char *file, unsigned line, const char *function,
|
namespace Platform {
|
||||||
const char *condition, const char *message) {
|
|
||||||
dbp("File %s, line %u, function %s:", file, line, function);
|
bool handlingFatalError = false;
|
||||||
dbp("Assertion '%s' failed: ((%s) == false).", message, condition);
|
|
||||||
#ifdef NDEBUG
|
void FatalError(std::string message) {
|
||||||
_exit(1);
|
// Indicate that we're handling a fatal error, to avoid re-entering application code
|
||||||
#else
|
// and potentially crashing even harder.
|
||||||
abort();
|
handlingFatalError = true;
|
||||||
#endif
|
|
||||||
|
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) {
|
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)
|
#if !defined(LIBRARY) && defined(_MSC_VER)
|
||||||
// Don't display the abort message; it is aggravating in CLI binaries
|
// We display our own message on abort; just call ReportFault.
|
||||||
// and results in infinite WndProc recursion in GUI binaries.
|
_set_abort_behavior(_CALL_REPORTFAULT, _WRITE_ABORT_MSG|_CALL_REPORTFAULT);
|
||||||
_set_abort_behavior(0, _WRITE_ABORT_MSG);
|
|
||||||
int crtReportTypes[] = {_CRT_WARN, _CRT_ERROR, _CRT_ASSERT};
|
int crtReportTypes[] = {_CRT_WARN, _CRT_ERROR, _CRT_ASSERT};
|
||||||
for(int crtReportType : crtReportTypes) {
|
for(int crtReportType : crtReportTypes) {
|
||||||
_CrtSetReportMode(crtReportType, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
|
_CrtSetReportMode(crtReportType, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
|
||||||
@ -101,6 +113,11 @@ std::vector<std::string> InitPlatform(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
#endif
|
#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,
|
// Extract the command-line arguments; the ones from main() are ignored,
|
||||||
// since they are in the OEM encoding.
|
// since they are in the OEM encoding.
|
||||||
int argcW;
|
int argcW;
|
||||||
|
@ -56,7 +56,7 @@ typedef struct _cairo cairo_t;
|
|||||||
#define ssassert(condition, message) \
|
#define ssassert(condition, message) \
|
||||||
do { \
|
do { \
|
||||||
if(__builtin_expect((condition), true) == false) { \
|
if(__builtin_expect((condition), true) == false) { \
|
||||||
SolveSpace::assert_failure(__FILE__, __LINE__, __func__, #condition, message); \
|
SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \
|
||||||
__builtin_unreachable(); \
|
__builtin_unreachable(); \
|
||||||
} \
|
} \
|
||||||
} while(0)
|
} while(0)
|
||||||
@ -64,7 +64,7 @@ typedef struct _cairo cairo_t;
|
|||||||
#define ssassert(condition, message) \
|
#define ssassert(condition, message) \
|
||||||
do { \
|
do { \
|
||||||
if((condition) == false) { \
|
if((condition) == false) { \
|
||||||
SolveSpace::assert_failure(__FILE__, __LINE__, __func__, #condition, message); \
|
SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \
|
||||||
abort(); \
|
abort(); \
|
||||||
} \
|
} \
|
||||||
} while(0)
|
} while(0)
|
||||||
@ -83,8 +83,8 @@ using std::swap;
|
|||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
__attribute__((noreturn))
|
__attribute__((noreturn))
|
||||||
#endif
|
#endif
|
||||||
void assert_failure(const char *file, unsigned line, const char *function,
|
void AssertFailure(const char *file, unsigned line, const char *function,
|
||||||
const char *condition, const char *message);
|
const char *condition, const char *message);
|
||||||
|
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
__attribute__((__format__ (__printf__, 1, 2)))
|
__attribute__((__format__ (__printf__, 1, 2)))
|
||||||
|
@ -6,6 +6,15 @@
|
|||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
#include "solvespace.h"
|
#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, ...)
|
std::string SolveSpace::ssprintf(const char *fmt, ...)
|
||||||
{
|
{
|
||||||
va_list va;
|
va_list va;
|
||||||
|
Loading…
Reference in New Issue
Block a user