1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-10-20 17:41:13 +02:00

New library lib-exceptions

This commit is contained in:
Paul Licameli
2021-02-17 21:11:45 -05:00
parent e6c109b2cd
commit 0683d3c158
12 changed files with 45 additions and 19 deletions

View File

@@ -0,0 +1,116 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file AudacityException.cpp
@brief Implements AudacityException and related
Paul Licameli
***********************************************************************/
#include "AudacityException.h"
#include <wx/atomic.h>
#include "BasicUI.h"
AudacityException::~AudacityException()
{
}
void AudacityException::EnqueueAction(
std::exception_ptr pException,
std::function<void(AudacityException*)> delayedHandler)
{
BasicUI::CallAfter( [
pException = std::move(pException), delayedHandler = std::move(delayedHandler)
] {
try {
std::rethrow_exception(pException);
}
catch( AudacityException &e )
{ delayedHandler( &e ); }
} );
}
wxAtomicInt sOutstandingMessages {};
MessageBoxException::MessageBoxException(
ExceptionType exceptionType_, const TranslatableString& caption_)
: caption { caption_ }
, exceptionType { exceptionType_ }
{
if (!caption.empty())
wxAtomicInc( sOutstandingMessages );
else
// invalidate me
moved = true;
}
// The class needs a copy constructor to be throwable
// (or will need it, by C++14 rules). But the copy
// needs to act like a move constructor. There must be unique responsibility
// for each exception thrown to decrease the global count when it is handled.
MessageBoxException::MessageBoxException( const MessageBoxException& that )
{
caption = that.caption;
moved = that.moved;
helpUrl = that.helpUrl;
exceptionType = that.exceptionType;
that.moved = true;
}
MessageBoxException::~MessageBoxException()
{
if (!moved)
// If exceptions are used properly, you should never reach this,
// because moved should become true earlier in the object's lifetime.
wxAtomicDec( sOutstandingMessages );
}
SimpleMessageBoxException::~SimpleMessageBoxException()
{
}
TranslatableString SimpleMessageBoxException::ErrorMessage() const
{
return message;
}
// This is meant to be invoked via wxEvtHandler::CallAfter
void MessageBoxException::DelayedHandlerAction()
{
if (!moved) {
// This test prevents accumulation of multiple messages between idle
// times of the main even loop. Only the last queued exception
// displays its message. We assume that multiple messages have a
// common cause such as exhaustion of disk space so that the others
// give the user no useful added information.
using namespace BasicUI;
if ( wxAtomicDec( sOutstandingMessages ) == 0 ) {
if (exceptionType != ExceptionType::Internal
&& ErrorHelpUrl().IsEmpty()) {
// We show BadEnvironment and BadUserAction in a similar way
ShowMessageBox(
ErrorMessage(),
MessageBoxOptions{}
.Caption(caption.empty() ? DefaultCaption() : caption)
.IconStyle(Icon::Error) );
}
else {
using namespace BasicUI;
auto type = exceptionType == ExceptionType::Internal
? ErrorDialogType::ModalErrorReport : ErrorDialogType::ModalError;
ShowErrorDialog( {},
(caption.empty() ? DefaultCaption() : caption),
ErrorMessage(),
ErrorHelpUrl(),
ErrorDialogOptions{ type } );
}
}
moved = true;
}
}

View File

@@ -0,0 +1,231 @@
#ifndef __AUDACITY_EXCEPTION__
#define __AUDACITY_EXCEPTION__
/*!********************************************************************
Audacity: A Digital Audio Editor
@file AudacityException.h
@brief Declare abstract class AudacityException, some often-used subclasses, and @ref GuardedCall
Paul Licameli
**********************************************************************/
#include "MemoryX.h"
#include <exception>
#include <functional>
#include "Internat.h"
//! A type of an exception
enum class ExceptionType
{
Internal, //!< Indicates internal failure from Audacity.
BadUserAction, //!< Indicates that the user performed an action that is not allowed.
BadEnvironment, //!< Indicates problems with environment, such as a full disk
};
//! Base class for exceptions specially processed by the application
/*! Objects of this type can be thrown and caught in any thread, stored, and then used by the main
thread in later idle time to explain the error condition to the user.
*/
class AudacityException /* not final */
{
public:
AudacityException() {}
virtual ~AudacityException() = 0;
//! Action to do in the main thread at idle time of the event loop.
virtual void DelayedHandlerAction() = 0;
EXCEPTIONS_API static void EnqueueAction(
std::exception_ptr pException,
std::function<void(AudacityException*)> delayedHandler);
protected:
//! Make this protected to prevent slicing copies
AudacityException( const AudacityException& ) = default;
//! Don't allow moves of this class or subclasses
// see https://bugzilla.audacityteam.org/show_bug.cgi?id=2442
AudacityException( AudacityException&& ) PROHIBITED;
//! Disallow assignment
AudacityException &operator = ( const AudacityException & ) PROHIBITED;
};
//! Abstract AudacityException subclass displays a message, specified by further subclass
/*! At most one message will be displayed for each pass through the main event idle loop,
no matter how many exceptions were caught. */
class EXCEPTIONS_API MessageBoxException /* not final */
: public AudacityException
{
//! Privatize the inherited function
using AudacityException::DelayedHandlerAction;
//! Do not allow subclasses to change behavior, except by overriding ErrorMessage().
void DelayedHandlerAction() final;
protected:
//! If default-constructed with empty caption, it makes no message box.
explicit MessageBoxException(
ExceptionType exceptionType, //!< Exception type
const TranslatableString &caption //!< Shown in message box's frame; not the actual message
);
~MessageBoxException() override;
MessageBoxException( const MessageBoxException& );
//! %Format the error message for this exception.
virtual TranslatableString ErrorMessage() const = 0;
virtual wxString ErrorHelpUrl() const { return helpUrl; };
private:
TranslatableString caption; //!< Stored caption
ExceptionType exceptionType; //!< Exception type
mutable bool moved { false }; //!< Whether @c *this has been the source of a copy
protected:
mutable wxString helpUrl{ "" };
};
//! A MessageBoxException that shows a given, unvarying string.
class EXCEPTIONS_API SimpleMessageBoxException /* not final */
: public MessageBoxException
{
public:
explicit SimpleMessageBoxException(
ExceptionType exceptionType, //!< Exception type
const TranslatableString &message_, //<! Message to show
const TranslatableString &caption = XO("Message"), //<! Short caption in frame around message
const wxString &helpUrl_ = "" // Optional URL for help.
)
: MessageBoxException { exceptionType, caption }
, message{ message_ }
{
helpUrl = helpUrl_;
}
~SimpleMessageBoxException() override;
SimpleMessageBoxException( const SimpleMessageBoxException& ) = default;
SimpleMessageBoxException &operator = (
SimpleMessageBoxException && ) PROHIBITED;
// Format a default, internationalized error message for this exception.
virtual TranslatableString ErrorMessage() const override;
private:
TranslatableString message; //!< Stored message
};
//! A default template parameter for @ref GuardedCall
struct DefaultDelayedHandlerAction
{
void operator () (AudacityException *pException) const
{
if ( pException )
pException->DelayedHandlerAction();
}
};
//! A default template parameter for @ref GuardedCall<R>
/*! @tparam R return type from GuardedCall (or convertible to it) */
template <typename R> struct SimpleGuard
{
explicit SimpleGuard(
R value //!< The value to return from GurdedCall when an exception is handled
)
: m_value{ value } {}
R operator () ( AudacityException * ) const { return m_value; }
const R m_value;
};
//! Specialization of SimpleGuard, also defining a default value
template<> struct SimpleGuard<bool>
{
explicit SimpleGuard(
bool value //!< The value to return from @ref GaurdedCall when an exception is handled
)
: m_value{ value } {}
bool operator () ( AudacityException * ) const { return m_value; }
static SimpleGuard Default()
{ return SimpleGuard{ false }; }
const bool m_value;
};
//! Specialization of SimpleGuard, also defining a default value
template<> struct SimpleGuard<void>
{
SimpleGuard() {}
void operator () ( AudacityException * ) const {}
static SimpleGuard Default() { return {}; }
};
//! Convert a value to a handler function returning that value, suitable for @ref GuardedCall<R>
template < typename R >
SimpleGuard< R > MakeSimpleGuard( R value )
{ return SimpleGuard< R >{ value }; }
//! Convert a value to a no-op handler function, suitable for @ref GuardedCall<void>
inline SimpleGuard< void > MakeSimpleGuard() { return {}; }
/*!
Executes a given function (typically a lamba), in any thread.
If there is any exception, can invoke another given function as handler, which may rethrow that or
another exception, but usually just returns the value for the GuardedCall.
If AudacityException is handled, then it queues up a delayed handler action for execution later in
the event loop at idle time, on the main thread; typically this informs the user of the error.
The default delayed handler action is simply to invoke a method of the AudacityException, but this
too can be specified otherwise by a third function.
@tparam R Return type, defaulted to void, or else the only explicit template parameter
@tparam F1 deduced type of body function; takes no arguments, returns @b R
@tparam F2 deduced type of handler function, or defaulted to @ref SimpleGuard<R>;
takes pointer to AudacityException, which is null when some other type of exception is caught;
return value is converted to @b R
@tparam F3 deduced type of delayed handler function, if a nondefault argument is given;
takes pointer to AudacityException, return value is unused
*/
template <
typename R = void,
typename F1, // function object with signature R()
typename F2 = SimpleGuard< R > // function object
// with signature R( AudacityException * )
>
//! Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread
R GuardedCall(
const F1 &body, //!< typically a lambda
const F2 &handler = F2::Default(), //!< default just returns false or void; see also @ref MakeSimpleGuard
std::function<void(AudacityException*)> delayedHandler
= DefaultDelayedHandlerAction{} /*!<called later in the main thread,
passing it a stored exception; usually defaulted */
)
{
try { return body(); }
catch ( AudacityException &e ) {
auto end = finally([&]{
// At this point, e is the "current" exception, but not "uncaught"
// unless it was rethrown by handler. handler might also throw some
// other exception object.
if (!std::uncaught_exception()) {
auto pException = std::current_exception(); // This points to e
AudacityException::EnqueueAction(
pException, std::move(delayedHandler));
}
});
return handler( &e );
}
catch ( ... ) {
return handler( nullptr );
}
}
#endif

View File

@@ -0,0 +1,34 @@
#[[
Toolkit neutral library for exceptions.
Abstract class AudacityException with a member function for a delayed
handler action, enqueued in the main thread;
Some commonly useful subclasses, with delayed handlers that are no-ops or
are displays of messages to the user;
Function template GuardedCall which stops propagation of exceptions and
enqueues the delayed action.
But this library does NOT define a top-level handler for the whole application,
to catch all otherwise uncaught exceptions. That is a responsibility of high
level code.
]]#
list( APPEND SOURCES
AudacityException.cpp
AudacityException.h
InconsistencyException.cpp
InconsistencyException.h
UserException.cpp
UserException.h
)
set( LIBRARIES
lib-utility-interface
lib-basic-ui-interface
PRIVATE
wxBase
)
audacity_library( lib-exceptions "${SOURCES}" "${LIBRARIES}"
"" ""
)

View File

@@ -0,0 +1,35 @@
/*!
@file InconsistencyException.cpp
@brief Implements InconsistencyException
Created by Paul Licameli on 11/27/16.
*/
#include "InconsistencyException.h"
#include <wx/filename.h>
InconsistencyException::~InconsistencyException()
{
}
TranslatableString InconsistencyException::ErrorMessage() const
{
// Shorten the path
wxString path { file };
auto sub = wxString{ wxFILE_SEP_PATH } + "src" + wxFILE_SEP_PATH;
auto index = path.Find(sub);
if (index != wxNOT_FOUND)
path = path.Mid(index + sub.size());
#ifdef __func__
return
XO("Internal error in %s at %s line %d.\nPlease inform the Audacity team at https://forum.audacityteam.org/.")
.Format( func, path, line );
#else
return
XO("Internal error at %s line %d.\nPlease inform the Audacity team at https://forum.audacityteam.org/.")
.Format( path, line );
#endif
}

View File

@@ -0,0 +1,81 @@
/*!
@file InconsistencyException.h
@brief MessageBoxException for violation of preconditions or assertions
Created by Paul Licameli on 11/27/16.
*/
#ifndef __AUDACITY_INCONSISTENCY_EXCEPTION__
#define __AUDACITY_INCONSISTENCY_EXCEPTION__
#include "AudacityException.h"
//! Exception that should be impossible in production, thrown only from provably unreachable places
/*! Some errors that formerly were assertion violations now throw exceptions,
even in production code. These may be violations of function preconditions
or the results of logical errors internal to functions. These conditions
are supposed to be deducible statically as never happening.
The error message identifies source file and line number, possibly the function too (depending on
the compiler), and suggests that the user inform the development team.
*/
class EXCEPTIONS_API InconsistencyException final : public MessageBoxException
{
public:
InconsistencyException ()
: MessageBoxException{ ExceptionType::Internal, XO ("Internal Error") }
{}
//! Don't call this directly but use @ref CONSTRUCT_INCONSISTENCY_EXCEPTION or @ref THROW_INCONSISTENCY_EXCEPTION
explicit InconsistencyException(
const char *fn, //!< file name supplied by preprocessor
const char *f, //!< function name supplied by preprocessor
unsigned l //!< line number supplied by preprocessor
)
: MessageBoxException { ExceptionType::Internal, XO("Internal Error") }
, func { fn }, file { f }, line { l }
{}
InconsistencyException(InconsistencyException&& that)
: MessageBoxException(std::move(that))
, func{ that.func }
, file{ that.file }
, line{ that.line }
{}
~InconsistencyException() override;
unsigned GetLine() const { return line; }
private:
// Format a default, internationalized error message for this exception.
TranslatableString ErrorMessage() const override;
const char *func {};
const char *file {};
unsigned line {};
};
#ifdef __func__
#define CONSTRUCT_INCONSISTENCY_EXCEPTION \
InconsistencyException( __func__, __FILE__ , __LINE__ )
#else
/*! @def CONSTRUCT_INCONSISTENCY_EXCEPTION
@brief Construct InconsistencyException, using C++ preprocessor to identify the source code location
For cases where the exception object is not immediately thrown */
#define CONSTRUCT_INCONSISTENCY_EXCEPTION \
InconsistencyException( "", __FILE__ , __LINE__ )
#endif
/*! @def THROW_INCONSISTENCY_EXCEPTION
@brief Throw InconsistencyException, using C++ preprocessor to identify the source code location
*/
#define THROW_INCONSISTENCY_EXCEPTION throw CONSTRUCT_INCONSISTENCY_EXCEPTION
#endif

View File

@@ -0,0 +1,17 @@
/*!
@file UserException.cpp
@brief implements UserException
Created by Paul Licameli on 11/27/16.
*/
#include "UserException.h"
UserException::~UserException()
{
}
void UserException::DelayedHandlerAction()
{
}

View File

@@ -0,0 +1,26 @@
/*!
@file UserException.h
@brief An AudacityException with no visible message
Created by Paul Licameli on 11/27/16.
*/
#ifndef __AUDACITY_USER_EXCEPTION__
#define __AUDACITY_USER_EXCEPTION__
#include "AudacityException.h"
//! Can be thrown when user cancels operations, as with a progress dialog. Delayed handler does nothing
/*! This class does not inherit from MessageBoxException. */
class EXCEPTIONS_API UserException final : public AudacityException
{
public:
UserException() {}
~UserException() override;
void DelayedHandlerAction() override;
};
#endif