diff options
Diffstat (limited to 'indra/llcommon')
25 files changed, 754 insertions, 93 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 907dbab8f8..410a5819b3 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -58,6 +58,7 @@ set(llcommon_SOURCE_FILES      lleventfilter.cpp      llevents.cpp      lleventtimer.cpp +    llexception.cpp      llfasttimer.cpp      llfile.cpp      llfindlocale.cpp @@ -157,6 +158,7 @@ set(llcommon_HEADER_FILES      lleventfilter.h      llevents.h      lleventemitter.h +    llexception.h      llfasttimer.h      llfile.h      llfindlocale.h @@ -315,7 +317,7 @@ if (LL_TESTS)    LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}") -  LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")                           +  LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") @@ -328,6 +330,11 @@ if (LL_TESTS)    LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") +## llexception_test.cpp isn't a regression test, and doesn't need to be run +## every build. It's to help a developer make implementation choices about +## throwing and catching exceptions. +##LL_ADD_INTEGRATION_TEST(llexception "" "${test_libs}") +    # *TODO - reenable these once tcmalloc libs no longer break the build.    #ADD_BUILD_TEST(llallocator llcommon)    #ADD_BUILD_TEST(llallocator_heap_profile llcommon) diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index d9933b3d36..ff9a92b45f 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -172,12 +172,12 @@ public:  	virtual bool cleanup() = 0;			// Override to do application cleanup  	// -	// mainLoop() +	// frame()  	// -	// Runs the application main loop.  It's assumed that when you exit -	// this method, the application is in one of the cleanup states, either QUITTING or ERROR +	// Pass control to the application for a single frame. Returns 'done' +	// flag: if frame() returns false, it expects to be called again.  	// -	virtual bool mainLoop() = 0; // Override for the application main loop.  Needs to at least gracefully notice the QUITTING state and exit. +	virtual bool frame() = 0; // Override for application body logic  	//  	// Crash logging diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index d16bf0160b..8e516d8beb 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -38,6 +38,7 @@  #include "llevents.h"  #include "llerror.h"  #include "stringize.h" +#include "llexception.h"  // do nothing, when we need nothing done  void LLCoros::no_cleanup(CoroData*) {} @@ -131,9 +132,9 @@ bool LLCoros::cleanup(const LLSD&)              if ((previousCount < 5) || !(previousCount % 50))              {                  if (previousCount < 5) -                    LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL; +                    LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;                  else -                    LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << "("<< previousCount << ")" << LL_ENDL; +                    LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << "("<< previousCount << ")" << LL_ENDL;              }              // The erase() call will invalidate its passed iterator value -- @@ -185,9 +186,9 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const              if ((previousCount < 5) || !(previousCount % 50))              {                  if (previousCount < 5) -                    LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL; +                    LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;                  else -                    LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL; +                    LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL;              } @@ -223,7 +224,7 @@ std::string LLCoros::getName() const  void LLCoros::setStackSize(S32 stacksize)  { -    LL_INFOS("LLCoros") << "Setting coroutine stack size to " << stacksize << LL_ENDL; +    LL_DEBUGS("LLCoros") << "Setting coroutine stack size to " << stacksize << LL_ENDL;      mStackSize = stacksize;  } @@ -235,7 +236,23 @@ void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& calla      // capture the 'self' param in CoroData      data->mSelf = &self;      // run the code the caller actually wants in the coroutine -    callable(); +    try +    { +        callable(); +    } +    catch (const LLContinueError&) +    { +        // Any uncaught exception derived from LLContinueError will be caught +        // here and logged. This coroutine will terminate but the rest of the +        // viewer will carry on. +        LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName)); +    } +    catch (...) +    { +        // Any OTHER kind of uncaught exception will cause the viewer to +        // crash, hopefully informatively. +        CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName)); +    }      // This cleanup isn't perfectly symmetrical with the way we initially set      // data->mPrev, but this is our last chance to reset mCurrentCoro.      sCurrentCoro.reset(data->mPrev); diff --git a/indra/llcommon/lldependencies.cpp b/indra/llcommon/lldependencies.cpp index 0e72c175cb..0d5757effd 100644 --- a/indra/llcommon/lldependencies.cpp +++ b/indra/llcommon/lldependencies.cpp @@ -40,6 +40,7 @@  #include <boost/graph/topological_sort.hpp>  #include <boost/graph/exception.hpp>  // other Linden headers +#include "llexception.h"  LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const EdgeList& edges) const  { @@ -76,7 +77,7 @@ LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const          // Omit independent nodes: display only those that might contribute to          // the cycle.          describe(out, false); -        throw Cycle(out.str()); +        LLTHROW(Cycle(out.str()));      }      // A peculiarity of boost::topological_sort() is that it emits results in      // REVERSE topological order: to get the result you want, you must diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h index e0294e271b..125bd6a835 100644 --- a/indra/llcommon/lldependencies.h +++ b/indra/llcommon/lldependencies.h @@ -34,13 +34,13 @@  #include <vector>  #include <set>  #include <map> -#include <stdexcept>  #include <iosfwd>  #include <boost/iterator/transform_iterator.hpp>  #include <boost/iterator/indirect_iterator.hpp>  #include <boost/range/iterator_range.hpp>  #include <boost/function.hpp>  #include <boost/bind.hpp> +#include "llexception.h"  /*****************************************************************************  *   Utilities @@ -106,9 +106,9 @@ public:      /**       * Exception thrown by sort() if there's a cycle       */ -    struct Cycle: public std::runtime_error +    struct Cycle: public LLException      { -        Cycle(const std::string& what): std::runtime_error(what) {} +        Cycle(const std::string& what): LLException(what) {}      };      /** diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 3beef65723..7cbe4334b3 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -174,7 +174,8 @@ namespace LLError  			// not really a level  			// used to indicate that no messages should be logged  	}; -	 +	// If you change ELevel, please update llvlog() macro below. +  	/*	Macro support  		The classes CallSite and Log are used by the logging macros below.  		They are not intended for general use. @@ -305,24 +306,38 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;  /////////////////////////////////  // Error Logging Macros -// See top of file for common usage.	 +// See top of file for common usage.  ///////////////////////////////// -// this macro uses a one-shot do statement to avoid parsing errors when writing control flow statements -// without braces: -// if (condition) LL_INFOS() << "True" << LL_ENDL; else LL_INFOS()() << "False" << LL_ENDL - -#define lllog(level, once, ...)																	          \ -	do {                                                                                                  \ -		const char* tags[] = {"", ##__VA_ARGS__};													      \ -		::size_t tag_count = LL_ARRAY_SIZE(tags) - 1;													  \ -		static LLError::CallSite _site(                                                                   \ -		    level, __FILE__, __LINE__, typeid(_LL_CLASS_TO_LOG), __FUNCTION__, once, &tags[1], tag_count);\ -		if (LL_UNLIKELY(_site.shouldLog()))			                                                      \ -		{                                                                                                 \ -			std::ostringstream* _out = LLError::Log::out();                                               \ +// Instead of using LL_DEBUGS(), LL_INFOS() et al., it may be tempting to +// directly code the lllog() macro so you can pass in the LLError::ELevel as a +// variable. DON'T DO IT! The reason is that the first time control passes +// through lllog(), it initializes a local static LLError::CallSite with that +// *first* ELevel value. All subsequent visits will decide whether or not to +// emit output based on the *first* ELevel value bound into that static +// CallSite instance. Use LL_VLOGS() instead. lllog() assumes its ELevel +// argument never varies. + +// this macro uses a one-shot do statement to avoid parsing errors when +// writing control flow statements without braces: +// if (condition) LL_INFOS() << "True" << LL_ENDL; else LL_INFOS()() << "False" << LL_ENDL; + +#define lllog(level, once, ...)                                         \ +	do {                                                                \ +		const char* tags[] = {"", ##__VA_ARGS__};                       \ +		static LLError::CallSite _site(lllog_site_args_(level, once, tags)); \ +		lllog_test_() + +#define lllog_test_()                                       \ +		if (LL_UNLIKELY(_site.shouldLog()))                 \ +		{                                                   \ +			std::ostringstream* _out = LLError::Log::out(); \  			(*_out) +#define lllog_site_args_(level, once, tags)                 \ +	level, __FILE__, __LINE__, typeid(_LL_CLASS_TO_LOG),    \ +	__FUNCTION__, once, &tags[1], LL_ARRAY_SIZE(tags)-1 +  //Use this construct if you need to do computation in the middle of a  //message:  //	 @@ -363,4 +378,46 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;  #define LL_INFOS_ONCE(...)	lllog(LLError::LEVEL_INFO, true, ##__VA_ARGS__)  #define LL_WARNS_ONCE(...)	lllog(LLError::LEVEL_WARN, true, ##__VA_ARGS__) +// Use this if you need to pass LLError::ELevel as a variable. +#define LL_VLOGS(level, ...)      llvlog(level, false, ##__VA_ARGS__) +#define LL_VLOGS_ONCE(level, ...) llvlog(level, true,  ##__VA_ARGS__) + +// The problem with using lllog() with a variable level is that the first time +// through, it initializes a static CallSite instance with whatever level you +// pass. That first level is bound into the CallSite; the level parameter is +// never again examined. One approach to variable level would be to +// dynamically construct a CallSite instance every call -- which could get +// expensive, depending on context. So instead, initialize a static CallSite +// for each level value we support, then dynamically select the CallSite +// instance for the passed level value. +// Compare implementation to lllog() above. +#define llvlog(level, once, ...)                                        \ +	do {                                                                \ +		const char* tags[] = {"", ##__VA_ARGS__};                       \ +		/* Need a static CallSite instance per expected ELevel value. */ \ +		/* Since we intend to index this array with the ELevel, */      \ +		/* _sites[0] should be ELevel(0), and so on -- avoid using */   \ +		/* ELevel symbolic names when initializing -- except for */     \ +		/* the last entry, which handles anything beyond the end. */    \ +		/* (Commented ELevel value names are from 2016-09-01.) */       \ +		/* Passing an ELevel past the end of this array is itself */    \ +		/* a fatal error, so ensure the last is LEVEL_ERROR. */         \ +		static LLError::CallSite _sites[] =                             \ +		{                                                               \ +			/* LEVEL_DEBUG */                                           \ +			LLError::CallSite(lllog_site_args_(LLError::ELevel(0), once, tags)), \ +			/* LEVEL_INFO */                                            \ +			LLError::CallSite(lllog_site_args_(LLError::ELevel(1), once, tags)), \ +			/* LEVEL_WARN */                                            \ +			LLError::CallSite(lllog_site_args_(LLError::ELevel(2), once, tags)), \ +			/* LEVEL_ERROR */                                           \ +			LLError::CallSite(lllog_site_args_(LLError::LEVEL_ERROR, once, tags)) \ +		};                                                              \ +		/* Clamp the passed 'level' to at most last entry */            \ +		std::size_t which((std::size_t(level) >= LL_ARRAY_SIZE(_sites)) ? \ +						  (LL_ARRAY_SIZE(_sites) - 1) : std::size_t(level)); \ +		/* selected CallSite *must* be named _site for LL_ENDL */       \ +		LLError::CallSite& _site(_sites[which]);                        \ +		lllog_test_() +  #endif // LL_LLERROR_H diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 2d5f964deb..56367b8f54 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -39,6 +39,7 @@  #include "llerror.h"  #include "llcoros.h"  #include "llmake.h" +#include "llexception.h"  #include "lleventfilter.h" @@ -351,7 +352,7 @@ LLSD errorException(const LLEventWithID& result, const std::string& desc)      // returning it, deliver it via exception.      if (result.second)      { -        throw LLErrorEvent(desc, result.first); +        LLTHROW(LLErrorEvent(desc, result.first));      }      // That way, our caller knows a simple return must be from the reply      // pump (pump 0). diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h index 87926c692d..84827aab4a 100644 --- a/indra/llcommon/lleventcoro.h +++ b/indra/llcommon/lleventcoro.h @@ -31,10 +31,10 @@  #include <boost/optional.hpp>  #include <string> -#include <stdexcept>  #include <utility>                  // std::pair  #include "llevents.h"  #include "llerror.h" +#include "llexception.h"  /**   * Like LLListenerOrPumpName, this is a class intended for parameter lists: @@ -234,11 +234,11 @@ LLSD errorException(const LLEventWithID& result, const std::string& desc);   * because it's not an error in event processing: rather, this exception   * announces an event that bears error information (for some other API).   */ -class LL_COMMON_API LLErrorEvent: public std::runtime_error +class LL_COMMON_API LLErrorEvent: public LLException  {  public:      LLErrorEvent(const std::string& what, const LLSD& data): -        std::runtime_error(what), +        LLException(what),          mData(data)      {}      virtual ~LLErrorEvent() throw() {} diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index e38fc5b2a6..97270e4931 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -57,6 +57,7 @@  #include "stringize.h"  #include "llerror.h"  #include "llsdutil.h" +#include "llexception.h"  #if LL_MSVC  #pragma warning (disable : 4702)  #endif @@ -174,7 +175,7 @@ std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string      // Unless we're permitted to tweak it, that's Bad.      if (! tweak)      { -        throw LLEventPump::DupPumpName(std::string("Duplicate LLEventPump name '") + name + "'"); +        LLTHROW(LLEventPump::DupPumpName("Duplicate LLEventPump name '" + name + "'"));      }      // The passed name isn't unique, but we're permitted to tweak it. Find the      // first decimal-integer suffix not already taken. The insert() attempt @@ -335,8 +336,8 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL          // is only when the existing connection object is still connected.          if (found != mConnections.end() && found->second.connected())          { -            throw DupListenerName(std::string("Attempt to register duplicate listener name '") + name + -                "' on " + typeid(*this).name() + " '" + getName() + "'"); +        LLTHROW(DupListenerName("Attempt to register duplicate listener name '" + name + +                                "' on " + typeid(*this).name() + " '" + getName() + "'"));          }          // Okay, name is unique, try to reconcile its dependencies. Specify a new          // "node" value that we never use for an mSignal placement; we'll fix it @@ -362,8 +363,8 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL              // unsortable. If we leave the new node in mDeps, it will continue              // to screw up all future attempts to sort()! Pull it out.              mDeps.remove(name); -            throw Cycle(std::string("New listener '") + name + "' on " + typeid(*this).name() + -                " '" + getName() + "' would cause cycle: " + e.what()); +        LLTHROW(Cycle("New listener '" + name + "' on " + typeid(*this).name() + +                      " '" + getName() + "' would cause cycle: " + e.what()));          }          // Walk the list to verify that we haven't changed the order.          float previous = 0.0, myprev = 0.0; @@ -427,7 +428,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL                  // NOW remove the offending listener node.                  mDeps.remove(name);                  // Having constructed a description of the order change, inform caller. -                throw OrderChange(out.str()); +            LLTHROW(OrderChange(out.str()));              }              // This node becomes the previous one.              previous = dmi->second; @@ -627,7 +628,7 @@ bool LLListenerOrPumpName::operator()(const LLSD& event) const  {      if (! mListener)      { -        throw Empty("attempting to call uninitialized"); +        LLTHROW(Empty("attempting to call uninitialized"));      }      return (*mListener)(event);  } diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 694951e699..a3b9ec02e0 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -37,7 +37,6 @@  #include <set>  #include <vector>  #include <deque> -#include <stdexcept>  #if LL_WINDOWS  	#pragma warning (push)  	#pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch @@ -62,6 +61,7 @@  #include "llsingleton.h"  #include "lldependencies.h"  #include "llstl.h" +#include "llexception.h"  /*==========================================================================*|  // override this to allow binding free functions with more parameters @@ -95,12 +95,32 @@ struct LLStopWhenHandled      result_type operator()(InputIterator first, InputIterator last) const      {          for (InputIterator si = first; si != last; ++si) -		{ -            if (*si) -			{ -                return true; -			} -		} +        { +            try +            { +                if (*si) +                { +                    return true; +                } +            } +            catch (const LLContinueError&) +            { +                // We catch LLContinueError here because an LLContinueError- +                // based exception means the viewer as a whole should carry on +                // to the best of our ability. Therefore subsequent listeners +                // on the same LLEventPump should still receive this event. + +                // The iterator passed to a boost::signals2 Combiner is very +                // clever, but provides no contextual information. We would +                // very much like to be able to log the name of the LLEventPump +                // plus the name of this particular listener, but alas. +                LOG_UNHANDLED_EXCEPTION("LLEventPump"); +            } +            // We do NOT catch (...) here because we might as well let it +            // propagate out to the generic handler. If we were able to log +            // context information here, that would be great, but we can't, so +            // there's no point. +        }          return false;      }  }; @@ -188,10 +208,10 @@ public:      bool operator()(const LLSD& event) const;      /// exception if you try to call when empty -    struct Empty: public std::runtime_error +    struct Empty: public LLException      {          Empty(const std::string& what): -            std::runtime_error(std::string("LLListenerOrPumpName::Empty: ") + what) {} +            LLException(std::string("LLListenerOrPumpName::Empty: ") + what) {}      };  private: @@ -373,10 +393,10 @@ public:       * you didn't pass <tt>tweak=true</tt> to permit it to generate a unique       * variant.       */ -    struct DupPumpName: public std::runtime_error +    struct DupPumpName: public LLException      {          DupPumpName(const std::string& what): -            std::runtime_error(std::string("DupPumpName: ") + what) {} +            LLException(std::string("DupPumpName: ") + what) {}      };      /** @@ -401,9 +421,9 @@ public:      /// group exceptions thrown by listen(). We use exceptions because these      /// particular errors are likely to be coding errors, found and fixed by      /// the developer even before preliminary checkin. -    struct ListenError: public std::runtime_error +    struct ListenError: public LLException      { -        ListenError(const std::string& what): std::runtime_error(what) {} +        ListenError(const std::string& what): LLException(what) {}      };      /**       * exception thrown by listen(). You are attempting to register a diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp new file mode 100644 index 0000000000..b32ec2c9c9 --- /dev/null +++ b/indra/llcommon/llexception.cpp @@ -0,0 +1,55 @@ +/** + * @file   llexception.cpp + * @author Nat Goodspeed + * @date   2016-08-12 + * @brief  Implementation for llexception. + *  + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llexception.h" +// STL headers +// std headers +#include <typeinfo> +// external library headers +#include <boost/exception/diagnostic_information.hpp> +// other Linden headers +#include "llerror.h" +#include "llerrorcontrol.h" + +namespace { +// used by crash_on_unhandled_exception_() and log_unhandled_exception_() +void log_unhandled_exception_(LLError::ELevel level, +                              const char* file, int line, const char* pretty_function, +                              const std::string& context) +{ +    // log same message but allow caller-specified severity level +    LL_VLOGS(level, "LLException") << LLError::abbreviateFile(file) +        << "(" << line << "): Unhandled exception caught in " << pretty_function; +    if (! context.empty()) +    { +        LL_CONT << ": " << context; +    } +    LL_CONT << ":\n" << boost::current_exception_diagnostic_information() << LL_ENDL; +} +} + +void crash_on_unhandled_exception_(const char* file, int line, const char* pretty_function, +                                   const std::string& context) +{ +    // LL_ERRS() terminates and propagates message into crash dump. +    log_unhandled_exception_(LLError::LEVEL_ERROR, file, line, pretty_function, context); +} + +void log_unhandled_exception_(const char* file, int line, const char* pretty_function, +                              const std::string& context) +{ +    // Use LL_WARNS() because we seriously do not expect this to happen +    // routinely, but we DO expect to return from this function. +    log_unhandled_exception_(LLError::LEVEL_WARN, file, line, pretty_function, context); +} diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h new file mode 100644 index 0000000000..dfcb7c192f --- /dev/null +++ b/indra/llcommon/llexception.h @@ -0,0 +1,85 @@ +/** + * @file   llexception.h + * @author Nat Goodspeed + * @date   2016-06-29 + * @brief  Types needed for generic exception handling + *  + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEXCEPTION_H) +#define LL_LLEXCEPTION_H + +#include <stdexcept> +#include <boost/exception/exception.hpp> +#include <boost/throw_exception.hpp> +#include <boost/current_function.hpp> + +// "Found someone who can comfort me +//  But there are always exceptions..." +//  - Empty Pages, Traffic, from John Barleycorn (1970) +//    https://www.youtube.com/watch?v=dRH0CGVK7ic + +/** + * LLException is intended as the common base class from which all + * viewer-specific exceptions are derived. Rationale for why it's derived from + * both std::exception and boost::exception is explained in + * tests/llexception_test.cpp. + * + * boost::current_exception_diagnostic_information() is quite wonderful: if + * all we need to do with an exception is log it, in most places we should + * catch (...) and log boost::current_exception_diagnostic_information(). + * See CRASH_ON_UNHANDLED_EXCEPTION() and LOG_UNHANDLED_EXCEPTION() below. + * + * There may be circumstances in which it would be valuable to distinguish an + * exception explicitly thrown by viewer code from an exception thrown by + * (say) a third-party library. Catching (const LLException&) supports such + * usage. However, most of the value of this base class is in the + * diagnostic_information() available via Boost.Exception. + */ +struct LLException: +    public std::runtime_error, +    public boost::exception +{ +    LLException(const std::string& what): +        std::runtime_error(what) +    {} +}; + +/** + * The point of LLContinueError is to distinguish exceptions that need not + * terminate the whole viewer session. In general, an uncaught exception will + * be logged and will crash the viewer. However, though an uncaught exception + * derived from LLContinueError will still be logged, the viewer will attempt + * to continue processing. + */ +struct LLContinueError: public LLException +{ +    LLContinueError(const std::string& what): +        LLException(what) +    {} +}; + +/** + * Please use LLTHROW() to throw viewer exceptions whenever possible. This + * enriches the exception's diagnostic_information() with the source file, + * line and containing function of the LLTHROW() macro. + */ +// Currently we implement that using BOOST_THROW_EXCEPTION(). Wrap it in +// LLTHROW() in case we ever want to revisit that implementation decision. +#define LLTHROW(x) BOOST_THROW_EXCEPTION(x) + +/// Call this macro from a catch (...) clause +#define CRASH_ON_UNHANDLED_EXCEPTION(CONTEXT) \ +     crash_on_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT) +void crash_on_unhandled_exception_(const char*, int, const char*, const std::string&); + +/// Call this from a catch (const LLContinueError&) clause, or from a catch +/// (...) clause in which you do NOT want the viewer to crash. +#define LOG_UNHANDLED_EXCEPTION(CONTEXT) \ +     log_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT) +void log_unhandled_exception_(const char*, int, const char*, const std::string&); + +#endif /* ! defined(LL_LLEXCEPTION_H) */ diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index 2370253078..f56e5596f5 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -296,7 +296,16 @@ LL_FORCE_INLINE BlockTimer::BlockTimer(BlockTimerStatHandle& timer)  {  #if LL_FAST_TIMER_ON  	BlockTimerStackRecord* cur_timer_data = LLThreadLocalSingletonPointer<BlockTimerStackRecord>::getInstance(); -	if (!cur_timer_data) return; +	if (!cur_timer_data) +	{ +		// How likely is it that +		// LLThreadLocalSingletonPointer<T>::getInstance() will return NULL? +		// Even without researching, what we can say is that if we exit +		// without setting mStartTime at all, gcc 4.7 produces (fatal) +		// warnings about a possibly-uninitialized data member. +		mStartTime = 0; +		return; +	}  	TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator();  	accumulator.mActiveCount++;  	// keep current parent as long as it is active when we are diff --git a/indra/llcommon/llhandle.h b/indra/llcommon/llhandle.h index 401e4d759a..feb5f41848 100644 --- a/indra/llcommon/llhandle.h +++ b/indra/llcommon/llhandle.h @@ -28,8 +28,11 @@  #define LLHANDLE_H  #include "llpointer.h" +#include "llexception.h" +#include <stdexcept>  #include <boost/type_traits/is_convertible.hpp>  #include <boost/utility/enable_if.hpp> +#include <boost/throw_exception.hpp>  /**   * Helper object for LLHandle. Don't instantiate these directly, used @@ -213,4 +216,82 @@ private:  	mutable LLRootHandle<T> mHandle;  }; + + +class LLCheckedHandleBase +{ +public: +    class Stale : public LLException +    { +    public: +        Stale() : +            LLException("Attempt to access stale handle.") +        {} +    }; + +protected: +    LLCheckedHandleBase() { } + +}; + +/** + * This is a simple wrapper for Handles, allowing direct calls to the underlying  + * pointer. The checked handle will throw a Stale if an attempt  + * is made to access the object referenced by the handle and that object has  + * been destroyed. + **/ +template <typename T>  +class LLCheckedHandle: public LLCheckedHandleBase +{ +public: + +    LLCheckedHandle(LLHandle<T> handle): +        mHandle(handle) +    { } + +    /** +     * Test the underlying handle.  If it is no longer valid, throw a Stale exception. +     */ +    void check() const +    { +        T* ptr = mHandle.get(); +        if (!ptr) +            BOOST_THROW_EXCEPTION(Stale()); +    } + +    /** +     * Cast back to an appropriate handle +     */ +    operator LLHandle<T>() const +    { +        return mHandle; +    } + +    /** +     * Converts the LLCheckedHandle to a bool. Allows for if (chkdHandle) {}  +     * Does not throw. +     */ +    /*explicit*/ operator bool() const // explicit conversion operator not available with Linux compiler +    { +        return (mHandle.get() != NULL); +    } + +    /** +     * Attempt to call a method or access a member in the structure referenced  +     * by the handle.  If the handle no longer points to a valid structure  +     * throw a Stale. +     */ +    T* operator ->() const +    { +        T* ptr = mHandle.get(); +        if (!ptr) +            BOOST_THROW_EXCEPTION(Stale()); +        return ptr; +    } + +private: + +    LLHandle<T> mHandle; +}; +  #endif diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 84d2a12f65..c87d2a3e58 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -33,6 +33,7 @@  #include "lltimer.h"  #include "lluuid.h"  #include "llleaplistener.h" +#include "llexception.h"  #if LL_MSVC  #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally @@ -69,7 +70,7 @@ public:          // Rule out empty vector          if (plugin.empty())          { -            throw Error("no plugin command"); +            LLTHROW(Error("no plugin command"));          }          // Don't leave desc empty either, but in this case, if we weren't @@ -112,7 +113,7 @@ public:          // If that didn't work, no point in keeping this LLLeap object.          if (! mChild)          { -            throw Error(STRINGIZE("failed to run " << mDesc)); +            LLTHROW(Error(STRINGIZE("failed to run " << mDesc)));          }          // Okay, launch apparently worked. Change our mDonePump listener. diff --git a/indra/llcommon/llleap.h b/indra/llcommon/llleap.h index e33f25e530..8aac8a64c5 100644 --- a/indra/llcommon/llleap.h +++ b/indra/llcommon/llleap.h @@ -13,9 +13,9 @@  #define LL_LLLEAP_H  #include "llinstancetracker.h" +#include "llexception.h"  #include <string>  #include <vector> -#include <stdexcept>  /**   * LLSD Event API Plugin class. Because instances are managed by @@ -67,9 +67,9 @@ public:       * string(s) passed to create() might come from an external source. This       * way the caller can catch LLLeap::Error and try to recover.       */ -    struct Error: public std::runtime_error +    struct Error: public LLException      { -        Error(const std::string& what): std::runtime_error(what) {} +        Error(const std::string& what): LLException(what) {}      };      virtual ~LLLeap(); diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index 0fb257aab1..575edddc43 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -110,11 +110,15 @@ template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address)  	#if defined(LL_WINDOWS)  		return _aligned_malloc(size, align);  	#else +        char* aligned = NULL;  		void* mem = malloc( size + (align - 1) + sizeof(void*) ); -		char* aligned = ((char*)mem) + sizeof(void*); -		aligned += align - ((uintptr_t)aligned & (align - 1)); +        if (mem) +        { +            aligned = ((char*)mem) + sizeof(void*); +            aligned += align - ((uintptr_t)aligned & (align - 1)); -		((void**)aligned)[-1] = mem; +            ((void**)aligned)[-1] = mem; +        }  		return aligned;  	#endif  	} diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 44f56daf2d..8c321d06b9 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -34,6 +34,7 @@  #include "llapr.h"  #include "apr_signal.h"  #include "llevents.h" +#include "llexception.h"  #include <boost/foreach.hpp>  #include <boost/bind.hpp> @@ -472,9 +473,9 @@ private:  *****************************************************************************/  /// Need an exception to avoid constructing an invalid LLProcess object, but  /// internal use only -struct LLProcessError: public std::runtime_error +struct LLProcessError: public LLException  { -	LLProcessError(const std::string& msg): std::runtime_error(msg) {} +	LLProcessError(const std::string& msg): LLException(msg) {}  };  LLProcessPtr LLProcess::create(const LLSDOrParams& params) @@ -530,8 +531,8 @@ LLProcess::LLProcess(const LLSDOrParams& params):  	if (! params.validateBlock(true))  	{ -		throw LLProcessError(STRINGIZE("not launched: failed parameter validation\n" -									   << LLSDNotationStreamer(params))); +		LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n" +										 << LLSDNotationStreamer(params))));  	}  	mPostend = params.postend; @@ -596,10 +597,10 @@ LLProcess::LLProcess(const LLSDOrParams& params):  		}  		else  		{ -			throw LLProcessError(STRINGIZE("For " << params.executable() -										   << ": unsupported FileParam for " << which -										   << ": type='" << fparam.type() -										   << "', name='" << fparam.name() << "'")); +			LLTHROW(LLProcessError(STRINGIZE("For " << params.executable() +											 << ": unsupported FileParam for " << which +											 << ": type='" << fparam.type() +											 << "', name='" << fparam.name() << "'")));  		}  	}  	// By default, pass APR_NO_PIPE for unspecified slots. @@ -678,7 +679,7 @@ LLProcess::LLProcess(const LLSDOrParams& params):  	if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr,  										   gAPRPoolp)))  	{ -		throw LLProcessError(STRINGIZE(params << " failed")); +		LLTHROW(LLProcessError(STRINGIZE(params << " failed")));  	}  	// arrange to call status_callback() @@ -1063,7 +1064,7 @@ PIPETYPE& LLProcess::getPipe(FILESLOT slot)  	PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);  	if (! wp)  	{ -		throw NoPipe(error); +		LLTHROW(NoPipe(error));  	}  	return *wp;  } diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 43ccadc412..bfac4567a5 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -30,13 +30,13 @@  #include "llinitparam.h"  #include "llsdparam.h"  #include "llwin32headerslean.h" +#include "llexception.h"  #include "apr_thread_proc.h"  #include <boost/shared_ptr.hpp>  #include <boost/ptr_container/ptr_vector.hpp>  #include <boost/optional.hpp>  #include <boost/noncopyable.hpp>  #include <iosfwd>                   // std::ostream -#include <stdexcept>  #if LL_WINDOWS  #include "llwin32headerslean.h"	// for HANDLE @@ -479,9 +479,9 @@ public:  	/// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to  	/// create a pipe at the corresponding FILESLOT. -	struct NoPipe: public std::runtime_error +	struct NoPipe: public LLException  	{ -		NoPipe(const std::string& what): std::runtime_error(what) {} +		NoPipe(const std::string& what): LLException(what) {}  	};  	/** diff --git a/indra/llcommon/llthreadsafequeue.cpp b/indra/llcommon/llthreadsafequeue.cpp index 185f0d63fb..491f920c0f 100644 --- a/indra/llcommon/llthreadsafequeue.cpp +++ b/indra/llcommon/llthreadsafequeue.cpp @@ -27,6 +27,7 @@  #include <apr_pools.h>  #include <apr_queue.h>  #include "llthreadsafequeue.h" +#include "llexception.h" @@ -41,13 +42,13 @@ LLThreadSafeQueueImplementation::LLThreadSafeQueueImplementation(apr_pool_t * po  {  	if(mOwnsPool) {  		apr_status_t status = apr_pool_create(&mPool, 0); -		if(status != APR_SUCCESS) throw LLThreadSafeQueueError("failed to allocate pool"); +		if(status != APR_SUCCESS) LLTHROW(LLThreadSafeQueueError("failed to allocate pool"));  	} else {  		; // No op.  	}  	apr_status_t status = apr_queue_create(&mQueue, capacity, mPool); -	if(status != APR_SUCCESS) throw LLThreadSafeQueueError("failed to allocate queue"); +	if(status != APR_SUCCESS) LLTHROW(LLThreadSafeQueueError("failed to allocate queue"));  } @@ -68,9 +69,9 @@ void LLThreadSafeQueueImplementation::pushFront(void * element)  	apr_status_t status = apr_queue_push(mQueue, element);  	if(status == APR_EINTR) { -		throw LLThreadSafeQueueInterrupt(); +		LLTHROW(LLThreadSafeQueueInterrupt());  	} else if(status != APR_SUCCESS) { -		throw LLThreadSafeQueueError("push failed"); +		LLTHROW(LLThreadSafeQueueError("push failed"));  	} else {  		; // Success.  	} @@ -88,9 +89,9 @@ void * LLThreadSafeQueueImplementation::popBack(void)  	apr_status_t status = apr_queue_pop(mQueue, &element);  	if(status == APR_EINTR) { -		throw LLThreadSafeQueueInterrupt(); +		LLTHROW(LLThreadSafeQueueInterrupt());  	} else if(status != APR_SUCCESS) { -		throw LLThreadSafeQueueError("pop failed"); +		LLTHROW(LLThreadSafeQueueError("pop failed"));  	} else {  		return element;  	} diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h index 58cac38769..45289ef0b4 100644 --- a/indra/llcommon/llthreadsafequeue.h +++ b/indra/llcommon/llthreadsafequeue.h @@ -27,9 +27,8 @@  #ifndef LL_LLTHREADSAFEQUEUE_H  #define LL_LLTHREADSAFEQUEUE_H - +#include "llexception.h"  #include <string> -#include <stdexcept>  struct apr_pool_t; // From apr_pools.h @@ -40,11 +39,11 @@ class LLThreadSafeQueueImplementation; // See below.  // A general queue exception.  //  class LL_COMMON_API LLThreadSafeQueueError: -public std::runtime_error +	public LLException  {  public:  	LLThreadSafeQueueError(std::string const & message): -	std::runtime_error(message) +		LLException(message)  	{  		; // No op.  	} diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index e3671047b4..d4af2c6b01 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -83,7 +83,7 @@ unsigned int decode( char const * fiveChars ) throw( bad_input_data )  unsigned int ret = 0;  for( int ix = 0; ix < 5; ++ix ) {  char * s = strchr( encodeTable, fiveChars[ ix ] ); -if( s == 0 ) throw bad_input_data(); +if( s == 0 ) LLTHROW(bad_input_data());  ret = ret * 85 + (s-encodeTable);  }  return ret; diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index f51279e817..8bace8ac41 100644 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -237,8 +237,21 @@ namespace tut  	void ErrorTestObject::test<4>()  		// file abbreviation  	{ -		std::string thisFile = __FILE__; -		std::string abbreviateFile = LLError::abbreviateFile(thisFile); +		std::string prev, abbreviateFile = __FILE__; +        do +        { +            prev = abbreviateFile; +            abbreviateFile = LLError::abbreviateFile(abbreviateFile); +            // __FILE__ is assumed to end with +            // indra/llcommon/tests/llerror_test.cpp. This test used to call +            // abbreviateFile() exactly once, then check below whether it +            // still contained the string 'indra'. That fails if the FIRST +            // part of the pathname also contains indra! Certain developer +            // machine images put local directory trees under +            // /ngi-persist/indra, which is where we observe the problem. So +            // now, keep calling abbreviateFile() until it returns its +            // argument unchanged, THEN check. +        } while (abbreviateFile != prev);  		ensure_ends_with("file name abbreviation",  			abbreviateFile, diff --git a/indra/llcommon/tests/llexception_test.cpp b/indra/llcommon/tests/llexception_test.cpp new file mode 100644 index 0000000000..6bee1943c2 --- /dev/null +++ b/indra/llcommon/tests/llexception_test.cpp @@ -0,0 +1,308 @@ +/** + * @file   llexception_test.cpp + * @author Nat Goodspeed + * @date   2016-08-12 + * @brief  Tests for throwing exceptions. + * + * This isn't a regression test: it doesn't need to be run every build, which + * is why the corresponding line in llcommon/CMakeLists.txt is commented out. + * Rather it's a head-to-head test of what kind of exception information we + * can collect from various combinations of exception base classes, type of + * throw verb and sequences of catch clauses. + * + * This "test" makes no ensure() calls: its output goes to stdout for human + * examination. + * + * As of 2016-08-12 with Boost 1.57, we come to the following conclusions. + * These should probably be re-examined from time to time as we update Boost. + * + * - It is indisputably beneficial to use BOOST_THROW_EXCEPTION() rather than + *   plain throw. The macro annotates the exception object with the filename, + *   line number and function name from which the exception was thrown. + * + * - That being the case, deriving only from boost::exception isn't an option. + *   Every exception object passed to BOOST_THROW_EXCEPTION() must be derived + *   directly or indirectly from std::exception. The only question is whether + *   to also derive from boost::exception. We decided to derive LLException + *   from both, as it makes message output slightly cleaner, but this is a + *   trivial reason: if a strong reason emerges to prefer single inheritance, + *   dropping the boost::exception base class shouldn't be a problem. + * + * - (As you will have guessed, ridiculous things like a char* or int or a + *   class derived from neither boost::exception nor std::exception can only + *   be caught by that specific type or (...), and + *   boost::current_exception_diagnostic_information() simply throws up its + *   hands and confesses utter ignorance. Stay away from such nonsense.) + * + * - But if you derive from std::exception, to nat's surprise, + *   boost::current_exception_diagnostic_information() gives as much + *   information about exceptions in a catch (...) clause as you can get from + *   a specific catch (const std::exception&) clause, notably the concrete + *   exception class and the what() string. So instead of a sequence like + * + *   try { ... } + *   catch (const boost::exception& e) { ... boost-flavored logging ... } + *   catch (const std::exception& e)   { ... std::exception logging ... } + *   catch (...)                       { ... generic logging ... } + * + *   we should be able to get away with only a catch (...) clause that logs + *   boost::current_exception_diagnostic_information(). + * + * - Going further: boost::current_exception_diagnostic_information() provides + *   just as much information even within a std::set_terminate() handler. So + *   it might not even be strictly necessary to include a catch (...) clause + *   since the viewer does use std::set_terminate(). + * + * - (We might consider adding a catch (int) clause because Kakadu internally + *   throws ints, and who knows if one of those might leak out. If it does, + *   boost::current_exception_diagnostic_information() can do nothing with it. + *   A catch (int) clause could at least log the value and rethrow.) + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llexception.h" +// STL headers +// std headers +#include <typeinfo> +// external library headers +#include <boost/throw_exception.hpp> +// other Linden headers +#include "../test/lltut.h" + +// helper for display output +// usage: std::cout << center(some string value, fill char, width) << std::endl; +// (assumes it's the only thing on that particular line) +struct center +{ +    center(const std::string& label, char fill, std::size_t width): +        mLabel(label), +        mFill(fill), +        mWidth(width) +    {} + +    // Use friend declaration not because we need to grant access, but because +    // it lets us declare a free operator like a member function. +    friend std::ostream& operator<<(std::ostream& out, const center& ctr) +    { +        std::size_t padded = ctr.mLabel.length() + 2; +        std::size_t left  = (ctr.mWidth - padded) / 2; +        std::size_t right = ctr.mWidth - left - padded; +        return out << std::string(left, ctr.mFill) << ' ' << ctr.mLabel << ' ' +                   << std::string(right, ctr.mFill); +    } + +    std::string mLabel; +    char mFill; +    std::size_t mWidth; +}; + +/***************************************************************************** +*   Four kinds of exceptions: derived from boost::exception, from +*   std::exception, from both, from neither +*****************************************************************************/ +// Interestingly, we can't use this variant with BOOST_THROW_EXCEPTION() +// (which we want) -- we reach a failure topped by this comment: +//  //All boost exceptions are required to derive from std::exception, +//  //to ensure compatibility with BOOST_NO_EXCEPTIONS. +struct FromBoost: public boost::exception +{ +    FromBoost(const std::string& what): mWhat(what) {} +    ~FromBoost() throw() {} +    std::string what() const { return mWhat; } +    std::string mWhat; +}; + +struct FromStd: public std::runtime_error +{ +    FromStd(const std::string& what): std::runtime_error(what) {} +}; + +struct FromBoth: public boost::exception, public std::runtime_error +{ +    FromBoth(const std::string& what): std::runtime_error(what) {} +}; + +// Same deal with FromNeither: can't use with BOOST_THROW_EXCEPTION(). +struct FromNeither +{ +    FromNeither(const std::string& what): mWhat(what) {} +    std::string what() const { return mWhat; } +    std::string mWhat; +}; + +/***************************************************************************** +*   Two kinds of throws: plain throw and BOOST_THROW_EXCEPTION() +*****************************************************************************/ +template <typename EXC> +void plain_throw(const std::string& what) +{ +    throw EXC(what); +} + +template <typename EXC> +void boost_throw(const std::string& what) +{ +    BOOST_THROW_EXCEPTION(EXC(what)); +} + +// Okay, for completeness, functions that throw non-class values. We wouldn't +// even deign to consider these if we hadn't found examples in our own source +// code! (Note that Kakadu's internal exception support is still based on +// throwing ints.) +void throw_char_ptr(const std::string& what) +{ +    throw what.c_str(); // umm... +} + +void throw_int(const std::string& what) +{ +    throw int(what.length()); +} + +/***************************************************************************** +*   Three sequences of catch clauses: +*   boost::exception then ..., +*   std::exception then ..., +*   or just ... +*****************************************************************************/ +void catch_boost_dotdotdot(void (*thrower)(const std::string&), const std::string& what) +{ +    try +    { +        thrower(what); +    } +    catch (const boost::exception& e) +    { +        std::cout << "catch (const boost::exception& e)" << std::endl; +        std::cout << "e is " << typeid(e).name() << std::endl; +        std::cout << "boost::diagnostic_information(e):\n'" +                  << boost::diagnostic_information(e) << "'" << std::endl; +        // no way to report e.what() +    } +    catch (...) +    { +        std::cout << "catch (...)" << std::endl; +        std::cout << "boost::current_exception_diagnostic_information():\n'" +                  << boost::current_exception_diagnostic_information() << "'" +                  << std::endl; +    } +} + +void catch_std_dotdotdot(void (*thrower)(const std::string&), const std::string& what) +{ +    try +    { +        thrower(what); +    } +    catch (const std::exception& e) +    { +        std::cout << "catch (const std::exception& e)" << std::endl; +        std::cout << "e is " << typeid(e).name() << std::endl; +        std::cout << "boost::diagnostic_information(e):\n'" +                  << boost::diagnostic_information(e) << "'" << std::endl; +        std::cout << "e.what: '" +                  << e.what() << "'" << std::endl; +    } +    catch (...) +    { +        std::cout << "catch (...)" << std::endl; +        std::cout << "boost::current_exception_diagnostic_information():\n'" +                  << boost::current_exception_diagnostic_information() << "'" +                  << std::endl; +    } +} + +void catch_dotdotdot(void (*thrower)(const std::string&), const std::string& what) +{ +    try +    { +        thrower(what); +    } +    catch (...) +    { +        std::cout << "catch (...)" << std::endl; +        std::cout << "boost::current_exception_diagnostic_information():\n'" +                  << boost::current_exception_diagnostic_information() << "'" +                  << std::endl; +    } +} + +/***************************************************************************** +*   Try a particular kind of throw against each of three catch sequences +*****************************************************************************/ +void catch_several(void (*thrower)(const std::string&), const std::string& what) +{ +    std::cout << std::string(20, '-') << "catch_boost_dotdotdot(" << what << ")" << std::endl; +    catch_boost_dotdotdot(thrower, "catch_boost_dotdotdot(" + what + ")"); + +    std::cout << std::string(20, '-') << "catch_std_dotdotdot(" << what << ")" << std::endl; +    catch_std_dotdotdot(thrower, "catch_std_dotdotdot(" + what + ")"); + +    std::cout << std::string(20, '-') << "catch_dotdotdot(" << what << ")" << std::endl; +    catch_dotdotdot(thrower, "catch_dotdotdot(" + what + ")"); +} + +/***************************************************************************** +*   For a particular kind of exception, try both kinds of throw against all +*   three catch sequences +*****************************************************************************/ +template <typename EXC> +void catch_both_several(const std::string& what) +{ +    std::cout << std::string(20, '*') << "plain_throw<" << what << ">" << std::endl; +    catch_several(plain_throw<EXC>, "plain_throw<" + what + ">"); + +    std::cout << std::string(20, '*') << "boost_throw<" << what << ">" << std::endl; +    catch_several(boost_throw<EXC>, "boost_throw<" + what + ">"); +} + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llexception_data +    { +    }; +    typedef test_group<llexception_data> llexception_group; +    typedef llexception_group::object object; +    llexception_group llexceptiongrp("llexception"); + +    template<> template<> +    void object::test<1>() +    { +        set_test_name("throwing exceptions"); + +        // For each kind of exception, try both kinds of throw against all +        // three catch sequences +        std::size_t margin = 72; +        std::cout << center("FromStd", '=', margin) << std::endl; +        catch_both_several<FromStd>("FromStd"); + +        std::cout << center("FromBoth", '=', margin) << std::endl; +        catch_both_several<FromBoth>("FromBoth"); + +        std::cout << center("FromBoost", '=', margin) << std::endl; +        // can't throw with BOOST_THROW_EXCEPTION(), just use catch_several() +        catch_several(plain_throw<FromBoost>, "plain_throw<FromBoost>"); + +        std::cout << center("FromNeither", '=', margin) << std::endl; +        // can't throw this with BOOST_THROW_EXCEPTION() either +        catch_several(plain_throw<FromNeither>, "plain_throw<FromNeither>"); + +        std::cout << center("const char*", '=', margin) << std::endl; +        // We don't expect BOOST_THROW_EXCEPTION() to throw anything so daft +        // as a const char* or an int, so don't bother with +        // catch_both_several() -- just catch_several(). +        catch_several(throw_char_ptr, "throw_char_ptr"); + +        std::cout << center("int", '=', margin) << std::endl; +        catch_several(throw_int, "throw_int"); +    } +} // namespace tut diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 785197ba11..9a4bbbd630 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -35,13 +35,13 @@  #include <tut/tut.hpp>  #include "llerrorcontrol.h" +#include "llexception.h"  #include "stringize.h"  #include <boost/bind.hpp>  #include <boost/noncopyable.hpp>  #include <boost/shared_ptr.hpp>  #include <list>  #include <string> -#include <stdexcept>  // statically reference the function in test.cpp... it's short, we could  // replicate, but better to reuse @@ -67,9 +67,9 @@ struct WrapLLErrs          LLError::restoreSettings(mPriorErrorSettings);      } -    struct FatalException: public std::runtime_error +    struct FatalException: public LLException      { -        FatalException(const std::string& what): std::runtime_error(what) {} +        FatalException(const std::string& what): LLException(what) {}      };      void operator()(const std::string& message) @@ -78,7 +78,7 @@ struct WrapLLErrs          error = message;          // Also throw an appropriate exception since calling code is likely to          // assume that control won't continue beyond LL_ERRS. -        throw FatalException(message); +        LLTHROW(FatalException(message));      }      std::string error; | 
