Add wxApp::StoreCurrentException() and RethrowStoredException().

These methods can be used to ensure that the exceptions thrown from event
handlers are safely rethrown from the code dispatching the events once the
control flow gets back there.

This allows to work around the problem with not being able to propagate
exceptions through non-C++ code and can be used, for example, to catch
exceptions thrown by the handlers invoked from inside wxYield() by a try/catch
block around wxYield() -- something that didn't work before, update the except
sample to show that it does work now.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@77468 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin 2014-08-24 15:31:44 +00:00
parent c779385a1a
commit 1cecee5bb7
9 changed files with 333 additions and 22 deletions

View File

@ -32,6 +32,7 @@ Changes in behaviour which may result in build errors
All:
- Add wxApp::StoreCurrentException() and RethrowStoredException().
- Allow iterating over wxCmdLineParser arguments in order (Armel Asselin).
- Add wxScopedArray ctor taking the number of elements to allocate.
- Add wxDynamicLibrary::GetModuleFromAddress() (Luca Bacci).

View File

@ -72,6 +72,40 @@ the user about the problem (while being careful not to throw any more
exceptions as otherwise @c std::terminate() will be called).
@section overview_exceptions_store_rethrow Handling Exception Inside wxYield()
In some, relatively rare cases, using wxApp::OnExceptionInMainLoop() may not
be sufficiently flexible. The most common example is using automated GUI tests,
when test failures are signaled by throwing an exception and these exceptions
can't be caught in a single central method because their handling depends on
the test logic, e.g. sometimes an exception is expected while at other times it
is an actual error. Typically this results in writing code like the following:
@code
void TestNewDocument()
{
wxUIActionSimulator ui;
ui.Char('n', wxMOD_CONTROL); // simulate creating a new file
// Let wxWidgets dispatch Ctrl+N event, invoke the handler and create the
// new document.
try {
wxYield();
} catch ( ... ) {
// Handle exceptions as failure in the new document creation test.
}
}
@endcode
Unfortunately, by default this example does @e not work because an exception
can't be safely propagated back to the code handling it in @c TestNewDocument()
through the system event dispatch functions which are not compatible with C++
exceptions. Because of this, you need to override wxApp::StoreCurrentException()
and wxApp::RethrowStoredException() to help wxWidgets to safely transport the
exception from the event handler that throws it to the @c catch clause. Please
see the documentation of these functions for more details.
@section overview_exceptions_tech Technicalities
To use any kind of exception support in the library you need to build it

View File

@ -298,10 +298,24 @@ public:
// Function called if an uncaught exception is caught inside the main
// event loop: it may return true to continue running the event loop or
// false to stop it (in the latter case it may rethrow the exception as
// well)
// false to stop it. If this function rethrows the exception, as it does by
// default, simply because there is no general way to handle exceptions,
// StoreCurrentException() will be called to store it because in any case
// the exception can't be allowed to escape.
virtual bool OnExceptionInMainLoop();
// This function can be overridden to store the current exception, in view
// of rethrowing it later when RethrowStoredException() is called. If the
// exception was stored, return true. The default implementation returns
// false, indicating that the exception wasn't stored and that the program
// should be simply aborted.
virtual bool StoreCurrentException();
// If StoreCurrentException() is overridden, this function should be
// overridden as well to rethrow the exceptions stored by it when the
// control gets back to our code, i.e. when it's safe to do it. The default
// version does nothing.
virtual void RethrowStoredException() { }
#endif // wxUSE_EXCEPTIONS

View File

@ -420,16 +420,47 @@ public:
This function is called if an unhandled exception occurs inside the main
application event loop. It can return @true to ignore the exception and to
continue running the loop or @false to exit the loop and terminate the
program. In the latter case it can also use C++ @c throw keyword to
rethrow the current exception.
program.
The default behaviour of this function is the latter in all ports except under
Windows where a dialog is shown to the user which allows him to choose between
the different options. You may override this function in your class to do
something more appropriate.
Finally note that if the exception is rethrown from here, it can be caught in
OnUnhandledException().
If this method rethrows the exception and if the exception can't be
stored for later processing using StoreCurrentException(), the program
will terminate after calling OnUnhandledException().
You should consider overriding this method to perform whichever last
resort exception handling that would be done in a typical C++ program
in a @c try/catch block around the entire @c main() function. As this
method is called during exception handling, you may use the C++ @c
throw keyword to rethrow the current exception to catch it again and
analyze it. For example:
@code
class MyApp : public wxApp {
public:
virtual bool OnExceptionInMainLoop()
{
wxString error;
try {
throw; // Rethrow the current exception.
} catch (const MyException& e) {
error = e.GetMyErrorMessage();
} catch (const std::exception& e) {
error = e.what();
} catch ( ... ) {
error = "unknown error.";
}
wxLogError("Unexpected exception has occurred: %s, the program will terminate.", error);
// Exit the main loop and thus terminate the program.
return false;
}
};
@endcode
*/
virtual bool OnExceptionInMainLoop();
@ -452,6 +483,105 @@ public:
*/
virtual void OnUnhandledException();
/**
Method to store exceptions not handled by OnExceptionInMainLoop().
This function can be overridden to store the current exception, in view
of rethrowing it later when RethrowStoredException() is called. If the
exception was stored, return true. If the exception can't be stored,
i.e. if this function returns false, the program will abort after
calling OnUnhandledException().
It is necessary to override this function if OnExceptionInMainLoop()
doesn't catch all exceptions, but you still want to handle them using
explicit @c try/catch statements. Typical use could be to allow code
like the following to work:
@code
void MyFrame::SomeFunction()
{
try {
MyDialog dlg(this);
dlg.ShowModal();
} catch ( const MyExpectedException& e ) {
// Deal with the exceptions thrown from the dialog.
}
}
@endcode
By default, throwing an exception from an event handler called from the
dialog modal event loop would terminate the application as the
exception can't be safely propagated to the code in the catch clause
because of the presence of the native system functions (through which
C++ exceptions can't, generally speaking, propagate) in the call stack
between them.
Overriding this method allows the exception to be stored when it is
detected and rethrown using RethrowStoredException() when the native
system function dispatching the dialog events terminates, with the
result that the code above works as expected.
An example of implementing this method:
@code
class MyApp : public wxApp {
public:
virtual bool StoreCurrentException()
{
try {
throw;
} catch ( const std::runtime_exception& e ) {
if ( !m_runtimeError.empty() ) {
// This is not supposed to happen, only one exception,
// at most, should be stored.
return false;
}
m_runtimeError = e.what();
// Don't terminate, let our code handle this exception later.
return true;
} catch ( ... ) {
// This could be extended to store information about any
// other exceptions too, but if we don't store them, we
// should return false to let the program die.
}
return false;
}
virtual void RethrowStoredException()
{
if ( !m_runtimeError.empty() ) {
std::runtime_exception e(m_runtimeError);
m_runtimeError.clear();
throw e;
}
}
private:
std::string m_runtimeError;
};
@endcode
@see OnExceptionInMainLoop(), RethrowStoredException()
@since 3.1.0
*/
virtual bool StoreCurrentException();
/**
Method to rethrow exceptions stored by StoreCurrentException().
If StoreCurrentException() is overridden, this function should be
overridden as well to rethrow the exceptions stored by it when the
control gets back to our code, i.e. when it's safe to do it.
See StoreCurrentException() for an example of implementing this method.
@since 3.1.0
*/
virtual void RethrowStoredException();
//@}

View File

@ -76,6 +76,11 @@ static void DoCrash()
class MyApp : public wxApp
{
public:
MyApp()
{
m_numStoredExceptions = 0;
}
// override base class virtuals
// ----------------------------
@ -86,6 +91,12 @@ public:
// event handler here
virtual bool OnExceptionInMainLoop() wxOVERRIDE;
// 2nd-level exception handling helpers: if we can't deal with the
// exception immediately, we may also store it and rethrow it later, when
// we're back from events processing loop.
virtual bool StoreCurrentException() wxOVERRIDE;
virtual void RethrowStoredException() wxOVERRIDE;
// 3rd, and final, level exception handling: whenever an unhandled
// exception is caught, this function is called
virtual void OnUnhandledException() wxOVERRIDE;
@ -101,6 +112,11 @@ public:
const wxChar *func,
const wxChar *cond,
const wxChar *msg) wxOVERRIDE;
private:
// This stores the number of times StoreCurrentException() was called,
// typically at most 1.
int m_numStoredExceptions;
};
// Define a new frame type: this is going to be our main frame
@ -155,6 +171,7 @@ public:
// event handlers
void OnThrowInt(wxCommandEvent& event);
void OnThrowObject(wxCommandEvent& event);
void OnThrowUnhandled(wxCommandEvent& event);
void OnCrash(wxCommandEvent& event);
private:
@ -174,6 +191,9 @@ private:
};
// Another exception class which just has to be different from anything else
//
// It is not handled by OnExceptionInMainLoop() but is still handled by
// explicit try/catch blocks so it's not quite completely unhandled, actually.
class UnhandledException
{
};
@ -236,6 +256,7 @@ wxEND_EVENT_TABLE()
wxBEGIN_EVENT_TABLE(MyDialog, wxDialog)
EVT_BUTTON(Except_ThrowInt, MyDialog::OnThrowInt)
EVT_BUTTON(Except_ThrowObject, MyDialog::OnThrowObject)
EVT_BUTTON(Except_ThrowUnhandled, MyDialog::OnThrowUnhandled)
EVT_BUTTON(Except_Crash, MyDialog::OnCrash)
wxEND_EVENT_TABLE()
@ -291,6 +312,41 @@ bool MyApp::OnExceptionInMainLoop()
return true;
}
bool MyApp::StoreCurrentException()
{
try
{
throw;
}
catch ( UnhandledException& )
{
if ( m_numStoredExceptions )
{
wxLogWarning("Unexpectedly many exceptions to store.");
}
m_numStoredExceptions++;
return true;
}
catch ( ... )
{
// Don't know how to store other exceptions.
}
return false;
}
void MyApp::RethrowStoredException()
{
if ( m_numStoredExceptions )
{
m_numStoredExceptions = 0;
throw UnhandledException();
}
}
void MyApp::OnUnhandledException()
{
// this shows how we may let some exception propagate uncaught
@ -424,6 +480,10 @@ void MyFrame::OnDialog(wxCommandEvent& WXUNUSED(event))
dlg.ShowModal();
}
catch ( UnhandledException& )
{
wxLogMessage("Caught unhandled exception inside the dialog.");
}
catch ( ... )
{
wxLogWarning(wxT("An exception in MyDialog"));
@ -457,8 +517,12 @@ void MyFrame::OnThrowFromYield(wxCommandEvent& WXUNUSED(event))
{
#if wxUSE_UIACTIONSIMULATOR
// Simulate selecting the "Throw unhandled" menu item, its handler will be
// executed from inside wxYield(), so we may not be able to catch the
// exception here under Win64 even in spite of an explicit catch.
// executed from inside wxYield() and as the exception is not handled by
// our OnExceptionInMainLoop(), will call StoreCurrentException() and, when
// wxYield() regains control, RethrowStoredException().
//
// Notice that if we didn't override these methods we wouldn't be able to
// catch this exception here!
try
{
wxUIActionSimulator sim;
@ -550,13 +614,15 @@ MyDialog::MyDialog(wxFrame *parent)
wxSizer *sizerTop = new wxBoxSizer(wxVERTICAL);
sizerTop->Add(new wxButton(this, Except_ThrowInt, wxT("Throw &int")),
0, wxCENTRE | wxALL, 5);
0, wxEXPAND | wxALL, 5);
sizerTop->Add(new wxButton(this, Except_ThrowObject, wxT("Throw &object")),
0, wxCENTRE | wxALL, 5);
0, wxEXPAND | wxALL, 5);
sizerTop->Add(new wxButton(this, Except_ThrowUnhandled, wxT("Throw &unhandled")),
0, wxEXPAND | wxALL, 5);
sizerTop->Add(new wxButton(this, Except_Crash, wxT("&Crash")),
0, wxCENTRE | wxALL, 5);
0, wxEXPAND | wxALL, 5);
sizerTop->Add(new wxButton(this, wxID_CANCEL, wxT("&Cancel")),
0, wxCENTRE | wxALL, 5);
0, wxEXPAND | wxALL, 5);
SetSizerAndFit(sizerTop);
}
@ -571,6 +637,11 @@ void MyDialog::OnThrowObject(wxCommandEvent& WXUNUSED(event))
throw MyException(wxT("Exception thrown from MyDialog"));
}
void MyDialog::OnThrowUnhandled(wxCommandEvent& WXUNUSED(event))
{
throw UnhandledException();
}
void MyDialog::OnCrash(wxCommandEvent& WXUNUSED(event))
{
DoCrash();

View File

@ -662,6 +662,11 @@ bool wxAppConsoleBase::OnExceptionInMainLoop()
throw;
}
bool wxAppConsoleBase::StoreCurrentException()
{
return false;
}
#endif // wxUSE_EXCEPTIONS
// ----------------------------------------------------------------------------

View File

@ -1624,24 +1624,59 @@ bool wxEvtHandler::SafelyProcessEvent(wxEvent& event)
loop->Exit();
}
//else: continue running current event loop
return false;
}
catch ( ... )
{
// OnExceptionInMainLoop() threw, possibly rethrowing the same
// exception again: very good, but we still need Exit() to
// be called, unless we're not called from the loop directly but
// from Yield(), in which case we shouldn't exit the loop but just
// unwind to the point where Yield() is called where the exception
// might be handled -- and if not, then it will unwind further and
// exit the loop when it is caught.
// exception again. We have to deal with it here because we can't
// allow the exception to escape from the handling code, this will
// result in a crash at best (e.g. when using wxGTK as C++
// exceptions can't propagate through the C GTK+ code and corrupt
// the stack) and in something even more weird at worst (like
// exceptions completely disappearing into the void under some
// 64 bit versions of Windows).
if ( loop && !loop->IsYielding() )
loop->Exit();
throw;
// Give the application one last possibility to store the exception
// for rethrowing it later, when we get back to our code.
bool stored = false;
try
{
if ( wxTheApp )
stored = wxTheApp->StoreCurrentException();
}
catch ( ... )
{
// StoreCurrentException() really shouldn't throw, but if it
// did, take it as an indication that it didn't store it.
}
// If it didn't take it, just abort, at least like this we behave
// consistently everywhere.
if ( !stored )
{
try
{
if ( wxTheApp )
wxTheApp->OnUnhandledException();
}
catch ( ... )
{
// And OnUnhandledException() absolutely shouldn't throw,
// but we still must account for the possibility that it
// did. At least show some information about the exception
// in this case.
wxTheApp->wxAppConsoleBase::OnUnhandledException();
}
wxAbort();
}
}
}
#endif // wxUSE_EXCEPTIONS
return false;
}
bool wxEvtHandler::SearchEventTable(wxEventTable& table, wxEvent& event)

View File

@ -140,6 +140,14 @@ bool wxEventLoopBase::YieldFor(long eventsToProcess)
DoYieldFor(eventsToProcess);
// If any handlers called from inside DoYieldFor() threw exceptions, they
// may have been stored for later rethrow as it's unsafe to let them escape
// from inside DoYieldFor() itself, as it calls native functions through
// which the exceptions can't propagate. But now that we're back to our own
// code, we may rethrow them.
if ( wxTheApp )
wxTheApp->RethrowStoredException();
return true;
}
@ -207,7 +215,15 @@ bool wxEventLoopManual::ProcessEvents()
if ( m_shouldExit )
return false;
}
return Dispatch();
const bool res = Dispatch();
// Rethrow any exceptions which could have been produced by the handlers
// ran by Dispatch().
if ( wxTheApp )
wxTheApp->RethrowStoredException();
return res;
}
int wxEventLoopManual::DoRun()

View File

@ -78,6 +78,11 @@ int wxGUIEventLoop::DoRun()
OnExit();
// Rethrow any exceptions which could have been produced by the handlers
// ran by the event loop.
if ( wxTheApp )
wxTheApp->RethrowStoredException();
return m_exitcode;
}