Replace gmx::Mutex with std::mutex
[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-2018, The GROMACS development team.
5  * Copyright (c) 2019,2020,2021, by the GROMACS development team, led by
6  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7  * and including many others, as listed in the AUTHORS file in the
8  * top-level source directory and at http://www.gromacs.org.
9  *
10  * GROMACS is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1
13  * of the License, or (at your option) any later version.
14  *
15  * GROMACS is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with GROMACS; if not, see
22  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24  *
25  * If you want to redistribute modifications to GROMACS, please
26  * consider that scientific software is very special. Version
27  * control is crucial - bugs must be traceable. We will be happy to
28  * consider code for inclusion in the official distribution, but
29  * derived work must not be called official GROMACS. Details are found
30  * in the README & COPYING files - if they are missing, get the
31  * official version at http://www.gromacs.org.
32  *
33  * To help us fund GROMACS development, we humbly ask that you cite
34  * the research papers on the package. Check out http://www.gromacs.org.
35  */
36 /*! \internal \file
37  * \brief
38  * Implements classes and functions in exceptions.h.
39  *
40  * \author Teemu Murtola <teemu.murtola@gmail.com>
41  * \ingroup module_utility
42  */
43 #include "gmxpre.h"
44
45 #include "gromacs/utility/exceptions.h"
46
47 #include <cstring>
48
49 #include <map>
50 #include <memory>
51 #include <new>
52 #include <stdexcept>
53 #include <typeinfo>
54
55 #include "gromacs/utility/basenetwork.h"
56 #include "gromacs/utility/fatalerror.h"
57 #include "gromacs/utility/gmxassert.h"
58 #include "gromacs/utility/stringutil.h"
59 #include "gromacs/utility/textwriter.h"
60
61 #include "errorcodes.h"
62 #include "errorformat.h"
63
64 namespace gmx
65 {
66
67 namespace internal
68 {
69
70 IExceptionInfo::~IExceptionInfo() {}
71
72 class ExceptionData
73 {
74 public:
75     std::map<std::type_index, ExceptionInfoPointer> infos_;
76 };
77
78 } // namespace internal
79
80 namespace
81 {
82
83 /********************************************************************
84  * ErrorMessage
85  */
86
87 /*! \brief
88  * Error message or error context text item.
89  *
90  * Error messages for an exception are represented as a chain of ErrorMessage
91  * objects: the elements at the bottom of the chain (with no children) is the
92  * error message, and other elements are the context strings added.
93  *
94  * \ingroup module_utility
95  */
96 class ErrorMessage
97 {
98 public:
99     /*! \brief
100      * Creates an error message object with the specified text.
101      *
102      * \param[in] text  Text for the message.
103      */
104     explicit ErrorMessage(const std::string& text);
105
106     //! Whether this object is a context string.
107     bool isContext() const { return static_cast<bool>(child_); }
108     //! Returns the text for this object.
109     const std::string& text() const { return text_; }
110     /*! \brief
111      * Returns the child object for a context object.
112      *
113      * Must not be called if isContext() returns false.
114      */
115     const ErrorMessage& child() const
116     {
117         GMX_ASSERT(isContext(), "Attempting to access nonexistent message object");
118         return *child_;
119     }
120
121     /*! \brief
122      * Creates a new message object with context prepended.
123      *
124      * \param[in] context  Context string to add.
125      * \returns   New error message object that has \p context as its text
126      *      and \c this as its child.
127      * \throws    std::bad_alloc if out of memory.
128      */
129     ErrorMessage prependContext(const std::string& context) const;
130
131 private:
132     std::string                   text_;
133     std::shared_ptr<ErrorMessage> child_;
134 };
135
136 /*! \internal \brief
137  * Stores a reason or the top-most context string of an exception.
138  *
139  * \ingroup module_utility
140  */
141 typedef ExceptionInfo<struct ExceptionInfoMessage_, ErrorMessage> ExceptionInfoMessage;
142
143 ErrorMessage::ErrorMessage(const std::string& text) : text_(text)
144 {
145     size_t length = text_.find_last_not_of(" \n");
146     if (length == std::string::npos)
147     {
148         length = text_.length() - 1;
149     }
150     text_.resize(length + 1);
151 }
152
153 ErrorMessage ErrorMessage::prependContext(const std::string& context) const
154 {
155     ErrorMessage newMessage(context);
156     newMessage.child_ = std::make_shared<ErrorMessage>(*this);
157     return newMessage;
158 }
159
160 /*! \brief
161  * Stores list of nested exceptions for Gromacs exceptions.
162  *
163  * \ingroup module_utility
164  */
165 typedef ExceptionInfo<struct ExceptionInfoNestedExceptions_, internal::NestedExceptionList> ExceptionInfoNestedExceptions;
166
167 } // namespace
168
169 /********************************************************************
170  * GromacsException
171  */
172
173 GromacsException::GromacsException(const ExceptionInitializer& details) :
174     data_(new internal::ExceptionData)
175 {
176     setInfo(ExceptionInfoMessage(ErrorMessage(details.reason_)));
177     if (details.hasNestedExceptions())
178     {
179         setInfo(ExceptionInfoNestedExceptions(details.nested_));
180     }
181 }
182
183 const char* GromacsException::what() const noexcept
184 {
185     const ErrorMessage* msg = getInfo<ExceptionInfoMessage>();
186     if (msg == nullptr)
187     {
188         return "No reason provided";
189     }
190     while (msg->isContext())
191     {
192         msg = &msg->child();
193     }
194     return msg->text().c_str();
195 }
196
197 void GromacsException::prependContext(const std::string& context)
198 {
199     const ErrorMessage* msg = getInfo<ExceptionInfoMessage>();
200     GMX_RELEASE_ASSERT(msg != nullptr, "Message should always be set");
201     setInfo(ExceptionInfoMessage(msg->prependContext(context)));
202 }
203
204 const internal::IExceptionInfo* GromacsException::getInfo(const std::type_index& index) const
205 {
206     auto iter = data_->infos_.find(index);
207     if (iter != data_->infos_.end())
208     {
209         return iter->second.get();
210     }
211     return nullptr;
212 }
213
214 void GromacsException::setInfo(const std::type_index& index, internal::ExceptionInfoPointer&& item)
215 {
216     data_->infos_[index] = std::move(item);
217 }
218
219 /********************************************************************
220  * Derived exception classes
221  */
222
223 int FileIOError::errorCode() const
224 {
225     return eeFileIO;
226 }
227
228 int InvalidInputError::errorCode() const
229 {
230     return eeInvalidInput;
231 }
232
233 int InconsistentInputError::errorCode() const
234 {
235     return eeInconsistentInput;
236 }
237
238 int ToleranceError::errorCode() const
239 {
240     return eeTolerance;
241 }
242
243 int SimulationInstabilityError::errorCode() const
244 {
245     return eeInstability;
246 }
247
248 int InternalError::errorCode() const
249 {
250     return eeInternalError;
251 }
252
253 int APIError::errorCode() const
254 {
255     return eeAPIError;
256 }
257
258 int RangeError::errorCode() const
259 {
260     return eeRange;
261 }
262
263 int NotImplementedError::errorCode() const
264 {
265     return eeNotImplemented;
266 }
267
268 int ParallelConsistencyError::errorCode() const
269 {
270     return eeParallelConsistency;
271 }
272
273 int ModularSimulatorError::errorCode() const
274 {
275     return eeModularSimulator;
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, int indent) = 0;
318 };
319
320 /*! \brief
321  * Exception information writer for cases where exceptions should be avoided.
322  *
323  * Formats the messages into the provided FILE handle without checking for
324  * errors in std::fprintf() calls.
325  */
326 class MessageWriterFileNoThrow : public IMessageWriter
327 {
328 public:
329     //! Initializes a writer that writes to the given file handle.
330     explicit MessageWriterFileNoThrow(FILE* fp) : fp_(fp) {}
331
332     void writeLine(const char* text, int indent) override
333     {
334         internal::printFatalErrorMessageLine(fp_, text, indent);
335     }
336     void writeErrNoInfo(int errorNumber, const char* funcName, int indent) override
337     {
338         std::fprintf(fp_, "%*sReason: %s\n", indent, "", std::strerror(errorNumber));
339         if (funcName != nullptr)
340         {
341             std::fprintf(fp_, "%*s(call to %s() returned error code %d)\n", indent, "", funcName, errorNumber);
342         }
343     }
344
345 private:
346     FILE* fp_;
347 };
348
349 /*! \brief
350  * Exception information writer to format into a TextOutputStream.
351  */
352 class MessageWriterTextWriter : public IMessageWriter
353 {
354 public:
355     //! Initializes a writer that writes to the given stream.
356     explicit MessageWriterTextWriter(TextWriter* writer) : writer_(writer) {}
357
358     void writeLine(const char* text, int indent) override
359     {
360         writer_->wrapperSettings().setIndent(indent);
361         writer_->writeLine(text);
362     }
363     void writeErrNoInfo(int errorNumber, const char* funcName, int indent) override
364     {
365         writer_->wrapperSettings().setIndent(indent);
366         writer_->writeLine(formatString("Reason: %s", std::strerror(errorNumber)));
367         if (funcName != nullptr)
368         {
369             writer_->writeLine(formatString("(call to %s() returned error code %d)", funcName, errorNumber));
370         }
371     }
372
373 private:
374     TextWriter* writer_;
375 };
376
377 /*! \brief
378  * Exception information writer to format into an std::string.
379  */
380 class MessageWriterString : public IMessageWriter
381 {
382 public:
383     //! Post-processes the output string to not end in a line feed.
384     void removeTerminatingLineFeed()
385     {
386         if (!result_.empty())
387         {
388             result_.erase(result_.size() - 1);
389         }
390     }
391     //! Returns the constructed string.
392     const std::string& result() const { return result_; }
393
394     void writeLine(const char* text, int indent) override
395     {
396         result_.append(indent, ' ');
397         result_.append(text);
398         result_.append("\n");
399     }
400     void writeErrNoInfo(int errorNumber, const char* funcName, int indent) override
401     {
402         writeLine(formatString("Reason: %s", std::strerror(errorNumber)).c_str(), indent);
403         if (funcName != nullptr)
404         {
405             writeLine(formatString("(call to %s() returned error code %d)", funcName, errorNumber).c_str(),
406                       indent);
407         }
408     }
409
410 private:
411     std::string result_;
412 };
413
414 /*! \brief
415  * Prints error information for an exception object.
416  *
417  * \param[in] writer  Writer to write out the information.
418  * \param[in] ex      Exception object to print.
419  * \param[in] indent  Indentation for the information.
420  *
421  * If the exception contains nested exceptions, information from them is
422  * recursively printed.
423  *
424  * Does not throw unless the writer throws.
425  */
426 void formatExceptionMessageInternal(IMessageWriter* writer, const std::exception& ex, int indent)
427 {
428     const GromacsException* gmxEx = dynamic_cast<const GromacsException*>(&ex);
429     if (gmxEx != nullptr)
430     {
431         // TODO: Add an option to print location information for the tests
432
433         // std::string        result;
434         // if (filePtr != NULL && linePtr != NULL)
435         // {
436         //     result = formatString("%s:%d: %s\n", *filePtr, *linePtr,
437         //                           funcPtr != NULL ? *funcPtr : "");
438         // }
439
440         bool bAnythingWritten = false;
441         // TODO: Remove duplicate context if present in multiple nested exceptions.
442         const ErrorMessage* msg = gmxEx->getInfo<ExceptionInfoMessage>();
443         if (msg != nullptr)
444         {
445             while (msg != nullptr && msg->isContext())
446             {
447                 writer->writeLine(msg->text().c_str(), indent * 2);
448                 ++indent;
449                 msg = &msg->child();
450             }
451             if (msg != nullptr && !msg->text().empty())
452             {
453                 writer->writeLine(msg->text().c_str(), indent * 2);
454                 bAnythingWritten = true;
455             }
456         }
457         else
458         {
459             writer->writeLine(ex.what(), indent * 2);
460             bAnythingWritten = true;
461         }
462
463         const int* errorNumber = gmxEx->getInfo<ExceptionInfoErrno>();
464         if (errorNumber != nullptr && *errorNumber != 0)
465         {
466             const char* const* funcName = gmxEx->getInfo<ExceptionInfoApiFunction>();
467             writer->writeErrNoInfo(
468                     *errorNumber, funcName != nullptr ? *funcName : nullptr, (indent + 1) * 2);
469             bAnythingWritten = true;
470         }
471
472         const internal::NestedExceptionList* nested = gmxEx->getInfo<ExceptionInfoNestedExceptions>();
473         if (nested != nullptr)
474         {
475             internal::NestedExceptionList::const_iterator ni;
476             for (ni = nested->begin(); ni != nested->end(); ++ni)
477             {
478                 try
479                 {
480                     std::rethrow_exception(*ni);
481                 }
482                 catch (const std::exception& nestedEx)
483                 {
484                     const int newIndent = indent + (bAnythingWritten ? 1 : 0);
485                     formatExceptionMessageInternal(writer, nestedEx, newIndent);
486                 }
487             }
488         }
489     }
490     else
491     {
492         writer->writeLine(ex.what(), indent * 2);
493     }
494 }
495
496 //! \}
497
498 } // namespace
499
500 void printFatalErrorMessage(FILE* fp, const std::exception& ex)
501 {
502     const char*             title      = "Unknown exception";
503     bool                    bPrintType = false;
504     const GromacsException* gmxEx      = dynamic_cast<const GromacsException*>(&ex);
505     // TODO: Treat more of the standard exceptions
506     if (gmxEx != nullptr)
507     {
508         title = getErrorCodeString(gmxEx->errorCode());
509     }
510     else if (dynamic_cast<const std::bad_alloc*>(&ex) != nullptr)
511     {
512         title = "Memory allocation failed";
513     }
514     else if (dynamic_cast<const std::logic_error*>(&ex) != nullptr)
515     {
516         title      = "Standard library logic error (bug)";
517         bPrintType = true;
518     }
519     else if (dynamic_cast<const std::runtime_error*>(&ex) != nullptr)
520     {
521         title      = "Standard library runtime error (possible bug)";
522         bPrintType = true;
523     }
524     else
525     {
526         bPrintType = true;
527     }
528     const char* func = nullptr;
529     const char* file = nullptr;
530     int         line = 0;
531     if (gmxEx != nullptr)
532     {
533         const ThrowLocation* loc = gmxEx->getInfo<ExceptionInfoLocation>();
534         if (loc != nullptr)
535         {
536             func = loc->func;
537             file = loc->file;
538             line = loc->line;
539         }
540     }
541     internal::printFatalErrorHeader(fp, title, func, file, line);
542     if (bPrintType)
543     {
544         std::fprintf(fp, "(exception type: %s)\n", typeid(ex).name());
545     }
546     MessageWriterFileNoThrow writer(fp);
547     formatExceptionMessageInternal(&writer, ex, 0);
548     internal::printFatalErrorFooter(fp);
549 }
550
551 std::string formatExceptionMessageToString(const std::exception& ex)
552 {
553     MessageWriterString writer;
554     formatExceptionMessageInternal(&writer, ex, 0);
555     writer.removeTerminatingLineFeed();
556     return writer.result();
557 }
558
559 void formatExceptionMessageToFile(FILE* fp, const std::exception& ex)
560 {
561     MessageWriterFileNoThrow writer(fp);
562     formatExceptionMessageInternal(&writer, ex, 0);
563 }
564
565 void formatExceptionMessageToWriter(TextWriter* writer, const std::exception& ex)
566 {
567     MessageWriterTextWriter messageWriter(writer);
568     formatExceptionMessageInternal(&messageWriter, ex, 0);
569 }
570
571 int processExceptionAtExit(const std::exception& /*ex*/)
572 {
573     int returnCode = 1;
574     // If we have more than one rank (whether real MPI or thread-MPI),
575     // we cannot currently know whether just one rank or all ranks
576     // actually threw the error, so we need to exit here.
577     // Returning would mean graceful cleanup, which is not possible if
578     // some code is still executing on other ranks/threads.
579     if (gmx_node_num() > 1)
580     {
581         gmx_exit_on_fatal_error(ExitType_Abort, returnCode);
582     }
583     return returnCode;
584 }
585
586 void processExceptionAsFatalError(const std::exception& ex)
587 {
588     printFatalErrorMessage(stderr, ex);
589     gmx_exit_on_fatal_error(ExitType_Abort, 1);
590 }
591
592 } // namespace gmx