Line data Source code
1 : // Copyright (c) 2011-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : // self
21 : //
22 : #include "./exception.h"
23 :
24 : #include "./demangle.h"
25 :
26 :
27 : // C++ includes
28 : //
29 : #include <iostream>
30 : #include <memory>
31 : #include <vector>
32 :
33 :
34 : // C lib includes
35 : //
36 : #include <execinfo.h>
37 : #include <link.h>
38 : #include <unistd.h>
39 :
40 :
41 : /** \file
42 : * \brief Implementation of the libexcept classes.
43 : *
44 : * This file includes the library implementation. Especially, it has the
45 : * code that generates a stack trace and converts the results to a C++
46 : * vector of strings.
47 : */
48 :
49 :
50 : /** \mainpage
51 : *
52 : * The libexcept library offers us a way to automatically get a stack trace
53 : * every time an exception occurs.
54 : *
55 : * \section introduction Introduction
56 : *
57 : * The Snap! C++ environment uses a lot of exceptions, but nearly only when
58 : * the exception can't be avoided (i.e. more or less a fatal error in the
59 : * current situation.) Therefore, having a way to immediately discover where
60 : * the exception occurred by using the libexcept exception classes gives you
61 : * a way to immediately find out which function raised the exception nearly
62 : * 99% of the time including in the runtime environment of Snap! C++ and
63 : * any other project using the libexcept library.
64 : *
65 : * \section classes Classes to Derive From
66 : *
67 : * This library gives you two exception classes to derive from:
68 : *
69 : * \subsection logic_exception libexcept::logic_exception_t
70 : *
71 : * Used to raise an exception about logic; although this is often an
72 : * "emergency" type of error (even worse than a fatal error), we have
73 : * a definitions for it because we raise many logic errors.
74 : *
75 : * Example of a logic error:
76 : *
77 : * A function is expected to receive two parameters, say both are enumerations.
78 : * When the first enumeration is set to `FOO` then the second is expected
79 : * to be one of `BAR` or `BAZ`. If the second is set to `NOT_SO_GOOD` instead,
80 : * then the function raises a logic error because the programmer made a
81 : * mistake and the problem can be fixed by fixing the code (i.e. once the
82 : * code is fixed, you should then never see the error again.)
83 : *
84 : * \subsection out_of_range_exception libexcept::out_of_range_t
85 : *
86 : * This is an extension of the std::logic_error which is expected to be used
87 : * whenever an out of range error occurs. This is mainly for when an index
88 : * is out of range when attempting to retrieve an item from an array or
89 : * similar concept.
90 : *
91 : * \subsection runtime_exception libexcept::exception_t
92 : *
93 : * Used for most of our exceptions. This is based on the
94 : * `std::runtime_error` base class.
95 : *
96 : * \section object_stack_trace Collect a Stack Trace Creating an Object
97 : *
98 : * You may also use the libexcept::exception_base_t class directly in your
99 : * class(es) in order to collect a stack trace at the time the class is
100 : * instantiated. The libexcept::exception_base_t::get_stack_trace()
101 : * gives you the results.
102 : *
103 : * \code
104 : * libexcept::exception_base_t stack_info;
105 : * libexcept::stack_trace_t stack_dump(stack_info.get_stack_trace());
106 : * ...here `stack_dump` is a list of strings, one string per frame...
107 : * \endcode
108 : *
109 : * By default we use STACK_TRACE_DEPTH as the number of stings to return
110 : * in the libexcept::stack_trace_t vector.
111 : *
112 : * \section in_place_stack_trace Collect a Stack Trace Anywhere
113 : *
114 : * Finally, you can directly call the libexcept::collect_stack_trace()
115 : * function since it is a global function. It gives you a vector of
116 : * strings representing the stack trace.
117 : *
118 : * We also offer the libexcept::collect_stack_trace_with_line_numbers()
119 : * for debug only. The exceptions do not make use of that function by
120 : * default because it is way too slow. It is useful to convert the
121 : * frame IP addresses to line numbers (assuming you still have debug
122 : * information in your text files and the software can find the text
123 : * file concerned by the problem.)
124 : *
125 : * \section thread_safety Thread Safety
126 : *
127 : * The library is thread safe. All the functions are reentrant except
128 : * the set_collect_stack(), which is still safe to use, only the
129 : * results may not always be exactly as expected.
130 : *
131 : * In terms of parallelism, the collect_stack_trace_with_line_numbers()
132 : * runs some console processes to collect the line number and demangle
133 : * the function names. This means that it could be really heavy if many
134 : * threads use that function often.
135 : */
136 :
137 :
138 :
139 : namespace libexcept
140 : {
141 :
142 :
143 :
144 :
145 : namespace
146 : {
147 :
148 :
149 : /** \brief Global flag to eventually prevent stack trace collection.
150 : *
151 : * Whenever a libexcept exception is raised, the stack gets collected.
152 : * This is very slow if you run a test which is to generate exceptions
153 : * over and over again, like 1,000,000 times in a tight loop.
154 : *
155 : * To make your tests faster we added a general flag which one can use
156 : * to collect or not collect the stack trace.
157 : *
158 : * At some point we may add an option to our command lines/configuration
159 : * files to tweak this flag on load. That way any of our daemon can
160 : * benefit by not having a stack trace in a production environment unless
161 : * requested. Rmember, though, that we use exceptions wisely so they really
162 : * only happens when something really bad is detected so it is fairly
163 : * safe to keep the collection of the stack trace turned on.
164 : */
165 : collect_stack_t g_collect_stack = collect_stack_t::COLLECT_STACK_YES;
166 :
167 :
168 :
169 :
170 : } // no name namespace
171 :
172 :
173 :
174 :
175 :
176 :
177 :
178 :
179 :
180 :
181 :
182 :
183 :
184 : /** \brief Tells you whether the general flag is true or false.
185 : *
186 : * This function gives you the current status of the collect stack flag.
187 : * If true, when exceptions will collect the stack at the time they
188 : * are emitted. This is very practical in debug since it gives you
189 : * additional information of where and possibly why an exception
190 : * occurred.
191 : */
192 32 : collect_stack_t get_collect_stack()
193 : {
194 32 : return g_collect_stack;
195 : }
196 :
197 :
198 : /** \brief Set a general flag on whether to collect stack traces or not.
199 : *
200 : * Because collecting the stack trace can be time consuming and once in
201 : * a while you may need the highest possible speed including libexcept
202 : * exceptions, we offer a flag to avoid all stack collection processing.
203 : *
204 : * We especially use this feature when running tests because we generate
205 : * the exceptions on purpose and do not want to get the stack trace which
206 : * is rather useless in this case. We do not yet have any other situations
207 : * where we do not want a stack trace.
208 : *
209 : * By default \p collect_stack is already true so you do not need to change
210 : * it on startup.
211 : *
212 : * \warning
213 : * The function itself is not multithread safe. It is unlikely to cause
214 : * any serious problems, though. Some threads may have or may be missing
215 : * the stack trace, that's all. If you never call this function, all threads
216 : * will always include the stack trace. Calling this function before you
217 : * create threads will resolve all possible issues (if you do not have
218 : * to dynamically change the flag.)
219 : *
220 : * \param[in] collect_stack Whether to collect the stack or not.
221 : */
222 33 : void set_collect_stack(collect_stack_t collect_stack)
223 : {
224 33 : g_collect_stack = collect_stack;
225 33 : }
226 :
227 :
228 :
229 :
230 :
231 : /** \var int libexcept::exception_base_t::STACK_TRACE_DEPTH
232 : * \brief Default depth of stack traces collected.
233 : *
234 : * This parameter defines the default number of lines returned by the
235 : * collect_stack_trace() function.
236 : *
237 : * All the functions that call the collect_stack_trace() have a
238 : * `stack_trace_depth` parameter you can use to change this value.
239 : *
240 : * Note that a value of 0 is valid as the stack trace depth. This
241 : * just means not even one line is going to be taken from the
242 : * stack.
243 : *
244 : * \attention
245 : * It is to be noted that since a few functions from the libexcept are
246 : * going to be included in your stack trace, using a very small depth
247 : * such as 1 or 2 is not going to be helpful at all. You would only
248 : * get data about the libexcept functions instead of the actual function
249 : * that generated the error.
250 : */
251 :
252 :
253 : /** \var stack_trace_t libexcept::exception_base_t::f_stack_trace
254 : * \brief The variable where the exception stack trace gets saved.
255 : *
256 : * This parameter holds the vector of strings representing the stack
257 : * trace at the time an exception was raised and an instance of
258 : * the exception_base_t class was created.
259 : */
260 :
261 :
262 : /** \var typedef std::vector<std::string> libexcept::exception_base_t::stack_trace_t
263 : * \brief The stack trace results.
264 : *
265 : * This typedef defines the type of the variables used to pass the stack
266 : * trace between functions. It is a simple vector a C++ strings.
267 : *
268 : * The first string (`trace[0]`) represents the current function. Note that
269 : * the collected functions will include all the functions, including the
270 : * exception_base_t::collect_stack_trace() and various calling functions
271 : * from the libexcept library. In most cases this means 2 or 3 lines at
272 : * the start of the stack trace vector are going to be about libexcept
273 : * functions and not the function where the exception was raised.
274 : */
275 :
276 :
277 : /** \brief Initialize this Snap! exception.
278 : *
279 : * Initialize the base exception class by generating the output of
280 : * a stack trace to a list of strings.
281 : *
282 : * \warning
283 : * At this time every single exception derived from exception_t generates
284 : * a stack trace. Note that in most cases, our philosophy is to generate
285 : * exceptions only in very exceptional cases and not on every single error
286 : * so the event should be rare in a normal run of our daemons.
287 : *
288 : * \param[in] stack_trace_depth The number of lines to grab in our
289 : * stack trace.
290 : *
291 : * \sa collect_stack_trace()
292 : */
293 23 : exception_base_t::exception_base_t(int const stack_trace_depth)
294 : {
295 23 : switch(get_collect_stack())
296 : {
297 7 : case collect_stack_t::COLLECT_STACK_NO:
298 7 : break;
299 :
300 8 : case collect_stack_t::COLLECT_STACK_YES:
301 8 : f_stack_trace = collect_stack_trace(stack_trace_depth);
302 8 : break;
303 :
304 8 : case collect_stack_t::COLLECT_STACK_COMPLETE:
305 8 : f_stack_trace = collect_stack_trace_with_line_numbers(stack_trace_depth);
306 8 : break;
307 :
308 : }
309 23 : }
310 :
311 :
312 : /** \fn exception_base_t::~exception_base_t()
313 : * \brief Destructor of the exception base class.
314 : *
315 : * This destructor is defined to ease derivation when some of the classes
316 : * have virtual functions.
317 : */
318 :
319 :
320 : /** \brief Retrieve the set of exception parameters.
321 : *
322 : * This function returns a reference to all the parameters found in this
323 : * exception. In most cases, exceptions do not have parameters, however, we
324 : * intend to change that as we continue work on our libraries.
325 : *
326 : * \return The reference to this exception parameters.
327 : */
328 2 : parameter_t const & exception_base_t::get_parameters() const
329 : {
330 2 : return f_parameters;
331 : }
332 :
333 :
334 : /** \brief Retrieve one of the exception parameters.
335 : *
336 : * Exceptions can be assigned parameters with the set_parameter() function.
337 : * For example, you could include a filename as a parameter. This is useful
338 : * when sending logs to a database. It can simplify your searches to know
339 : * exact parameters instead of trying to parse strings.
340 : *
341 : * \param[in] name The name of the parameter to search for.
342 : *
343 : * \return The value of the named parameter.
344 : */
345 6 : std::string exception_base_t::get_parameter(std::string const & name) const
346 : {
347 6 : auto const it(f_parameters.find(name));
348 6 : if(it == f_parameters.end())
349 : {
350 4 : return std::string();
351 : }
352 :
353 2 : return it->second;
354 : }
355 :
356 :
357 : /** \brief Set a parameter in this exception.
358 : *
359 : * You may add parameters to your exceptions simply by calling this function.
360 : *
361 : * Parameters are given a name. At the moment the name is not restricted,
362 : * however, if you want to make sure that it works in most places (i.e. in
363 : * the snaplogger), then you probably want to limit the name to this regex:
364 : *
365 : * \code
366 : * [A-Za-z_][A-Za-z_0-9]*
367 : * \endcode
368 : *
369 : * Parameter values are strings.
370 : *
371 : * This is an exception, so we do not raise an exception if the name of a
372 : * parameter is considered invalid. At the moment, an empty string is
373 : * considered invalid.
374 : *
375 : * \param[in] name The name of the parameter. It cannot be empty.
376 : * \param[in] value The value of this parameter.
377 : */
378 2 : void exception_base_t::set_parameter(std::string const & name, std::string const & value)
379 : {
380 2 : if(name.empty())
381 : {
382 1 : return;
383 : }
384 :
385 1 : f_parameters[name] = value;
386 : }
387 :
388 :
389 : /** \fn exception_base_t::get_stack_trace()
390 : * \brief Retrieve the stack trace.
391 : *
392 : * This function retreives a reference to the vector of strings representing
393 : * the stack trace at the time the exception was raised.
394 : */
395 :
396 :
397 :
398 : /** \brief Initialize an exception from a C++ string.
399 : *
400 : * This function initializes an exception settings its 'what' string to
401 : * the specified \p what parameter.
402 : *
403 : * \note
404 : * Logic exceptions are used for things that just should not ever happen.
405 : * More or less, a verification of your class contract that fails.
406 : *
407 : * \param[in] what The string used to initialize the exception what parameter.
408 : * \param[in] stack_trace_depth The number of lines to grab in our
409 : * stack trace.
410 : */
411 3 : logic_exception_t::logic_exception_t(
412 : std::string const & what
413 3 : , int const stack_trace_depth)
414 : : std::logic_error(what.c_str())
415 3 : , exception_base_t(stack_trace_depth)
416 : {
417 3 : }
418 :
419 :
420 : /** \fn logic_exception_t::~logic_exception_t()
421 : * \brief Destructor of the logic exception class.
422 : *
423 : * This destructor is defined to ease derivation when some of the classes
424 : * have virtual functions.
425 : */
426 :
427 :
428 : /** \brief Initialize an exception from a C string.
429 : *
430 : * This function initializes an exception settings its 'what' string to
431 : * the specified \p what parameter.
432 : *
433 : * \note
434 : * Logic exceptions are used for things that just should not ever happen.
435 : * More or less, a verification of your class contract that fails.
436 : *
437 : * \param[in] what The string used to initialize the exception what parameter.
438 : * \param[in] stack_trace_depth The number of lines to grab in our
439 : * stack trace.
440 : */
441 5 : logic_exception_t::logic_exception_t(
442 : char const * what
443 5 : , int const stack_trace_depth)
444 : : std::logic_error(what)
445 5 : , exception_base_t(stack_trace_depth)
446 : {
447 5 : }
448 :
449 :
450 : /** \brief Retrieve the `what` parameter as passed to the constructor.
451 : *
452 : * This function returns the `what` description of the exception when the
453 : * exception was initialized.
454 : *
455 : * \note
456 : * We have an overload because of the dual derivation.
457 : *
458 : * \return A pointer to the what string. Must be used before the exception
459 : * gets destructed.
460 : */
461 8 : char const * logic_exception_t::what() const throw()
462 : {
463 8 : return std::logic_error::what();
464 : }
465 :
466 :
467 :
468 :
469 :
470 :
471 :
472 :
473 :
474 :
475 :
476 : /** \brief Initialize an exception from a C++ string.
477 : *
478 : * This function initializes an exception settings its 'what' string to
479 : * the specified \p what parameter.
480 : *
481 : * \note
482 : * Out of Range exceptions are an extension of the Logic Exception used
483 : * whenever a container is being accessed with an index which is too large.
484 : * It may also be used whenever a number doesn't fit its destination variable
485 : * (i.e. trying to return 300 in an `int8_t`).
486 : *
487 : * \param[in] what The string used to initialize the exception what parameter.
488 : * \param[in] stack_trace_depth The number of lines to grab in our
489 : * stack trace.
490 : */
491 3 : out_of_range_t::out_of_range_t(
492 : std::string const & what
493 3 : , int const stack_trace_depth)
494 : : std::out_of_range(what.c_str())
495 3 : , exception_base_t(stack_trace_depth)
496 : {
497 3 : }
498 :
499 :
500 : /** \fn out_of_range_t::~out_of_range_t()
501 : * \brief Destructor of the out_of_range exception class.
502 : *
503 : * This destructor is defined to ease derivation when some of the classes
504 : * have virtual functions.
505 : */
506 :
507 :
508 : /** \brief Initialize an exception from a C string.
509 : *
510 : * This function initializes an exception settings its 'what' string to
511 : * the specified \p what parameter.
512 : *
513 : * \note
514 : * Logic exceptions are used for things that just should not ever happen.
515 : * More or less, a verification of your class contract that fails.
516 : *
517 : * \param[in] what The string used to initialize the exception what parameter.
518 : * \param[in] stack_trace_depth The number of lines to grab in our
519 : * stack trace.
520 : */
521 3 : out_of_range_t::out_of_range_t(
522 : char const * what
523 3 : , int const stack_trace_depth)
524 : : std::out_of_range(what)
525 3 : , exception_base_t(stack_trace_depth)
526 : {
527 3 : }
528 :
529 :
530 : /** \brief Retrieve the `what` parameter as passed to the constructor.
531 : *
532 : * This function returns the `what` description of the exception when the
533 : * exception was initialized.
534 : *
535 : * \note
536 : * We have an overload because of the dual derivation.
537 : *
538 : * \return A pointer to the what string. Must be used before the exception
539 : * gets destructed.
540 : */
541 6 : char const * out_of_range_t::what() const throw()
542 : {
543 6 : return std::out_of_range::what();
544 : }
545 :
546 :
547 :
548 :
549 :
550 :
551 :
552 :
553 :
554 :
555 :
556 :
557 : /** \brief Initialize an exception from a C++ string.
558 : *
559 : * This function initializes an exception settings its 'what' string to
560 : * the specified \p what parameter.
561 : *
562 : * \param[in] what The string used to initialize the exception what parameter.
563 : * \param[in] stack_trace_depth The number of lines to grab in our
564 : * stack trace.
565 : */
566 3 : exception_t::exception_t(
567 : std::string const & what
568 3 : , int const stack_trace_depth)
569 : : std::runtime_error(what.c_str())
570 3 : , exception_base_t(stack_trace_depth)
571 : {
572 3 : }
573 :
574 :
575 : /** \fn exception_t::~exception_t()
576 : * \brief Destructor of the exception class.
577 : *
578 : * This destructor is defined to ease derivation when some of the classes
579 : * have virtual functions.
580 : */
581 :
582 :
583 : /** \brief Initialize an exception from a C string.
584 : *
585 : * This function initializes an exception settings its 'what' string to
586 : * the specified \p what parameter.
587 : *
588 : * \param[in] what The string used to initialize the exception what parameter.
589 : * \param[in] stack_trace_depth The number of lines to grab in our
590 : * stack trace.
591 : */
592 6 : exception_t::exception_t(
593 : char const * what
594 6 : , int const stack_trace_depth)
595 : : std::runtime_error(what)
596 6 : , exception_base_t(stack_trace_depth)
597 : {
598 6 : }
599 :
600 :
601 : /** \brief Retrieve the `what` parameter as passed to the constructor.
602 : *
603 : * This function returns the `what` description of the exception when the
604 : * exception was initialized.
605 : *
606 : * \note
607 : * We have an overload because of the dual derivation.
608 : *
609 : * \return A pointer to the what string. Must be used before the exception
610 : * gets destructed.
611 : */
612 10 : char const * exception_t::what() const throw()
613 : {
614 10 : return std::runtime_error::what();
615 : }
616 :
617 :
618 6 : }
619 : // namespace libexcept
620 : // vim: ts=4 sw=4 et
|