LCOV - code coverage report
Current view: top level - libexcept - exception.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 58 58 100.0 %
Date: 2022-06-30 20:42:18 Functions: 17 17 100.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13