Treat exceptions better in interactive selections.
[alexxy/gromacs.git] / src / gromacs / utility / exceptions.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2011,2012,2013, by the GROMACS development team, led by
5  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6  * others, as listed in the AUTHORS file in the top-level source
7  * directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements classes and functions in exceptions.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_utility
41  */
42 #include "exceptions.h"
43
44 #include <cstring>
45
46 #include <new>
47 #include <stdexcept>
48 #include <typeinfo>
49
50 #include <boost/exception/get_error_info.hpp>
51 #include <boost/shared_ptr.hpp>
52
53 #include "gromacs/legacyheaders/thread_mpi/system_error.h"
54 #include "gromacs/utility/errorcodes.h"
55 #include "gromacs/utility/gmxassert.h"
56 #include "gromacs/utility/stringutil.h"
57
58 #include "errorformat.h"
59
60 namespace gmx
61 {
62
63 namespace
64 {
65
66 /********************************************************************
67  * ErrorMessage
68  */
69
70 class ErrorMessage
71 {
72     public:
73         /*! \brief
74          * Creates an error message object with the specified text.
75          *
76          * \param[in] text  Text for the message.
77          */
78         explicit ErrorMessage(const std::string &text);
79
80         //! Whether this object is a context string.
81         bool isContext() const { return static_cast<bool>(child_); }
82         //! Returns the text for this object.
83         const std::string &text() const { return text_; }
84         /*! \brief
85          * Returns the child object for a context object.
86          *
87          * Must not be called if isContext() returns false.
88          */
89         const ErrorMessage &child() const
90         {
91             GMX_ASSERT(isContext(),
92                        "Attempting to access nonexistent message object");
93             return *child_;
94         }
95
96         /*! \brief
97          * Creates a new message object with context prepended.
98          *
99          * \param[in] context  Context string to add.
100          * \returns   New error message object that has \p context as its text
101          *      and \c this as its child.
102          * \throws    std::bad_alloc if out of memory.
103          */
104         ErrorMessage prependContext(const std::string &context) const;
105
106     private:
107         std::string                     text_;
108         boost::shared_ptr<ErrorMessage> child_;
109 };
110
111 /*! \internal \brief
112  * Stores a reason or the top-most context string of an exception.
113  *
114  * \ingroup module_utility
115  */
116 typedef boost::error_info<struct errinfo_message_, ErrorMessage>
117     errinfo_message;
118
119 ErrorMessage::ErrorMessage(const std::string &text)
120     : text_(text)
121 {
122     size_t length = text_.find_last_not_of(" \n");
123     if (length == std::string::npos)
124     {
125         length = text_.length() - 1;
126     }
127     text_.resize(length + 1);
128 }
129
130 ErrorMessage
131 ErrorMessage::prependContext(const std::string &context) const
132 {
133     ErrorMessage newMessage(context);
134     newMessage.child_.reset(new ErrorMessage(*this));
135     return newMessage;
136 }
137
138 /*! \brief
139  * Stores list of nested exceptions for Gromacs exceptions.
140  *
141  * \ingroup module_utility
142  */
143 typedef boost::error_info<struct errinfo_message_, internal::NestedExceptionList>
144     errinfo_nested_exceptions;
145
146 }   // namespace
147
148 /********************************************************************
149  * GromacsException
150  */
151
152 GromacsException::GromacsException(const ExceptionInitializer &details)
153 {
154     *this << errinfo_message(ErrorMessage(details.reason_));
155     if (details.hasNestedExceptions())
156     {
157         *this << errinfo_nested_exceptions(details.nested_);
158     }
159 }
160
161 const char *GromacsException::what() const throw()
162 {
163     const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*this);
164     while (msg != NULL && msg->isContext())
165     {
166         msg = &msg->child();
167     }
168     return msg != NULL ? msg->text().c_str() : "No reason provided";
169 }
170
171 void GromacsException::prependContext(const std::string &context)
172 {
173     const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*this);
174     GMX_RELEASE_ASSERT(msg != NULL, "Message should always be set");
175     *this << errinfo_message(msg->prependContext(context));
176 }
177
178 /********************************************************************
179  * Derived exception classes
180  */
181
182 int FileIOError::errorCode() const
183 {
184     return eeFileIO;
185 }
186
187 int InvalidInputError::errorCode() const
188 {
189     return eeInvalidInput;
190 }
191
192 int InconsistentInputError::errorCode() const
193 {
194     return eeInconsistentInput;
195 }
196
197 int SimulationInstabilityError::errorCode() const
198 {
199     return eeInstability;
200 }
201
202 int InternalError::errorCode() const
203 {
204     return eeInternalError;
205 }
206
207 int APIError::errorCode() const
208 {
209     return eeAPIError;
210 }
211
212 int NotImplementedError::errorCode() const
213 {
214     return eeNotImplemented;
215 }
216
217
218 /********************************************************************
219  * Global functions
220  */
221
222 namespace
223 {
224
225 /*! \brief
226  * Abstracts actual output from the other logic in exception formatting.
227  *
228  * Object that implements this interface is passed to
229  * formatExceptionMessageInternal(), and is responsible for composing the
230  * output.  This allows using the same implementation of interpreting the
231  * exceptions while still supporting output to different formats (e.g., to a
232  * string or to stderr).
233  */
234 class MessageWriterInterface
235 {
236     public:
237         virtual ~MessageWriterInterface() {}
238
239         /*! \brief
240          * Writes a single line of text into the output.
241          *
242          * \param[in] text    Text to write on the line.
243          * \param[in] indent  Suggested number of spaces to indent the line.
244          */
245         virtual void writeLine(const char *text, int indent) = 0;
246         /*! \brief
247          * Writes information about a system error (errno-based).
248          *
249          * \param[in] errorNumber  errno value
250          * \param[in] funcName     Name of the system call (can be NULL).
251          * \param[in] indent       Suggested number of spaces to indent the output.
252          */
253         virtual void writeErrNoInfo(int errorNumber, const char *funcName,
254                                     int indent) = 0;
255 };
256
257 /*! \brief
258  * Exception information writer for cases where exceptions should be avoided.
259  *
260  * Formats the messages into the provided FILE handle without checking for
261  * errors in fprintf() calls.
262  */
263 class MessageWriterFileNoThrow : public MessageWriterInterface
264 {
265     public:
266         //! Initializes a writer that writes to the given file handle.
267         explicit MessageWriterFileNoThrow(FILE *fp) : fp_(fp) {}
268
269         virtual void writeLine(const char *text, int indent)
270         {
271             internal::printFatalErrorMessageLine(fp_, text, indent);
272         }
273         virtual void writeErrNoInfo(int errorNumber, const char *funcName,
274                                     int indent)
275         {
276             std::fprintf(fp_, "%*sReason: %s\n", indent, "",
277                          std::strerror(errorNumber));
278             if (funcName != NULL)
279             {
280                 std::fprintf(fp_, "%*s(call to %s() returned error code %d)\n",
281                              indent, "", funcName, errorNumber);
282             }
283         }
284
285     private:
286         FILE                   *fp_;
287 };
288
289 /*! \brief
290  * Exception information writer to format into an std::string.
291  */
292 class MessageWriterString : public MessageWriterInterface
293 {
294     public:
295         //! Post-processes the output string to not end in a line feed.
296         void removeTerminatingLineFeed()
297         {
298             if (result_.size() > 0U)
299             {
300                 result_.erase(result_.size() - 1);
301             }
302         }
303         //! Returns the constructed string.
304         const std::string &result() const { return result_; }
305
306         virtual void writeLine(const char *text, int indent)
307         {
308             result_.append(indent, ' ');
309             result_.append(text);
310             result_.append("\n");
311         }
312         virtual void writeErrNoInfo(int errorNumber, const char *funcName,
313                                     int indent)
314         {
315             writeLine(formatString("Reason: %s", std::strerror(errorNumber)).c_str(),
316                       indent);
317             if (funcName != NULL)
318             {
319                 writeLine(formatString("(call to %s() returned error code %d)",
320                                        funcName, errorNumber).c_str(),
321                           indent);
322             }
323         }
324
325     private:
326         std::string             result_;
327 };
328
329 /*! \brief
330  * Prints error information for an exception object.
331  *
332  * \param[in] writer  Writer to write out the information.
333  * \param[in] ex      Exception object to print.
334  * \param[in] indent  Indentation for the information.
335  *
336  * If the exception contains nested exceptions, information from them is
337  * recursively printed.
338  *
339  * Does not throw unless the writer throws.
340  */
341 void formatExceptionMessageInternal(MessageWriterInterface *writer,
342                                     const std::exception &ex, int indent)
343 {
344     const boost::exception *boostEx = dynamic_cast<const boost::exception *>(&ex);
345     if (boostEx != NULL)
346     {
347         // TODO: Add an option to print this information for the tests
348         // const char *const *funcPtr =
349         //     boost::get_error_info<boost::throw_function>(*boostEx);
350         // const char *const *filePtr =
351         //     boost::get_error_info<boost::throw_file>(*boostEx);
352         // const int         *linePtr =
353         //     boost::get_error_info<boost::throw_line>(*boostEx);
354
355         // std::string        result;
356         // if (filePtr != NULL && linePtr != NULL)
357         // {
358         //     result = formatString("%s:%d: %s\n", *filePtr, *linePtr,
359         //                           funcPtr != NULL ? *funcPtr : "");
360         // }
361
362         // TODO: Remove duplicate context if present in multiple nested exceptions.
363         const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*boostEx);
364         if (msg != NULL)
365         {
366             while (msg != NULL && msg->isContext())
367             {
368                 writer->writeLine(msg->text().c_str(), indent*2);
369                 ++indent;
370                 msg = &msg->child();
371             }
372             if (msg != NULL && !msg->text().empty())
373             {
374                 writer->writeLine(msg->text().c_str(), indent*2);
375             }
376         }
377         else
378         {
379             writer->writeLine(ex.what(), indent*2);
380         }
381
382         const int *errorNumber
383             = boost::get_error_info<boost::errinfo_errno>(*boostEx);
384         if (errorNumber != NULL)
385         {
386             const char * const *funcName
387                 = boost::get_error_info<boost::errinfo_api_function>(*boostEx);
388             writer->writeErrNoInfo(*errorNumber,
389                                    funcName != NULL ? *funcName : NULL,
390                                    (indent+1)*2);
391         }
392
393         // TODO: Treat also boost::nested_exception (not currently used, though)
394
395         const internal::NestedExceptionList *nested
396             = boost::get_error_info<errinfo_nested_exceptions>(*boostEx);
397         if (nested != NULL)
398         {
399             internal::NestedExceptionList::const_iterator ni;
400             for (ni = nested->begin(); ni != nested->end(); ++ni)
401             {
402                 try
403                 {
404                     rethrow_exception(*ni);
405                 }
406                 catch (const std::exception &nestedEx)
407                 {
408                     formatExceptionMessageInternal(writer, nestedEx, indent + 1);
409                 }
410             }
411         }
412     }
413     else
414     {
415         writer->writeLine(ex.what(), indent*2);
416     }
417 }
418
419 }   // namespace
420
421 void printFatalErrorMessage(FILE *fp, const std::exception &ex)
422 {
423     const char             *title      = "Unknown exception";
424     bool                    bPrintType = false;
425     const GromacsException *gmxEx      = dynamic_cast<const GromacsException *>(&ex);
426     // TODO: Treat more of the standard exceptions
427     if (gmxEx != NULL)
428     {
429         title = getErrorCodeString(gmxEx->errorCode());
430     }
431     else if (dynamic_cast<const tMPI::system_error *>(&ex) != NULL)
432     {
433         title = "System error in thread synchronization";
434     }
435     else if (dynamic_cast<const std::bad_alloc *>(&ex) != NULL)
436     {
437         title = "Memory allocation failed";
438     }
439     else if (dynamic_cast<const std::logic_error *>(&ex) != NULL)
440     {
441         title      = "Standard library logic error (bug)";
442         bPrintType = true;
443     }
444     else if (dynamic_cast<const std::runtime_error *>(&ex) != NULL)
445     {
446         title      = "Standard library runtime error (possible bug)";
447         bPrintType = true;
448     }
449     else
450     {
451         bPrintType = true;
452     }
453     // We can't call get_error_info directly on ex since our internal boost
454     // needs to be compiled with BOOST_NO_RTTI. So we do the dynamic_cast
455     // here instead.
456     const char *const      *funcPtr = NULL;
457     const char *const      *filePtr = NULL;
458     const int              *linePtr = NULL;
459     const boost::exception *boostEx = dynamic_cast<const boost::exception *>(&ex);
460     if (boostEx != NULL)
461     {
462         funcPtr = boost::get_error_info<boost::throw_function>(*boostEx);
463         filePtr = boost::get_error_info<boost::throw_file>(*boostEx);
464         linePtr = boost::get_error_info<boost::throw_line>(*boostEx);
465     }
466     internal::printFatalErrorHeader(fp, title,
467                                     funcPtr != NULL ? *funcPtr : NULL,
468                                     filePtr != NULL ? *filePtr : NULL,
469                                     linePtr != NULL ? *linePtr : 0);
470     if (bPrintType)
471     {
472         std::fprintf(fp, "(exception type: %s)\n", typeid(ex).name());
473     }
474     MessageWriterFileNoThrow writer(fp);
475     formatExceptionMessageInternal(&writer, ex, 0);
476     internal::printFatalErrorFooter(fp);
477 }
478
479 std::string formatExceptionMessageToString(const std::exception &ex)
480 {
481     MessageWriterString writer;
482     formatExceptionMessageInternal(&writer, ex, 0);
483     writer.removeTerminatingLineFeed();
484     return writer.result();
485 }
486
487 void formatExceptionMessageToFile(FILE *fp, const std::exception &ex)
488 {
489     MessageWriterFileNoThrow writer(fp);
490     formatExceptionMessageInternal(&writer, ex, 0);
491 }
492
493 } // namespace gmx