summaryrefslogtreecommitdiff
path: root/indra/llcommon/tests/llexception_test.cpp
blob: 8ddf636cd1954778f69ad978b0bc5be968c55ca0 (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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
/**
 * @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");
    }

    template<> template<>
    void object::test<2>()
    {
        set_test_name("reporting exceptions");

        try
        {
            LLTHROW(LLException("badness"));
        }
        catch (...)
        {
            LOG_UNHANDLED_EXCEPTION("llexception test<2>()");
        }
    }
} // namespace tut