Better context information handling for exceptions.
[alexxy/gromacs.git] / src / gromacs / utility / exceptions.cpp
1 /*
2  *
3  *                This source code is part of
4  *
5  *                 G   R   O   M   A   C   S
6  *
7  *          GROningen MAchine for Chemical Simulations
8  *
9  * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
10  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
11  * Copyright (c) 2001-2009, The GROMACS development team,
12  * check out http://www.gromacs.org for more information.
13  *
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License
16  * as published by the Free Software Foundation; either version 2
17  * of the License, or (at your option) any later version.
18  *
19  * If you want to redistribute modifications, please consider that
20  * scientific software is very special. Version control is crucial -
21  * bugs must be traceable. We will be happy to consider code for
22  * inclusion in the official distribution, but derived work must not
23  * be called official GROMACS. Details are found in the README & COPYING
24  * files - if they are missing, get the official version at www.gromacs.org.
25  *
26  * To help us fund GROMACS development, we humbly ask that you cite
27  * the papers on the package - you can find them in the top README file.
28  *
29  * For more info, check our website at http://www.gromacs.org
30  */
31 /*! \internal \file
32  * \brief
33  * Implements classes and functions in exceptions.h.
34  *
35  * \author Teemu Murtola <teemu.murtola@cbr.su.se>
36  * \ingroup module_utility
37  */
38 #include "exceptions.h"
39
40 #include <boost/exception/get_error_info.hpp>
41 #include <boost/shared_ptr.hpp>
42
43 #include "gromacs/utility/errorcodes.h"
44 #include "gromacs/utility/gmxassert.h"
45
46 #include "errorformat.h"
47
48 namespace gmx
49 {
50
51 namespace
52 {
53
54 /********************************************************************
55  * ErrorMessage
56  */
57
58 class ErrorMessage
59 {
60     public:
61         /*! \brief
62          * Creates an error message object with the specified text.
63          *
64          * \param[in] text  Text for the message.
65          */
66         explicit ErrorMessage(const std::string &text);
67
68         //! Whether this object is a context string.
69         bool isContext() const { return child_; }
70         //! Returns the text for this object.
71         const std::string &text() const { return text_; }
72         /*! \brief
73          * Returns the child object for a context object.
74          *
75          * Must not be called if isContext() returns false.
76          */
77         const ErrorMessage &child() const
78         {
79             GMX_ASSERT(isContext(),
80                        "Attempting to access nonexistent message object");
81             return *child_;
82         }
83
84         /*! \brief
85          * Creates a new message object with context prepended.
86          *
87          * \param[in] context  Context string to add.
88          * \returns   New error message object that has \p context as its text
89          *      and \c this as its child.
90          * \throws    std::bad_alloc if out of memory.
91          */
92         ErrorMessage prependContext(const std::string &context) const;
93
94     private:
95         std::string                     text_;
96         boost::shared_ptr<ErrorMessage> child_;
97 };
98
99 /*! \internal \brief
100  * Stores a reason or the top-most context string of an exception.
101  *
102  * \ingroup module_utility
103  */
104 typedef boost::error_info<struct errinfo_message_, ErrorMessage>
105         errinfo_message;
106
107 ErrorMessage::ErrorMessage(const std::string &text)
108     : text_(text)
109 {
110     size_t length = text_.find_last_not_of(" \n");
111     if (length == std::string::npos)
112     {
113         length = text_.length() - 1;
114     }
115     text_.resize(length + 1);
116 }
117
118 ErrorMessage
119 ErrorMessage::prependContext(const std::string &context) const
120 {
121     ErrorMessage newMessage(context);
122     newMessage.child_.reset(new ErrorMessage(*this));
123     return newMessage;
124 }
125
126 /*! \brief
127  * Stores list of nested exceptions for Gromacs exceptions.
128  *
129  * \ingroup module_utility
130  */
131 typedef boost::error_info<struct errinfo_message_, internal::NestedExceptionList>
132         errinfo_nested_exceptions;
133
134 } // namespace
135
136 /********************************************************************
137  * GromacsException
138  */
139
140 GromacsException::GromacsException(const ExceptionInitializer &details)
141 {
142     *this << errinfo_message(ErrorMessage(details.reason_));
143     if (details.hasNestedExceptions())
144     {
145         *this << errinfo_nested_exceptions(details.nested_);
146     }
147 }
148
149 const char *GromacsException::what() const throw()
150 {
151     const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*this);
152     while (msg != NULL && msg->isContext())
153     {
154         msg = &msg->child();
155     }
156     return msg != NULL ? msg->text().c_str() : "No reason provided";
157 }
158
159 void GromacsException::prependContext(const std::string &context)
160 {
161     const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*this);
162     GMX_RELEASE_ASSERT(msg != NULL, "Message should always be set");
163     *this << errinfo_message(msg->prependContext(context));
164 }
165
166 /********************************************************************
167  * Derived exception classes
168  */
169
170 int FileIOError::errorCode() const
171 {
172     return eeFileIO;
173 }
174
175 int InvalidInputError::errorCode() const
176 {
177     return eeInvalidInput;
178 }
179
180 int InconsistentInputError::errorCode() const
181 {
182     return eeInconsistentInput;
183 }
184
185 int SimulationInstabilityError::errorCode() const
186 {
187     return eeInstability;
188 }
189
190 int InternalError::errorCode() const
191 {
192     return eeInternalError;
193 }
194
195 int APIError::errorCode() const
196 {
197     return eeAPIError;
198 }
199
200 int NotImplementedError::errorCode() const
201 {
202     return eeNotImplemented;
203 }
204
205
206 /********************************************************************
207  * Global functions
208  */
209
210 namespace
211 {
212
213 void printExceptionMessage(FILE *fp, const std::exception &ex, int indent)
214 {
215     const boost::exception *boostEx = dynamic_cast<const boost::exception *>(&ex);
216     if (boostEx != NULL)
217     {
218         // TODO: Remove duplicate context if present in multiple nested exceptions.
219         const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*boostEx);
220         if (msg != NULL)
221         {
222             while (msg != NULL && msg->isContext())
223             {
224                 internal::printFatalErrorMessageLine(fp, msg->text().c_str(), indent*2);
225                 ++indent;
226                 msg = &msg->child();
227             }
228             if (msg != NULL && !msg->text().empty())
229             {
230                 internal::printFatalErrorMessageLine(fp, msg->text().c_str(), indent*2);
231             }
232         }
233         else
234         {
235             internal::printFatalErrorMessageLine(fp, ex.what(), 0);
236         }
237
238         // TODO: Treat also boost::nested_exception (not currently used, though)
239
240         const internal::NestedExceptionList *nested
241             = boost::get_error_info<errinfo_nested_exceptions>(*boostEx);
242         if (nested != NULL)
243         {
244             ++indent;
245             internal::NestedExceptionList::const_iterator ni;
246             for (ni = nested->begin(); ni != nested->end(); ++ni)
247             {
248                 try
249                 {
250                     rethrow_exception(*ni);
251                 }
252                 catch (const std::exception &nestedEx)
253                 {
254                     printExceptionMessage(fp, nestedEx, indent);
255                 }
256             }
257         }
258
259         // TODO: Treat errno information in boost exceptions
260     }
261     else
262     {
263         internal::printFatalErrorMessageLine(fp, ex.what(), 0);
264     }
265 }
266
267 } // namespace
268
269 void printFatalErrorMessage(FILE *fp, const std::exception &ex)
270 {
271     const char *title = "Unknown exception";
272     const GromacsException *gmxEx = dynamic_cast<const GromacsException *>(&ex);
273     // TODO: Also treat common standard exceptions
274     if (gmxEx != NULL)
275     {
276         title = getErrorCodeString(gmxEx->errorCode());
277     }
278     else if (dynamic_cast<const std::bad_alloc *>(&ex) != NULL)
279     {
280         title = "Memory allocation failed";
281     }
282     // We can't call get_error_info directly on ex since our internal boost
283     // needs to be compiled with BOOST_NO_RTTI. So we do the dynamic_cast
284     // here instead.
285     const char *const *funcPtr = NULL;
286     const char *const *filePtr = NULL;
287     const int         *linePtr = NULL;
288     const boost::exception *boostEx = dynamic_cast<const boost::exception *>(&ex);
289     if (boostEx != NULL)
290     {
291         funcPtr = boost::get_error_info<boost::throw_function>(*boostEx);
292         filePtr = boost::get_error_info<boost::throw_file>(*boostEx);
293         linePtr = boost::get_error_info<boost::throw_line>(*boostEx);
294     }
295     internal::printFatalErrorHeader(fp, title,
296                                     funcPtr != NULL ? *funcPtr : NULL,
297                                     filePtr != NULL ? *filePtr : NULL,
298                                     linePtr != NULL ? *linePtr : 0);
299     printExceptionMessage(fp, ex, 0);
300     internal::printFatalErrorFooter(fp);
301 }
302
303 } // namespace gmx