diff --git a/include/wx/debug.h b/include/wx/debug.h index f1a4d9b4e8..b6a61e293e 100644 --- a/include/wx/debug.h +++ b/include/wx/debug.h @@ -251,22 +251,41 @@ extern WXDLLIMPEXP_BASE void wxOnAssert(const char *file, this macro only does anything if wxDEBUG_LEVEL >= 2. */ #if wxDEBUG_LEVEL - // call this function to break into the debugger unconditionally (assuming - // the program is running under debugger, of course) - extern void WXDLLIMPEXP_BASE wxTrap(); - - // assert checks if the condition is true and calls the assert handler with - // the provided message if it isn't + // wxTrap() can be used to break into the debugger unconditionally + // (assuming the program is running under debugger, of course). // - // NB: the macro is defined like this to ensure that nested if/else - // statements containing it are compiled in the same way whether it is - // defined as empty or not; also notice that we can't use ";" instead - // of "{}" as some compilers warn about "possible unwanted ;" then + // If possible, we prefer to define it as a macro rather than as a function + // to open the debugger at the position where we trapped and not inside the + // trap function itself which is not very useful. + #ifdef __VISUALC__ + #define wxTrap() __debugbreak() + #else + extern WXDLLIMPEXP_BASE void wxTrap(); + #endif // Win VisualC + + // Global flag used to indicate that assert macros should call wxTrap(): it + // is set by the default assert handler if the user answers yes to the + // question of whether to trap. + extern WXDLLIMPEXP_DATA_BASE(bool) wxTrapInAssert; + + // This macro checks if the condition is true and calls the assert handler + // with the provided message if it isn't and finally traps if the special + // flag indicating that it should do it was set by the handler. + // + // Notice that we don't use the handler return value for compatibility + // reasons (if we changed its return type, we'd need to change wxApp:: + // OnAssertFailure() too which would break user code overriding it), hence + // the need for the ugly global flag. #define wxASSERT_MSG(cond, msg) \ - if ( !wxTheAssertHandler || (cond) ) \ - {} \ - else \ - wxOnAssert(__FILE__, __LINE__, __WXFUNCTION__, #cond, msg) + wxSTATEMENT_MACRO_BEGIN \ + if ( wxTheAssertHandler && !(cond) && \ + (wxOnAssert(__FILE__, __LINE__, __WXFUNCTION__, \ + #cond, msg), wxTrapInAssert) ) \ + { \ + wxTrapInAssert = false; \ + wxTrap(); \ + } \ + wxSTATEMENT_MACRO_END // a version without any additional message, don't use unless condition // itself is fully self-explanatory @@ -274,11 +293,17 @@ extern WXDLLIMPEXP_BASE void wxOnAssert(const char *file, // wxFAIL is a special form of assert: it always triggers (and so is // usually used in normally unreachable code) - #define wxFAIL_COND_MSG(cond, msg) \ - if ( !wxTheAssertHandler ) \ - {} \ - else \ - wxOnAssert(__FILE__, __LINE__, __WXFUNCTION__, cond, msg) + #define wxFAIL_COND_MSG(cond, msg) \ + wxSTATEMENT_MACRO_BEGIN \ + if ( wxTheAssertHandler && \ + (wxOnAssert(__FILE__, __LINE__, __WXFUNCTION__, \ + #cond, msg), wxTrapInAssert) ) \ + { \ + wxTrapInAssert = false; \ + wxTrap(); \ + } \ + wxSTATEMENT_MACRO_END + #define wxFAIL_MSG(msg) wxFAIL_COND_MSG("Assert failure", msg) #define wxFAIL wxFAIL_MSG((const char*)NULL) #else // !wxDEBUG_LEVEL diff --git a/samples/except/except.cpp b/samples/except/except.cpp index 772b1d02dd..1cb291eeb5 100644 --- a/samples/except/except.cpp +++ b/samples/except/except.cpp @@ -120,6 +120,7 @@ public: void OnThrowUnhandled(wxCommandEvent& event); void OnCrash(wxCommandEvent& event); + void OnTrap(wxCommandEvent& event); #if wxUSE_ON_FATAL_EXCEPTION void OnHandleCrash(wxCommandEvent& event); #endif @@ -188,6 +189,7 @@ enum Except_ThrowObject, Except_ThrowUnhandled, Except_Crash, + Except_Trap, #if wxUSE_ON_FATAL_EXCEPTION Except_HandleCrash, #endif // wxUSE_ON_FATAL_EXCEPTION @@ -217,6 +219,7 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(Except_ThrowObject, MyFrame::OnThrowObject) EVT_MENU(Except_ThrowUnhandled, MyFrame::OnThrowUnhandled) EVT_MENU(Except_Crash, MyFrame::OnCrash) + EVT_MENU(Except_Trap, MyFrame::OnTrap) #if wxUSE_ON_FATAL_EXCEPTION EVT_MENU(Except_HandleCrash, MyFrame::OnHandleCrash) #endif // wxUSE_ON_FATAL_EXCEPTION @@ -353,6 +356,8 @@ MyFrame::MyFrame() menuFile->Append(Except_ThrowUnhandled, wxT("Throw &unhandled exception\tCtrl-U")); menuFile->Append(Except_Crash, wxT("&Crash\tCtrl-C")); + menuFile->Append(Except_Trap, "&Trap\tCtrl-T", + "Break into the debugger (if one is running)"); menuFile->AppendSeparator(); #if wxUSE_ON_FATAL_EXCEPTION menuFile->AppendCheckItem(Except_HandleCrash, wxT("&Handle crashes\tCtrl-H")); @@ -447,6 +452,11 @@ void MyFrame::OnCrash(wxCommandEvent& WXUNUSED(event)) DoCrash(); } +void MyFrame::OnTrap(wxCommandEvent& WXUNUSED(event)) +{ + wxTrap(); +} + #if wxUSE_ON_FATAL_EXCEPTION void MyFrame::OnHandleCrash(wxCommandEvent& event) diff --git a/src/common/appbase.cpp b/src/common/appbase.cpp index 6f2caafc55..5014ca25df 100644 --- a/src/common/appbase.cpp +++ b/src/common/appbase.cpp @@ -1019,6 +1019,8 @@ void wxAbort() #if wxDEBUG_LEVEL // break into the debugger +#ifndef wxTrap + void wxTrap() { #if defined(__WINDOWS__) && !defined(__WXMICROWIN__) @@ -1032,6 +1034,8 @@ void wxTrap() #endif // Win/Unix } +#endif // wxTrap already defined as a macro + // default assert handler static void wxDefaultAssertHandler(const wxString& file, @@ -1181,6 +1185,8 @@ static void LINKAGEMODE SetTraceMasks() #if wxDEBUG_LEVEL +bool wxTrapInAssert = false; + static bool DoShowAssertDialog(const wxString& msg) { @@ -1199,7 +1205,14 @@ bool DoShowAssertDialog(const wxString& msg) MB_YESNOCANCEL | MB_ICONSTOP ) ) { case IDYES: - wxTrap(); + // If we called wxTrap() directly from here, the programmer would + // see this function and a few more calls between his own code and + // it in the stack trace which would be perfectly useless and often + // confusing. So instead just set the flag here and let the macros + // defined in wx/debug.h call wxTrap() themselves, this ensures + // that the debugger will show the line in the user code containing + // the failing assert. + wxTrapInAssert = true; break; case IDCANCEL: