summaryrefslogtreecommitdiff
path: root/indra/llcommon/llexception.h
blob: f58a553eb3d4212d5ed30302480ecaeed77ac02c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/**
 * @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 "always_return.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.
 */
#define LLTHROW(x)                                                      \
do {                                                                    \
    /* Capture the exception object 'x' by value. (Exceptions must */   \
    /* be copyable.) It might seem simpler to use                  */   \
    /* BOOST_THROW_EXCEPTION(annotate_exception_(x)) instead of    */   \
    /* three separate statements, but:                             */   \
    /* - We want to throw 'x' with its original type, not just a   */   \
    /*   reference to boost::exception.                            */   \
    /* - To return x's original type, annotate_exception_() would  */   \
    /*   have to be a template function.                           */   \
    /* - We want annotate_exception_() to be opaque.               */   \
    /* We also might consider embedding BOOST_THROW_EXCEPTION() in */   \
    /* our helper function, but we want the filename and line info */   \
    /* embedded by BOOST_THROW_EXCEPTION() to be the throw point   */   \
    /* rather than always indicating the same line in              */   \
    /* llexception.cpp.                                            */   \
    auto exc{x};                                                        \
    annotate_exception_(exc);                                           \
    BOOST_THROW_EXCEPTION(exc);                                         \
    /* Use the classic 'do { ... } while (0)' macro trick to wrap  */   \
    /* our multiple statements.                                    */   \
} while (0)
void annotate_exception_(boost::exception& exc);

/// 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&);

/*****************************************************************************
*   Structured Exception Handling
*****************************************************************************/
// this is used in platform-generic code -- define outside #if LL_WINDOWS
struct Windows_SEH_exception: public LLException
{
    Windows_SEH_exception(const std::string& what): LLException(what) {}
};

namespace LL
{
namespace seh
{

#if LL_WINDOWS //-------------------------------------------------------------

void fill_stacktrace(std::string& stacktrace, U32 code);

// wrapper around caller's U32 filter(U32 code, struct _EXCEPTION_POINTERS*)
// filter function: capture a stacktrace, if possible, before forwarding the
// call to the caller's filter() function
template <typename FILTER>
U32 filter_(std::string& stacktrace, FILTER&& filter,
            U32 code, struct _EXCEPTION_POINTERS* exptrs)
{
    // By the time the handler gets control, the stack has been unwound,
    // so report the stack trace now at filter() time.
    fill_stacktrace(stacktrace, code);
    return std::forward<FILTER>(filter)(code, exptrs);
}

template <typename TRYCODE, typename FILTER, typename HANDLER>
auto catcher_inner(std::string& stacktrace,
                   TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler)
{
    __try
    {
        return std::forward<TRYCODE>(trycode)();
    }
    __except (filter_(stacktrace,
                      std::forward<FILTER>(filter),
                      GetExceptionCode(), GetExceptionInformation()))
    {
        return always_return<decltype(trycode())>(
            std::forward<HANDLER>(handler), GetExceptionCode(), stacktrace);
    }
}

// triadic variant specifies try(), filter(U32, struct _EXCEPTION_POINTERS*),
// handler(U32, const std::string& stacktrace)
// stacktrace may or may not be available
template <typename TRYCODE, typename FILTER, typename HANDLER>
auto catcher(TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler)
{
    // Construct and destroy this stacktrace string in the outer function
    // because we can't do either in the function with __try/__except.
    std::string stacktrace;
    return catcher_inner(stacktrace,
                         std::forward<TRYCODE>(trycode),
                         std::forward<FILTER>(filter),
                         std::forward<HANDLER>(handler));
}

// common_filter() handles the typical case in which we want our handler
// clause to handle only Structured Exceptions rather than explicitly-thrown
// C++ exceptions
U32 common_filter(U32 code, struct _EXCEPTION_POINTERS*);

// dyadic variant specifies try(), handler(U32, stacktrace), assumes common_filter()
template <typename TRYCODE, typename HANDLER>
auto catcher(TRYCODE&& trycode, HANDLER&& handler)
{
    return catcher(std::forward<TRYCODE>(trycode),
                   common_filter,
                   std::forward<HANDLER>(handler));
}

// monadic variant specifies try(), assumes default filter and handler
template <typename TRYCODE>
auto catcher(TRYCODE&& trycode)
{
    return catcher(std::forward<TRYCODE>(trycode), rethrow);
}

[[noreturn]] void rethrow(U32 code, const std::string& stacktrace);

#else  // not LL_WINDOWS -----------------------------------------------------

template <typename TRYCODE, typename FILTER, typename HANDLER>
auto catcher(TRYCODE&& trycode, FILTER&&, HANDLER&&)
{
    return std::forward<TRYCODE>(trycode)();
}

template <typename TRYCODE, typename HANDLER>
auto catcher(TRYCODE&& trycode, HANDLER&&)
{
    return std::forward<TRYCODE>(trycode)();
}

template <typename TRYCODE>
auto catcher(TRYCODE&& trycode)
{
    return std::forward<TRYCODE>(trycode)();
}

#endif // not LL_WINDOWS -----------------------------------------------------

} // namespace LL::seh
} // namespace LL

#endif /* ! defined(LL_LLEXCEPTION_H) */