diff --git a/doc/tests.md b/doc/tests.md index ab1871ef..8b3c1482 100644 --- a/doc/tests.md +++ b/doc/tests.md @@ -556,12 +556,13 @@ mode there. number of arguments have to be refused, and `gaol::textToInterval` has to read the names of GAOL, and with two strings to take the left bound of the first and the right bound of the second. What the exceptions of GAOL say - has to be their explanation, or `gaol_exception` where there is none: the - `what()` of a `gaol_exception`, an `input_format_error`, an + has to be their explanation, or the name of their class where there is none: + the `what()` of a `gaol_exception`, an `input_format_error`, an `unavailable_feature_error` and an `invalid_action_error` built with a known - explanation, and of what the reader, `operator>>` and `nb_fp_numbers()` - throw, read through a `std::exception`; and `operator<<` has to write the - explanation once. `what()` was `std::exception`, which a handler of + explanation, without one, with a null pointer (no explanation) and with an + explanation that starts with a NUL, and of what the reader, `operator>>` and + `nb_fp_numbers()` throw, read through a `std::exception`; and `operator<<` has + to write the explanation once. `what()` was `std::exception`, which a handler of `std::exception` printed and an exception that nothing catches ended the program with, and `operator<<` wrote it next to the explanation (GAOL v5). The reading of diff --git a/gaol/gaol_exceptions.cpp b/gaol/gaol_exceptions.cpp index 24eb8690..452ca3e4 100644 --- a/gaol/gaol_exceptions.cpp +++ b/gaol/gaol_exceptions.cpp @@ -41,6 +41,17 @@ namespace gaol_core { which_line_=l; } + gaol_exception::gaol_exception(const char* f, unsigned l, const char* e) + { + which_file_=f; + which_line_=l; + // A null pointer is no explanation: the derived classes passed it on to + // the constructor of std::string, which is undefined behavior (GAOL v5) + if (e != nullptr) { + explanation_ = e; + } + } + gaol_exception::gaol_exception(const char* f, unsigned l, const std::string& e) { which_file_=f; @@ -67,24 +78,35 @@ namespace gaol_core { return explanation_; } + const char* + gaol_exception::class_name() const noexcept + { + return "gaol_exception"; + } + /* - The explanation, or a text of its own where there is none, so that what a - handler of std::exception prints, or the terminate handler of an exception - nothing catches, is never empty (GAOL v5). GAOL 4 left the what() of - std::exception, "std::exception", whatever went wrong. + The explanation, or the name of the class where there is none, so that + what a handler of std::exception prints, or the terminate handler of an + exception nothing catches, is never empty (GAOL v5). An explanation whose + C string is empty, a NUL first, is none: what() would be an empty text. + GAOL 4 left the what() of std::exception, "std::exception", whatever went + wrong. */ const char* gaol_exception::what() const noexcept { - return explanation_.empty() ? "gaol_exception" : explanation_.c_str(); + const char* text = explanation_.c_str(); + return (*text != '\0') ? text : class_name(); } std::ostream& operator<<(std::ostream& out, const gaol_exception &e) { // what() is the explanation now, written below: not here too (GAOL v5) out << e.file() << ", line " << e.line() << ": exception thrown"; - if (e.explanation().length() != 0) { // Some explanation given? - out << ": " << e.explanation(); + // explanation() returns a copy: one, not one per use + const std::string explanation = e.explanation(); + if (explanation.length() != 0) { // Some explanation given? + out << ": " << explanation; } return out; } diff --git a/gaol/gaol_exceptions.h b/gaol/gaol_exceptions.h index 60590d0e..168c1e87 100644 --- a/gaol/gaol_exceptions.h +++ b/gaol/gaol_exceptions.h @@ -55,6 +55,15 @@ namespace gaol_core { */ gaol_exception(const char* f, unsigned l); + /*! + \param f Name of the file where the exception is thrown + \param l Line in the file where the exception is thrown + \param e Explanation concerning the throwing; a null pointer is no + explanation (GAOL v5: GAOL 4 built a std::string from it, which is + undefined behavior) + */ + gaol_exception(const char* f, unsigned l, const char* e); + /*! \param f Name of the file where the exception is thrown \param l Line in the file where the exception is thrown @@ -93,9 +102,10 @@ namespace gaol_core { went wrong: that is what a catch (const std::exception&) printed, and what a program ended with when nothing caught the exception. - \return the explanation, as explanation() gives it, or the text - "gaol_exception" if none was given, never an empty text. The text is - valid as long as the exception is. + \return the explanation, as explanation() gives it, or the name of the + class ("gaol_exception", "input_format_error"...) if none was given or + if its text as a C string is empty (a NUL first), never an empty text. + The text is valid as long as the exception is. */ const char* what() const noexcept override; @@ -116,6 +126,13 @@ namespace gaol_core { \brief Short explanation concerning the exception thrown. */ std::string explanation_; + + private: + /*! + \brief The name of the class, which what() gives where there is no + explanation (GAOL v5) + */ + virtual const char* class_name() const noexcept; }; @@ -133,6 +150,11 @@ namespace gaol_core { gaol_exception(f,l,e) {} input_format_error(const char* f, unsigned l, const std::string& e) : gaol_exception(f,l,e) {} + + private: + const char* class_name() const noexcept override { + return "input_format_error"; + } }; @@ -148,6 +170,11 @@ namespace gaol_core { gaol_exception(f,l,e) {} unavailable_feature_error(const char* f, unsigned l, const std::string& e) : gaol_exception(f,l,e) {} + + private: + const char* class_name() const noexcept override { + return "unavailable_feature_error"; + } }; @@ -164,6 +191,11 @@ namespace gaol_core { gaol_exception(f,l,e) {} invalid_action_error(const char* f, unsigned l, const std::string& e) : gaol_exception(f,l,e) {} + + private: + const char* class_name() const noexcept override { + return "invalid_action_error"; + } }; /*! diff --git a/manual/v5/gaol.tex b/manual/v5/gaol.tex index 75826c92..a49fb1b2 100644 --- a/manual/v5/gaol.tex +++ b/manual/v5/gaol.tex @@ -4808,9 +4808,11 @@ \subsection{The \code{gaol\_exception} exception}\label{sec:gaol_exception} consequence, the constructors for \code{gaol\_exception} are as follows: \defmethodx{gaol\_exception}{}{gaol\_exception}{(const char* \var{f}, unsigned \var{l})} -\begin{defmethod}{gaol\_exception}{}{gaol\_exception}{(const char* \var{f}, unsigned \var{l}, \\\hbox{}\hfill const char* \var{e})} +\defmethodx{gaol\_exception}{}{gaol\_exception}{(const char* \var{f}, unsigned \var{l}, \\\hbox{}\hfill const char* \var{e})} +\begin{defmethod}{gaol\_exception}{}{gaol\_exception}{(const char* \var{f}, unsigned \var{l}, \\\hbox{}\hfill const std::string\& \var{e})} Constructs a \code{gaol\_exception} being thrown from file \var{f} at -line \var{l}. The second form permits adding some explanatory string \var{e}. +line \var{l}. The second and third forms permit adding some explanatory +string \var{e}. A null pointer \var{e} is no explanation.\newinvfive \end{defmethod} \noindent @@ -4830,8 +4832,10 @@ \subsection{The \code{gaol\_exception} exception}\label{sec:gaol_exception} \end{defmethod} \begin{defmethod}{gaol\_exception}{const char*}{what}{(void) const noexcept} - Returns the explanation, as a C string, or the text \code{gaol\_exception} - if no additional information was provided, so that it is never empty. It + Returns the explanation, as a C string, or the name of the class of the + exception (\code{gaol\_exception}, \code{input\_format\_error}\ldots) if no + additional information was provided or if the explanation starts with a + NUL character, so that it is never empty. It overrides the \code{what()} of \code{std::exception}: a handler of \code{std::exception}, and the message of an exception that nothing catches, now say what went wrong, where GAOL~4 gave \code{std::exception} whatever the @@ -4851,9 +4855,11 @@ \subsection{The \code{input\_format\_error} exception} than the ones for \code{gaol\_exception}, namely: \defmethodx{input\_format\_error}{}{input\_format\_error}{(const char* \var{f}, unsigned \var{l})} -\begin{defmethod}{input\_format\_error}{}{input\_format\_error}{(const char* \var{f}, unsigned \var{l}, const char* \var{e})} +\defmethodx{input\_format\_error}{}{input\_format\_error}{(const char* \var{f}, unsigned \var{l}, const char* \var{e})} +\begin{defmethod}{input\_format\_error}{}{input\_format\_error}{(const char* \var{f}, unsigned \var{l}, \\\hbox{}\hfill const std::string\& \var{e})} Constructs an \code{input\_format\_error} being thrown from file \var{f} at -line \var{l}. The second form permits adding some explanatory string \var{e}. +line \var{l}. The second and third forms permit adding some explanatory +string \var{e}. \end{defmethod} The methods of the class are inherited from \code{gaol\_exception} (\pxref{sec:gaol_exception}). @@ -4879,9 +4885,11 @@ \subsection{The \code{unavailable\_feature\_error} exception} than the ones for \code{gaol\_exception}, namely: \defmethodx{unavailable\_feature\_error}{}{unavailable\_feature\_error}{\\\hbox{}\hfill (const char* \var{f}, unsigned \var{l})} -\begin{defmethod}{unavailable\_feature\_error}{}{unavailable\_feature\_error}{\\\hbox{}\hfill (const char* \var{f}, unsigned \var{l}, const char* \var{e})} +\defmethodx{unavailable\_feature\_error}{}{unavailable\_feature\_error}{\\\hbox{}\hfill (const char* \var{f}, unsigned \var{l}, const char* \var{e})} +\begin{defmethod}{unavailable\_feature\_error}{}{unavailable\_feature\_error}{\\\hbox{}\hfill (const char* \var{f}, unsigned \var{l}, const std::string\& \var{e})} Constructs an \code{unavailable\_feature\_error} being thrown from file \var{f} at -line \var{l}. The second form permits adding some explanatory string \var{e}. +line \var{l}. The second and third forms permit adding some explanatory +string \var{e}. \end{defmethod} The methods of the class are inherited from \code{gaol\_exception} (\pxref{sec:gaol_exception}). @@ -4897,9 +4905,11 @@ \subsection{The \code{invalid\_action\_error} exception} than the ones for \code{gaol\_exception}, namely: \defmethodx{invalid\_action\_error}{}{invalid\_action\_error}{(const char* \var{f},\\\hbox{}\hfill unsigned \var{l})} -\begin{defmethod}{invalid\_action\_error}{}{invalid\_action\_error}{(const char* \var{f},\\\hbox{}\hfill unsigned \var{l}, const char* \var{e})} +\defmethodx{invalid\_action\_error}{}{invalid\_action\_error}{(const char* \var{f},\\\hbox{}\hfill unsigned \var{l}, const char* \var{e})} +\begin{defmethod}{invalid\_action\_error}{}{invalid\_action\_error}{(const char* \var{f},\\\hbox{}\hfill unsigned \var{l}, const std::string\& \var{e})} Constructs an \code{invalid\_action\_error} being thrown from file \var{f} at -line \var{l}. The second form permits adding some explanatory string \var{e}. +line \var{l}. The second and third forms permit adding some explanatory +string \var{e}. \end{defmethod} The methods of the class are inherited from \code{gaol\_exception} (\pxref{sec:gaol_exception}). diff --git a/tests/expressions.cpp b/tests/expressions.cpp index 4495066c..734bc128 100644 --- a/tests/expressions.cpp +++ b/tests/expressions.cpp @@ -378,38 +378,53 @@ namespace libc++ whatever went wrong: the text that a handler of std::exception printed, and the one that a program ends with when nothing catches the exception ("what(): std::exception"), where explanation() had the - explanation. what() is now the explanation, or "gaol_exception" where - there is none, and operator<< writes the explanation once, where it - wrote what() and the explanation. The exceptions are built here with a - known explanation, so that what they say does not depend on the text of - the reader. */ + explanation. what() is now the explanation, or the name of the class + where there is none, and operator<< writes the explanation once, where + it wrote what() and the explanation. A null pointer is no explanation, + where GAOL 4 built a std::string from it (undefined behavior), and an + explanation whose C string is empty (a NUL first) gives the name of the + class too, so that what() is never an empty text. The exceptions are + built here with a known explanation, so that what they say does not + depend on the text of the reader. */ template void says(const char* name) { const unsigned line = __LINE__; + const char* const no_text = nullptr; const Exception with_text(__FILE__, line, "boom"); const Exception with_string(__FILE__, line, std::string("boom")); const Exception without(__FILE__, line); + const Exception with_null(__FILE__, line, no_text); + const Exception with_nul_first(__FILE__, line, std::string("\0boom", 5)); // Through the base class, as a handler of std::exception reads them const std::exception& a = with_text; const std::exception& b = with_string; const std::exception& c = without; + const std::exception& d = with_null; + const std::exception& z = with_nul_first; check("exception: what() is the explanation, given as a text", std::string(a.what()) == "boom", [&] { return std::string(name) + ": \"" + a.what() + "\""; }); check("exception: what() is the explanation, given as a string", std::string(b.what()) == "boom", [&] { return std::string(name) + ": \"" + b.what() + "\""; }); - check("exception: what() says gaol_exception where there is no explanation", - std::string(c.what()) == "gaol_exception", [&] { return std::string(name) + ": \"" + c.what() + "\""; }); + check("exception: what() is the name of the class where there is no explanation", + std::string(c.what()) == name, [&] { return std::string(name) + ": \"" + c.what() + "\""; }); + check("exception: a null pointer is no explanation", + with_null.explanation().empty() && std::string(d.what()) == name, + [&] { return std::string(name) + ": \"" + with_null.explanation() + "\", what() \"" + d.what() + "\""; }); + check("exception: what() is the name of the class where the explanation starts with a NUL", + std::string(z.what()) == name, [&] { return std::string(name) + ": \"" + z.what() + "\""; }); const std::string where = std::string(__FILE__) + ", line " + std::to_string(line) + ": exception thrown"; - std::ostringstream shown, shown_without; + std::ostringstream shown, shown_without, shown_null; shown << with_text; shown_without << without; + shown_null << with_null; check("exception: operator<< writes the file, the line and the explanation, once", shown.str() == where + ": boom", [&] { return std::string(name) + ": " + shown.str(); }); check("exception: operator<< writes no explanation where there is none", - shown_without.str() == where, [&] { return std::string(name) + ": " + shown_without.str(); }); + shown_without.str() == where && shown_null.str() == where, + [&] { return std::string(name) + ": " + shown_without.str() + " | " + shown_null.str(); }); } /* f, which has to throw an exception of GAOL, caught as a std::exception diff --git a/todo-notes/synthese.md b/todo-notes/synthese.md index 5974ac61..b5f5e5da 100644 --- a/todo-notes/synthese.md +++ b/todo-notes/synthese.md @@ -213,9 +213,33 @@ doc/differences.md: place: after the bullet on `operator>>`.) ``` -In `configure-clean`: **no**. To adapt once S.14 is done: without explanation -(or with an empty one) `what()` will return the name of the derived class -(`input_format_error`...), not `gaol_exception`. +In `configure-clean`: **no**. Point S (the 14) has changed what `what()` gives +without explanation, and a null explanation: take these texts instead. + +ChangeLog: + +```text +* gaol/gaol_exceptions.h, gaol/gaol_exceptions.cpp: gaol_exception::what() returns the explanation, or the name of the class (gaol_exception, input_format_error...) if there is none or if it starts with a NUL, where GAOL 4 left "std::exception"; +a null const char* explanation is none, where GAOL 4 built a std::string from it; operator<< of the exceptions writes the explanation once. +``` + +doc/differences.md: + +```text +- **`what()` of GAOL's exceptions is their explanation.** `gaol_exception` + overrides `what()`: it returns the explanation, or the name of the class of + the exception (`gaol_exception`, `input_format_error`, + `unavailable_feature_error` or `invalid_action_error`) where there is none + or where it starts with a NUL, so that `catch (const std::exception& e)` and + the message of an exception that nothing catches say what went wrong. GAOL + left the `what()` of `std::exception`, which is `std::exception` with + libstdc++ and libc++, whatever the error. A null pointer given as the + explanation is no explanation: GAOL built a `std::string` from it, which is + undefined behavior. `operator<<` of an exception writes the explanation + once, `file, line n: exception thrown: explanation`; it wrote `what()` next + to it, `file, line n: exception std::exception thrown: explanation`. + (Suggested place: after the bullet on `operator>>`.) +``` ### `15.md` (#46) — point 15, `operator>>` on an empty line @@ -1213,14 +1237,6 @@ Nothing (the measurements are in TODO M.9 and PR #36). --- -### `14.md` (#32) — point 14, `what()` of GAOL's exceptions - -- `operator<<` of an exception still calls `explanation()` twice, copying the - string twice (`gaol/gaol_exceptions.cpp` l.86-87). Harmless; could be - tidied while S.14 edits that file. - ---- - ### `15.md` (#46) — point 15, `operator>>` on an empty line - Not in the ledger, possibly worth a clause in the synthesis: interactive