19886c1f4c40410bb97160dcde9b48d550c5401f
[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,2014,2015,2016,2017,2018, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source 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 "gmxpre.h"
43
44 #include "exceptions.h"
45
46 #include <cstring>
47
48 #include <map>
49 #include <memory>
50 #include <new>
51 #include <stdexcept>
52 #include <typeinfo>
53
54 #include "thread_mpi/system_error.h"
55
56 #include "gromacs/utility/basenetwork.h"
57 #include "gromacs/utility/errorcodes.h"
58 #include "gromacs/utility/fatalerror.h"
59 #include "gromacs/utility/gmxassert.h"
60 #include "gromacs/utility/stringutil.h"
61 #include "gromacs/utility/textwriter.h"
62
63 #include "errorformat.h"
64
65 namespace gmx
66 {
67
68 namespace internal
69 {
70
71 IExceptionInfo::~IExceptionInfo()
72 {
73 }
74
75 class ExceptionData
76 {
77     public:
78         std::map<std::type_index, ExceptionInfoPointer> infos_;
79 };
80
81 }    // namespace internal
82
83 namespace
84 {
85
86 /********************************************************************
87  * ErrorMessage
88  */
89
90 /*! \brief
91  * Error message or error context text item.
92  *
93  * Error messages for an exception are represented as a chain of ErrorMessage
94  * objects: the elements at the bottom of the chain (with no children) is the
95  * error message, and other elements are the context strings added.
96  *
97  * \ingroup module_utility
98  */
99 class ErrorMessage
100 {
101     public:
102         /*! \brief
103          * Creates an error message object with the specified text.
104          *
105          * \param[in] text  Text for the message.
106          */
107         explicit ErrorMessage(const std::string &text);
108
109         //! Whether this object is a context string.
110         bool isContext() const { return static_cast<bool>(child_); }
111         //! Returns the text for this object.
112         const std::string &text() const { return text_; }
113         /*! \brief
114          * Returns the child object for a context object.
115          *
116          * Must not be called if isContext() returns false.
117          */
118         const ErrorMessage &child() const
119         {
120             GMX_ASSERT(isContext(),
121                        "Attempting to access nonexistent message object");
122             return *child_;
123         }
124
125         /*! \brief
126          * Creates a new message object with context prepended.
127          *
128          * \param[in] context  Context string to add.
129          * \returns   New error message object that has \p context as its text
130          *      and \c this as its child.
131          * \throws    std::bad_alloc if out of memory.
132          */
133         ErrorMessage prependContext(const std::string &context) const;
134
135     private:
136         std::string                     text_;
137         std::shared_ptr<ErrorMessage>   child_;
138 };
139
140 /*! \internal \brief
141  * Stores a reason or the top-most context string of an exception.
142  *
143  * \ingroup module_utility
144  */
145 typedef ExceptionInfo<struct ExceptionInfoMessage_, ErrorMessage>
146     ExceptionInfoMessage;
147
148 ErrorMessage::ErrorMessage(const std::string &text)
149     : text_(text)
150 {
151     size_t length = text_.find_last_not_of(" \n");
152     if (length == std::string::npos)
153     {
154         length = text_.length() - 1;
155     }
156     text_.resize(length + 1);
157 }
158
159 ErrorMessage
160 ErrorMessage::prependContext(const std::string &context) const
161 {
162     ErrorMessage newMessage(context);
163     newMessage.child_ = std::make_shared<ErrorMessage>(*this);
164     return newMessage;
165 }
166
167 /*! \brief
168  * Stores list of nested exceptions for Gromacs exceptions.
169  *
170  * \ingroup module_utility
171  */
172 typedef ExceptionInfo<struct ExceptionInfoNestedExceptions_, internal::NestedExceptionList>
173     ExceptionInfoNestedExceptions;
174
175 }   // namespace
176
177 /********************************************************************
178  * GromacsException
179  */
180
181 GromacsException::GromacsException(const ExceptionInitializer &details)
182     : data_(new internal::ExceptionData)
183 {
184     setInfo(ExceptionInfoMessage(ErrorMessage(details.reason_)));
185     if (details.hasNestedExceptions())
186     {
187         setInfo(ExceptionInfoNestedExceptions(details.nested_));
188     }
189 }
190
191 const char *GromacsException::what() const noexcept
192 {
193     const ErrorMessage *msg = getInfo<ExceptionInfoMessage>();
194     if (msg == nullptr)
195     {
196         return "No reason provided";
197     }
198     while (msg->isContext())
199     {
200         msg = &msg->child();
201     }
202     return msg->text().c_str();
203 }
204
205 void GromacsException::prependContext(const std::string &context)
206 {
207     const ErrorMessage *msg = getInfo<ExceptionInfoMessage>();
208     GMX_RELEASE_ASSERT(msg != nullptr, "Message should always be set");
209     setInfo(ExceptionInfoMessage(msg->prependContext(context)));
210 }
211
212 const internal::IExceptionInfo *
213 GromacsException::getInfo(const std::type_index &index) const
214 {
215     auto iter = data_->infos_.find(index);
216     if (iter != data_->infos_.end())
217     {
218         return iter->second.get();
219     }
220     return nullptr;
221 }
222
223 void GromacsException::setInfo(
224         const std::type_index &index, internal::ExceptionInfoPointer &&item)
225 {
226     data_->infos_[index] = std::move(item);
227 }
228
229 /********************************************************************
230  * Derived exception classes
231  */
232
233 int FileIOError::errorCode() const
234 {
235     return eeFileIO;
236 }
237
238 int InvalidInputError::errorCode() const
239 {
240     return eeInvalidInput;
241 }
242
243 int InconsistentInputError::errorCode() const
244 {
245     return eeInconsistentInput;
246 }
247
248 int ToleranceError::errorCode() const
249 {
250     return eeTolerance;
251 }
252
253 int SimulationInstabilityError::errorCode() const
254 {
255     return eeInstability;
256 }
257
258 int InternalError::errorCode() const
259 {
260     return eeInternalError;
261 }
262
263 int APIError::errorCode() const
264 {
265     return eeAPIError;
266 }
267
268 int RangeError::errorCode() const
269 {
270     return eeRange;
271 }
272
273 int NotImplementedError::errorCode() const
274 {
275     return eeNotImplemented;
276 }
277
278
279 /********************************************************************
280  * Global functions
281  */
282
283 namespace
284 {
285
286 //! \addtogroup module_utility
287 //! \{
288
289 /*! \brief
290  * Abstracts actual output from the other logic in exception formatting.
291  *
292  * Object that implements this interface is passed to
293  * formatExceptionMessageInternal(), and is responsible for composing the
294  * output.  This allows using the same implementation of interpreting the
295  * exceptions while still supporting output to different formats (e.g., to a
296  * string or to \c stderr).
297  */
298 class IMessageWriter
299 {
300     public:
301         virtual ~IMessageWriter() {}
302
303         /*! \brief
304          * Writes a single line of text into the output.
305          *
306          * \param[in] text    Text to write on the line.
307          * \param[in] indent  Suggested number of spaces to indent the line.
308          */
309         virtual void writeLine(const char *text, int indent) = 0;
310         /*! \brief
311          * Writes information about a system error (errno-based).
312          *
313          * \param[in] errorNumber  errno value
314          * \param[in] funcName     Name of the system call (can be NULL).
315          * \param[in] indent       Suggested number of spaces to indent the output.
316          */
317         virtual void writeErrNoInfo(int errorNumber, const char *funcName,
318                                     int indent) = 0;
319 };
320
321 /*! \brief
322  * Exception information writer for cases where exceptions should be avoided.
323  *
324  * Formats the messages into the provided FILE handle without checking for
325  * errors in std::fprintf() calls.
326  */
327 class MessageWriterFileNoThrow : public IMessageWriter
328 {
329     public:
330         //! Initializes a writer that writes to the given file handle.
331         explicit MessageWriterFileNoThrow(FILE *fp) : fp_(fp) {}
332
333         virtual void writeLine(const char *text, int indent)
334         {
335             internal::printFatalErrorMessageLine(fp_, text, indent);
336         }
337         virtual void writeErrNoInfo(int errorNumber, const char *funcName,
338                                     int indent)
339         {
340             std::fprintf(fp_, "%*sReason: %s\n", indent, "",
341                          std::strerror(errorNumber));
342             if (funcName != nullptr)
343             {
344                 std::fprintf(fp_, "%*s(call to %s() returned error code %d)\n",
345                              indent, "", funcName, errorNumber);
346             }
347         }
348
349     private:
350         FILE                   *fp_;
351 };
352
353 /*! \brief
354  * Exception information writer to format into a TextOutputStream.
355  */
356 class MessageWriterTextWriter : public IMessageWriter
357 {
358     public:
359         //! Initializes a writer that writes to the given stream.
360         explicit MessageWriterTextWriter(TextWriter *writer) : writer_(writer)
361         {
362         }
363
364         virtual void writeLine(const char *text, int indent)
365         {
366             writer_->wrapperSettings().setIndent(indent);
367             writer_->writeLine(text);
368         }
369         virtual void writeErrNoInfo(int errorNumber, const char *funcName,
370                                     int indent)
371         {
372             writer_->wrapperSettings().setIndent(indent);
373             writer_->writeLine(formatString("Reason: %s", std::strerror(errorNumber)));
374             if (funcName != nullptr)
375             {
376                 writer_->writeLine(
377                         formatString("(call to %s() returned error code %d)",
378                                      funcName, errorNumber));
379             }
380         }
381
382     private:
383         TextWriter     *writer_;
384 };
385
386 /*! \brief
387  * Exception information writer to format into an std::string.
388  */
389 class MessageWriterString : public IMessageWriter
390 {
391     public:
392         //! Post-processes the output string to not end in a line feed.
393         void removeTerminatingLineFeed()
394         {
395             if (!result_.empty())
396             {
397                 result_.erase(result_.size() - 1);
398             }
399         }
400         //! Returns the constructed string.
401         const std::string &result() const { return result_; }
402
403         virtual void writeLine(const char *text, int indent)
404         {
405             result_.append(indent, ' ');
406             result_.append(text);
407             result_.append("\n");
408         }
409         virtual void writeErrNoInfo(int errorNumber, const char *funcName,
410                                     int indent)
411         {
412             writeLine(formatString("Reason: %s", std::strerror(errorNumber)).c_str(),
413                       indent);
414             if (funcName != nullptr)
415             {
416                 writeLine(formatString("(call to %s() returned error code %d)",
417                                        funcName, errorNumber).c_str(),
418                           indent);
419             }
420         }
421
422     private:
423         std::string             result_;
424 };
425
426 /*! \brief
427  * Prints error information for an exception object.
428  *
429  * \param[in] writer  Writer to write out the information.
430  * \param[in] ex      Exception object to print.
431  * \param[in] indent  Indentation for the information.
432  *
433  * If the exception contains nested exceptions, information from them is
434  * recursively printed.
435  *
436  * Does not throw unless the writer throws.
437  */
438 void formatExceptionMessageInternal(IMessageWriter *writer,
439                                     const std::exception &ex, int indent)
440 {
441     const GromacsException *gmxEx = dynamic_cast<const GromacsException *>(&ex);
442     if (gmxEx != nullptr)
443     {
444         // TODO: Add an option to print location information for the tests
445
446         // std::string        result;
447         // if (filePtr != NULL && linePtr != NULL)
448         // {
449         //     result = formatString("%s:%d: %s\n", *filePtr, *linePtr,
450         //                           funcPtr != NULL ? *funcPtr : "");
451         // }
452
453         bool                bAnythingWritten = false;
454         // TODO: Remove duplicate context if present in multiple nested exceptions.
455         const ErrorMessage *msg = gmxEx->getInfo<ExceptionInfoMessage>();
456         if (msg != nullptr)
457         {
458             while (msg != nullptr && msg->isContext())
459             {
460                 writer->writeLine(msg->text().c_str(), indent*2);
461                 ++indent;
462                 msg = &msg->child();
463             }
464             if (msg != nullptr && !msg->text().empty())
465             {
466                 writer->writeLine(msg->text().c_str(), indent*2);
467                 bAnythingWritten = true;
468             }
469         }
470         else
471         {
472             writer->writeLine(ex.what(), indent*2);
473             bAnythingWritten = true;
474         }
475
476         const int *errorNumber = gmxEx->getInfo<ExceptionInfoErrno>();
477         if (errorNumber != nullptr && *errorNumber != 0)
478         {
479             const char * const *funcName
480                 = gmxEx->getInfo<ExceptionInfoApiFunction>();
481             writer->writeErrNoInfo(*errorNumber,
482                                    funcName != nullptr ? *funcName : nullptr,
483                                    (indent+1)*2);
484             bAnythingWritten = true;
485         }
486
487         const internal::NestedExceptionList *nested
488             = gmxEx->getInfo<ExceptionInfoNestedExceptions>();
489         if (nested != nullptr)
490         {
491             internal::NestedExceptionList::const_iterator ni;
492             for (ni = nested->begin(); ni != nested->end(); ++ni)
493             {
494                 try
495                 {
496                     std::rethrow_exception(*ni);
497                 }
498                 catch (const std::exception &nestedEx)
499                 {
500                     const int newIndent = indent + (bAnythingWritten ? 1 : 0);
501                     formatExceptionMessageInternal(writer, nestedEx, newIndent);
502                 }
503             }
504         }
505     }
506     else
507     {
508         writer->writeLine(ex.what(), indent*2);
509     }
510 }
511
512 //! \}
513
514 }   // namespace
515
516 void printFatalErrorMessage(FILE *fp, const std::exception &ex)
517 {
518     const char             *title      = "Unknown exception";
519     bool                    bPrintType = false;
520     const GromacsException *gmxEx      = dynamic_cast<const GromacsException *>(&ex);
521     // TODO: Treat more of the standard exceptions
522     if (gmxEx != nullptr)
523     {
524         title = getErrorCodeString(gmxEx->errorCode());
525     }
526     else if (dynamic_cast<const tMPI::system_error *>(&ex) != nullptr)
527     {
528         title = "System error in thread synchronization";
529     }
530     else if (dynamic_cast<const std::bad_alloc *>(&ex) != nullptr)
531     {
532         title = "Memory allocation failed";
533     }
534     else if (dynamic_cast<const std::logic_error *>(&ex) != nullptr)
535     {
536         title      = "Standard library logic error (bug)";
537         bPrintType = true;
538     }
539     else if (dynamic_cast<const std::runtime_error *>(&ex) != nullptr)
540     {
541         title      = "Standard library runtime error (possible bug)";
542         bPrintType = true;
543     }
544     else
545     {
546         bPrintType = true;
547     }
548     const char *func = nullptr;
549     const char *file = nullptr;
550     int         line = 0;
551     if (gmxEx != nullptr)
552     {
553         const ThrowLocation *loc = gmxEx->getInfo<ExceptionInfoLocation>();
554         if (loc != nullptr)
555         {
556             func = loc->func;
557             file = loc->file;
558             line = loc->line;
559         }
560     }
561     internal::printFatalErrorHeader(fp, title, func, file, line);
562     if (bPrintType)
563     {
564         std::fprintf(fp, "(exception type: %s)\n", typeid(ex).name());
565     }
566     MessageWriterFileNoThrow writer(fp);
567     formatExceptionMessageInternal(&writer, ex, 0);
568     internal::printFatalErrorFooter(fp);
569 }
570
571 std::string formatExceptionMessageToString(const std::exception &ex)
572 {
573     MessageWriterString writer;
574     formatExceptionMessageInternal(&writer, ex, 0);
575     writer.removeTerminatingLineFeed();
576     return writer.result();
577 }
578
579 void formatExceptionMessageToFile(FILE *fp, const std::exception &ex)
580 {
581     MessageWriterFileNoThrow writer(fp);
582     formatExceptionMessageInternal(&writer, ex, 0);
583 }
584
585 void formatExceptionMessageToWriter(TextWriter           *writer,
586                                     const std::exception &ex)
587 {
588     MessageWriterTextWriter messageWriter(writer);
589     formatExceptionMessageInternal(&messageWriter, ex, 0);
590 }
591
592 int processExceptionAtExit(const std::exception & /*ex*/)
593 {
594     int returnCode = 1;
595     // If we have more than one rank (whether real MPI or thread-MPI),
596     // we cannot currently know whether just one rank or all ranks
597     // actually threw the error, so we need to exit here.
598     // Returning would mean graceful cleanup, which is not possible if
599     // some code is still executing on other ranks/threads.
600     if (gmx_node_num() > 1)
601     {
602         gmx_exit_on_fatal_error(ExitType_Abort, returnCode);
603     }
604     return returnCode;
605 }
606
607 void processExceptionAsFatalError(const std::exception &ex)
608 {
609     printFatalErrorMessage(stderr, ex);
610     gmx_exit_on_fatal_error(ExitType_Abort, 1);
611 }
612
613 } // namespace gmx