diff --git a/src/mmfatl.c b/src/mmfatl.c index f7505913..dac73b41 100644 --- a/src/mmfatl.c +++ b/src/mmfatl.c @@ -28,69 +28,20 @@ #include "mmfatl.h" /*! - * string terminating character + * some ASCII control characters */ enum { NUL = '\x00', + LF = '\n', }; -//---------- -/* - * During development you may not want to expose preliminary results to the - * normal compile, as this would trigger 'unused' warnings, for example. In - * the regression test environment your code may be referenced by testing code, - * though. - * - * This section should be empty, or even removed, once your development is - * finished. - */ -#ifdef TEST_ENABLE -# define UNDER_DEVELOPMENT -#endif +//*** utility code used in the implementation of the interface *** -#ifdef UNDER_DEVELOPMENT /*! - * converts an unsigned to a sequence of decimal digits representing its value. - * The value range is known to be at least 2**32 on contemporary hardware, but - * C99 guarantees just 2**16. We support unsigned in formatted error output - * to allow for macros like __LINE__ denoting error positions in text files. - * - * There exist no utoa in the C99 standard library, that could be used instead, - * and sprintf must not be used in a memory-tight situation (AS Unsafe heap, - * https://www.gnu.org/software/libc/manual/html_node/Formatted-Output-Functions.html). - * \param value an unsigned value to convert. - * \returns a pointer to a string converted from \p value. Except for zero, - * the result has a leading non-zero digit. - * \attention The result is stable only until the next call to this function. - */ -static char const* unsignedToString(unsigned value) { - /* - * sizeof(value) * CHAR_BIT are the bits encodable in value, the factor 146/485, - * derived from a chained fraction, is about 0.3010309, slightly greater than - * log 2. So the number within the brackets is the count of decimal digits - * encodable in value. Two extra bytes compensate for the truncation error of - * the division and allow for a terminating NUL. - */ - static char digits[(sizeof(value) * CHAR_BIT * 146) / 485 + 2]; - - unsigned ofs = sizeof(digits) - 1; - digits[ofs] = NUL; - if (value == 0) - digits[--ofs] = '0'; - else { - while (value) { - digits[--ofs] = (value % 10) + '0'; - value /= 10; - } - } - return digits + ofs; -} - -/*! - * \brief declares a buffer used to generate a text message through a - * formatting procedure. + * \brief a buffer used to generate a text message through a formatting + * procedure. * * This buffer type is used to send a final diagnostic message to the user, * before the program dies because of insufficient, or even corrupt memory. @@ -119,7 +70,8 @@ static char const* unsignedToString(unsigned value) { */ struct Buffer { - char* begin; /*!< points to first unallocated character */ + /*! points to first unallocated character */ + char* begin; /*! * marks the end of the writeable portion, where the \ref MMFATL_ELLIPSIS * begins. Logically constant once it got initialized. Never overwrite this @@ -139,10 +91,16 @@ struct Buffer { static struct Buffer buffer; /*! + * \brief initialize and empty the message buffer + * * We do not rely on any initialization during program start. Instead we * assume the worst case, a corrupted pointer overwrote the buffer. So we - * initialize it again immediately before use. - * \post \ref buffer is initialized + * initialize it immediately before use. + * \pre the ellipsis appended to the writeable portion of the buffer must + * terminate with a LF, so printing a buffer in overflow state will keep + * a command prompt in a new line after program exit. + * \post \ref buffer is initialized with all NUL characters, so NUL need + * not be copied * \param buffer [not null] the output buffer to empty and initialize */ static void initBuffer(struct Buffer* buffer) { @@ -155,17 +113,46 @@ static void initBuffer(struct Buffer* buffer) { } /*! + * \brief check the message buffer for emptiness + * + * \param buffer [const, not null] the buffer to check for emptiness. + * \return true, iff the \ref buffer is in its initial state. + * \pre \ref initBuffer was called + */ +inline static bool isBufferEmpty(struct Buffer const* buffer) { + return buffer->begin == buffer->text; +} + +/*! + * \brief last character in the message buffer + * + * Get the last stored character in the buffer. Discarded characters due to + * overflow are ignored. A returned NUL indicates the buffer is empty. + * \param buffer [const, not null] the buffer to investigate. + * \return the last character stored in buffer, or NUL iff empty. + * \pre \ref initBuffer was called + */ +static char getLastBufferedChar(struct Buffer const* buffer) { + return isBufferEmpty(buffer)? NUL : *(buffer->begin - 1); +} + +/*! + * \brief check whether the buffer is overflown + * * \param buffer [const, not null] the buffer to check for overflow. * \return true, iff the current contents exceeds the capacity of the * \ref buffer, so at least the terminating \ref NUL is cut off, maybe more. + * \pre \ref initBuffer was called */ inline static bool isBufferOverflow(struct Buffer const* buffer) { return buffer->begin == buffer->end; } /*! - * used to indicate whether \ref MMFATL_PH_PREFIX is a normal character, or - * is an escape character in a format string. + * \brief modes of appending text to the message buffer + * + * Used to indicate whether \ref MMFATL_PH_PREFIX is a normal character, or + * an escape character in a format string. */ enum SourceType { STRING, //format = empty; } -/*! reflect a possible buffer overflow in the parser state +/*! + * \brief convert an unsigned to a string of decimal numbers + * + * Converts an unsigned to a sequence of decimal digits representing its value. + * The value range is known to be at least 2**32 on contemporary hardware, but + * C99 guarantees just 2**16. We support unsigned in formatted error output + * to allow for macros like __LINE__ denoting error positions in text files. + * + * There exist no utoa in the C99 standard library, that could be used instead, + * and sprintf must not be used in a memory-tight situation (AS Unsafe heap, + * https://www.gnu.org/software/libc/manual/html_node/Formatted-Output-Functions.html). + * \param value an unsigned value to convert. + * \returns a pointer to a string converted from \p value. Except for zero, + * the result has a leading non-zero digit. + * \attention The result is stable only until the next call to this function. + */ +static char const* unsignedToString(unsigned value) { + /* + * sizeof(value) * CHAR_BIT are the bits encodable in value, the factor 146/485, + * derived from a chained fraction, is about 0.3010309, slightly greater than + * log 2. So the number within the brackets is the count of decimal digits + * encodable in value. Two extra bytes compensate for the truncation error of + * the division and allow for a terminating NUL. + */ + static char digits[(sizeof(value) * CHAR_BIT * 146) / 485 + 2]; + + unsigned ofs = sizeof(digits) - 1; + digits[ofs] = NUL; + if (value == 0) + digits[--ofs] = '0'; + else { + while (value) { + digits[--ofs] = (value % 10) + '0'; + value /= 10; + } + } + return digits + ofs; +} + +/*! + * \brief update the parser state in case of message buffer overflow + * + * Reflect a possible buffer overflow in the parser state * \param state [not null] ParserState object being updated in case of * overflow * \return false in case of overflow + * \pre \ref initState was called + * \post in case of overflow the current format position is moved to the end */ static bool checkOverflow(struct ParserState* state) { - bool result = !isBufferOverflow(state->out); - if (!result) + bool isOverflow = isBufferOverflow(state->out); + if (isOverflow) state->format += strlen(state->format); - return result; + return !isOverflow; } /*! - * \brief Preparing internal data structures for an error message. - * - * Empties the message buffer used to construct error messages. - * - * Prior to generating an error message some internal data structures need to - * be initialized. Usually such initialization is automatically executed on - * program startup. Since we are in a fatal error situation, we do not rely - * on this. Instead we assume memory corruption has affected this module's - * data and renders its state useless. So we initialize it immediately before - * the error message is generated. Note that we still rely on part of the - * system be running. We cannot overcome a fully clobbered system, we only - * can increase our chances of bypassing some degree of memory corruption. + * \brief copy a portion of text verbatim to the message buffer * - * \post internal data structures are initialized and ready for constructing - * a message from a format string and parameter values. - */ -static void fatalErrorInit(void) { - initBuffer(&buffer); - initState(&state, &buffer); -} - -/*! - * copy text verbatim from a format string to the message buffer, until either + * Copy text verbatim from a format string to the message buffer, until either * the format ends, or a placeholder is encountered. + * \param state struct ParserState* parser state going to be handled and updated + * \pre \ref initState was called * \post member format of \ref state is advanced over the copied text, if no * overflow. * \post member format of \ref state points to the terminating \ref NUL on * overflow. - * \param state struct ParserState* parser state going to be handled and updated */ static void handleText(struct ParserState* state) { state->format += appendText(state->format, FORMAT, &buffer); @@ -294,6 +313,8 @@ static void handleText(struct ParserState* state) { } /*! + * \brief handle a placeholder in a formatted message + * * A format specifier is a two character combination, where a placeholder * character \ref MMFATL_PH_PREFIX is followed by an alphabetic character * designating a type. A placeholder is substituted by the next argument in @@ -310,6 +331,7 @@ static void handleText(struct ParserState* state) { * Note that a duplicated MMFATL_PH_PREFIX is the accepted way to embed such a * character in a format string. This is correctly handled in this function. * \pre the format member in \ref state points to a MMFATL_PH_PREFIX. + * \pre \ref initState was called * \post the substituted value is written to \ref buffer. * \post the format member in \ref state is skipped * \param state struct ParserState* parser state going to be handled and updated @@ -345,10 +367,13 @@ static void handleSubstitution(struct ParserState* state) { } /*! - * parses the submitted format string, replacing each placeholder with one of + * \brief convert a formatted message to human readable text + * + * Parses the submitted format string, replacing each placeholder with one of * the values in member args of \ref state, and appends the result to the * current contents of \ref buffer. * \param state struct ParserState* parser state going to be handled and updated + * \pre \ref initState was called */ static void parse(struct ParserState* state) { do { @@ -359,7 +384,120 @@ static void parse(struct ParserState* state) { } while (*state->format != NUL); } -#endif // UNDER_DEVELOPMENT +/**** Implementation of the interface in the header file ****/ + +/* See the header file for descriptions + * Global instances buffer and state are known to the following functions. + * They must not be passed as parameters, as they are implementation details + * not to be exposed through the interface. + */ + +char const* getFatalErrorPlaceholderToken( + enum fatalErrorPlaceholderType type) { + + static char result[3]; + + switch (type) + { + case MMFATL_PH_PREFIX: + case MMFATL_PH_STRING: + case MMFATL_PH_UNSIGNED: + result[0] = MMFATL_PH_PREFIX; + result[1] = type; + result[2] = NUL; + break; + default: + return NULL; + } + return result; +} + +/*! + * \brief get the message buffer instance + * + * Gets the instance of Buffer to use with this interface (currently a global + * singleton). The returned instance is not guaranteed to be initialized. + * \return [not null] a pointer to the Buffer instance + */ +inline static struct Buffer* getBufferInstance(void) { + return &buffer; +} + +/*! + * \brief get the parser state instance + * + * Gets the instance of ParserState to use with this interface (currently a + * global singleton). The returned instance is not guaranteed to be + * initialized. + * \return [not null] a pointer to the ParserState instance + */ +inline static struct ParserState* getParserStateInstance(void) { + return &state; +} + +void fatalErrorInit(void) { + struct Buffer* buffer = getBufferInstance(); + + initBuffer(buffer); + initState(getParserStateInstance(), buffer); +} + +bool fatalErrorPush(char const* format, ...) { + struct ParserState* state = getParserStateInstance(); + + bool overflow = isBufferOverflow(state->out); + if (!overflow && format != NULL) { + // initialize the parser state + state->format = format; + va_start(state->args, format); + parse(state); + overflow = isBufferOverflow(state->out); + va_end(state->args); + } + + return !overflow; +} + +void fatalErrorPrintAndExit(void) { + struct Buffer* buffer = getBufferInstance(); + + if (!isBufferOverflow(buffer) + && !isBufferEmpty(buffer) + && getLastBufferedChar(buffer) != LF) + fatalErrorPush("\n"); +#ifndef TEST_ENABLE // we don't want a test program terminate here + fputs(buffer->text, stderr); + exit(EXIT_FAILURE); +#endif +} + +void fatalErrorExitAt(char const* file, unsigned line, + char const* msgWithPlaceholders, ...) { + fatalErrorInit(); + + // a format for the error location, only showing relevant data + char const* format = NULL; + if (file && *file) + { + if (line > 0) + format = "At %s:%u\n"; + else + format = "In file %s:\n"; + } + else if (line > 0) + format = "%sIn line %u:\n"; + + if (fatalErrorPush(format, file, line) && msgWithPlaceholders) { + struct ParserState* state = getParserStateInstance(); + + state->format = msgWithPlaceholders; + va_start(state->args, msgWithPlaceholders); + parse(state); + va_end(state->args); + } + + fatalErrorPrintAndExit(); +} //================= Regression tests ===================== @@ -495,6 +633,29 @@ static bool test_appendText(void) { return true; } +static bool test_isBufferEmpty(void) { + fatalErrorInit(); + ASSERT(isBufferEmpty(&buffer)); + appendText("a", STRING, &buffer); + ASSERT(!isBufferEmpty(&buffer)); + limitFreeBuffer(0); + ASSERT(!isBufferEmpty(&buffer)); + + return true; +} + +static bool test_getLastBufferedChar(void) { + fatalErrorInit(); + ASSERT(getLastBufferedChar(&buffer) == NUL); + appendText("a", STRING, &buffer); + ASSERT(getLastBufferedChar(&buffer) == 'a'); + limitFreeBuffer(1); + appendText("bc", STRING, &buffer); + ASSERT(getLastBufferedChar(&buffer) == 'b'); + + return true; +} + static bool test_handleText(void) { fatalErrorInit(); char const* format = state.format; @@ -668,14 +829,102 @@ static bool test_parse(void) { return true; } +static bool test_getFatalErrorPlaceholderToken(void) { + char const* result = getFatalErrorPlaceholderToken(MMFATL_PH_PREFIX); + ASSERT(result && strcmp(result, "%%") == 0); + result = getFatalErrorPlaceholderToken(MMFATL_PH_UNSIGNED); + ASSERT(result && strcmp(result, "%u") == 0); + ASSERT(getFatalErrorPlaceholderToken( + (enum fatalErrorPlaceholderType)NUL) == NULL); + return true; +} + +static bool test_fatalErrorPush() { + fatalErrorInit(); // pre-condition + + // case format NULL or empty (do nothing) + ASSERT(fatalErrorPush(NULL)); + ASSERT(strcmp(buffer.text, "") == 0); + ASSERT(fatalErrorPush("")); + ASSERT(strcmp(buffer.text, "") == 0); + + // simple message, no placeholder + ASSERT(fatalErrorPush("abc")); + ASSERT(strcmp(buffer.text, "abc") == 0); + + // message with placeholders, appended + ASSERT(fatalErrorPush("x%sy%uz", "--", 123)); + ASSERT(strcmp(buffer.text, "abcx--y123z") == 0); + + // overflow + limitFreeBuffer(2); + ASSERT(!fatalErrorPush("abc")); + ASSERT(strcmp(buffer.text, "$ab$") == 0); + + ASSERT(!fatalErrorPush(NULL)); + ASSERT(!fatalErrorPush("")); + ASSERT(strcmp(buffer.text, "$ab$") == 0); + + return true; +} + +static bool test_fatalErrorPrintAndExit(void) { + fatalErrorInit(); // pre-condition + fatalErrorPrintAndExit(); + ASSERT(strcmp(buffer.text, "") == 0); + + fatalErrorPush("aaa"); + fatalErrorPrintAndExit(); + ASSERT(strcmp(buffer.text, "aaa\n") == 0); + + // no second \n is appended + fatalErrorPrintAndExit(); + ASSERT(strcmp(buffer.text, "aaa\n") == 0); + + // in overflow condition do not append a LF + fatalErrorInit(); + while (!isBufferOverflow(&buffer)) // fill the buffer with "a" + fatalErrorPush("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + ASSERT(strcmp(buffer.text + MMFATL_MAX_MSG_SIZE - 3, "aaa" MMFATL_ELLIPSIS) == 0); + + return true; +} + +static bool test_fatalErrorExitAt() { + // note that in test mode the fatalErrorExitAt neither prints to stderr + // nor exits. The message is still in the buffer. + + fatalErrorExitAt("test.c", 1000, "%s failed!", "program"); + ASSERT(strcmp(buffer.text, "At test.c:1000\nprogram failed!\n") == 0); + // ignoring line + fatalErrorExitAt("x.c", 0, "test %u failed!", 5); + ASSERT(strcmp(buffer.text, "In file x.c:\ntest 5 failed!\n") == 0); + // ignoring file + fatalErrorExitAt(NULL, 123, "%s", "need help!\n"); + ASSERT(strcmp(buffer.text, "In line 123:\nneed help!\n") == 0); + + // ignoring error location + fatalErrorExitAt(NULL, 0, "take lessons, you fool!"); + ASSERT(strcmp(buffer.text, "take lessons, you fool!\n") == 0); + + return true; +} + + void test_mmfatl(void) { + RUN_TEST(test_unsignedToString); RUN_TEST(test_fatalErrorInit); RUN_TEST(test_isBufferOverflow); RUN_TEST(test_appendText); + RUN_TEST(test_isBufferEmpty); + RUN_TEST(test_getLastBufferedChar); RUN_TEST(test_handleText); RUN_TEST(test_parse); - RUN_TEST(test_unsignedToString); RUN_TEST(test_handleSubstitution); + RUN_TEST(test_getFatalErrorPlaceholderToken); + RUN_TEST(test_fatalErrorPush); + RUN_TEST(test_fatalErrorPrintAndExit); + RUN_TEST(test_fatalErrorExitAt); } #endif // TEST_ENABLE diff --git a/src/mmfatl.h b/src/mmfatl.h index 8773e9f3..88297ec8 100644 --- a/src/mmfatl.h +++ b/src/mmfatl.h @@ -10,60 +10,104 @@ #include #include "mmtest.h" +/* Documentation + * ============= + * + * Most comments are written in + * doxygen (Qt variant) style. If you have doxygen installed on your + * computer, you may generate a hyperlinked HTML documentation out of them with + * its root page placed in build/html/index.html by running build.sh with the + * -d option. + * + * Delete this once we have this description available in a central location. + * Keep this as a simple comment, so it is outside of Doxygen documentation. + */ + /*! * \file mmfatl.h * \brief supports generating of fatal error messages * - * part of the application's infrastructure + * In a sort of 3-tier architecture consisting of resource/configuration/data + * management, operation logic and algorithms, and presentation layer (or user + * interface), this code is interfacing the operating system on the + * lowest infrastructure (resource) layer. * * Rationale * ========= * * When a fatal error occurs, the internal structures of a program may be * corrupted to the point that recovery is impossible. The program exits - * immediately, but hopefully still displays a diagnostic message. + * immediately with a failure code, but hopefully still displays a diagnostic + * message. * * To display this final message, we restrict its code to very basic, * self-contained routines, independent of the rest of the program to the * extent possible, thus avoiding any corrupted data. * - * In particular everything should be pre-allocated and initialized, so the - * risk of a failure in a corrupted or memory-tight environment is minimized. - * This is to the detriment of flexibility, in particular, support for dynamic - * behavior is limited. Many Standard C library functions like printf MUST NOT - * be called when heap problems arise, since they use it internally. GNU tags + * In particular everything should be pre-allocated, so the risk of a failure + * in a corrupted or memory-tight environment is minimized. This is to the + * detriment of flexibility, in particular, support for dynamic behavior is + * limited. Many Standard C library functions like \p printf MUST NOT be + * called when heap problems arise, since they rely on it internally. GNU tags * such functions as 'AS-Unsafe heap' in their documentation (libc.pdf). * * Often it is sensible to embed details in a diagnosis message. Placeholders * in the format string mark insertion points for such values, much as in * \p printf. The variety and functionality is greatly reduced in our case, * though. Only pieces of text or unsigned integers can be embedded - * (%s or %u placeholder). + * (%s or %u placeholder). This is sufficient to embed an error location + * given by __FILE__ and __LINE__ into the message. * * For this kind of expansion you still need a buffer where the final message - * is constructed. In our context, this buffer is pre-allocated, and fixed in - * size, truncation of overflowing text enforced. + * is constructed. In our context, this buffer is pre-allocated, fixed in + * size, and truncation of overflowing text enforced. + * + * Implementation hint + * ------------------- + * + * In a memory tight situation we cannot reset the memory heap, or stack, to + * have free space again for, say, \p printf, even though we are about to exit + * program execution, for two reasons: + * - We want to gather diagnostic information, so the program structures need + * to be intact; + * - The fatal error routines need not be the last portion of the program + * executing. If a function is registered with \p atexit, it is called + * after an exit is triggered, and this function may rely on allocated + * data. */ -/*! the size a fatal error message including the terminating NUL character can +// *** Export basic features of the fatal error message processing ***/ + + +/*! + * \brief size of a text buffer used to construct a message + * + * the size a fatal error message including the terminating NUL character can * assume without truncation. Must be in the range of an int. */ enum { MMFATL_MAX_MSG_SIZE = 1024, }; -/* the character sequence appended to a truncated fatal error message due to - * a buffer overflow, so a reader is aware a displayed text is incomplete. +/*! + * \brief ASCII text sequence indicating truncated text + * + * the character sequence appended to a truncated fatal error message due to a + * buffer overflow, so its reader is aware a displayed text is incomplete. The + * ellipsis is followed by a line feed to ensure an overflown message is still + * on the previous line of the command prompt following program exit. */ -#define MMFATL_ELLIPSIS "..." +#define MMFATL_ELLIPSIS "...\n" /*! - * supported value types of a two character placeholder token in a format + * \brief ASCII characters used for placeholder tokens, printf style + * + * Supported value types of a two character placeholder token in a format * string. The first character of a placeholder is always an escape * character \ref MMFATL_PH_PREFIX, followed by one of the type characters * mentioned here. A valid placeholder in a format string is replaced with a * submitted value during a parse phase. The values in the enumeration here - * are all ASCII characters different from NUL, and distinct from each other. + * are all ASCII characters different from NUL, and mutually distinct. * * Two \ref MMFATL_PH_PREFIX in succession serve as a special token denoting * the character \ref MMFATL_PH_PREFIX itself, as an ordinary text character. @@ -71,13 +115,200 @@ enum { * in this particular case. */ enum fatalErrorPlaceholderType { - MMFATL_PH_PREFIX = '%', //!< escape character marking a placeholder + //! escape character marking a placeholder, followed by a type character + MMFATL_PH_PREFIX = '%', //! type character marking a placeholder for a string MMFATL_PH_STRING = 's', //! type character marking a placeholder for an unsigned MMFATL_PH_UNSIGNED = 'u', }; +// *** Interface of fatal error message processing ***/ + + +/*! + * \brief grammar support: generate a placeholder token for insertion into a + * format string. + * + * The placeholders in fatal errors are a subset of those used in the C library + * function printf. A flexible implementation might want to query placeholder + * tokens during an automatic message generation, rather than hardcoding them + * directly in the format string. + * \param aType data type of the value replacing the placeholder in a format + * string. \ref MMFATL_PH_PREFIX is allowed as a type, yielding a token + * standing for the character \ref MMFATL_PH_PREFIX itself. + * \return a placeholder of a supported type, or NULL, if you somehow + * manage to dodge C type checking. + * \attention the result is stable only until the next call to this + * function. + */ +extern char const* getFatalErrorPlaceholderToken( + enum fatalErrorPlaceholderType aType); + +/*! + * \brief Prepare internal data structures for an error message. + * + * Empties the message buffer used to construct error messages by + * \ref fatalErrorPush. + * + * Prior to generating an error message some internal data structures need to + * be initialized. Usually such initialization is automatically executed on + * program startup. Since we are in a fatal error situation, we do not rely + * on this. Instead we assume memory corruption has affected this module's + * data and renders its state useless. So we initialize it immediately before + * the error message is generated. Note that we still rely on part of the + * system be running. We cannot overcome a fully clobbered system, we only + * can increase our chances of bypassing some degree of memory corruption. + * \post internal data structures are initialized and ready for constructing + * a message from a format string and parameter values. Any previous + * contents is discarded. + * \invariant the memory state of the rest of the program is not changed. + */ +extern void fatalErrorInit(void); + + +/*! + * \brief append text to the current contents in the message buffer. + * + * Appends new text to the message buffer. The submitted extra parameters + * following \p format must match the placeholders in the \p format string + * in type. It is possible to add more parameters than necessary (they are + * simply ignored then), but never fewer. The caller is responsible for + * this pre-condition, no runtime check is performed. + * + * \param format [null] a format string usually containing NUL terminated + * ASCII encoded text, along with embedded placeholders, that are replaced + * with parameters following \p format in the call. + * + * A placeholder begins with an escape character \ref MMFATL_PH_PREFIX, + * immediately followed by a type character. Currently two types are + * implemented \ref MMFATL_PH_STRING and \ref MMFATL_PH_UNSIGNED. + * + * If you need a \ref MMFATL_PH_PREFIX verbatim in the error message, use + * two \ref MMFATL_PH_PREFIX in succession. They will automatically be + * replaced with a single one. + * + * For convenience \ref getFatalErrorPlaceholderToken may provide the + * correct placeholder token. + * + * NULL is equivalent to an empty format string, and supported both as a + * \p format string and as a parameter for a string placeholder, to + * enhance robustness. + * + * It is recommended to let the message end with a LF character, so a command + * prompt following it is displayed on a new line. If it is missing + * \ref fatalErrorPrintAndExit will supply one, but relying on this adds an + * unnecessary correction under severe conditions. + * + * + * The \p format is followed by a possibly empty list of paramaters substituted + * for placeholders. Currently unsigned int values may replace a + * \ref MMFATL_PH_UNSIGNED type placeholder, and a char const* pointer a + * \ref MMFATL_PH_STRING type placeholder. If the latter pointer is NULL, + * the placeholder is replaced with an empty string, else it must point to + * ASCII encoded NUL terminated text. No value is required for the + * \ref MMFATL_PH_PREFIX type tokens. + * \return false iff the message buffer is in overflow state. + * + * \pre \p format if not NULL, contains NUL terminated ASCII text. + * \pre \ref fatalErrorInit was called before. + * \pre the submitted parameters following \p format must match in type and + * order the placeholders in \p format. Their count may exceed that of the + * placeholders, but must never be less. + * \post the message is appended to the current buffer contents. It is + * truncated if there is insufficient space for it, including the + * terminating NUL. + * \invariant the memory state of the rest of the program is not changed. + * \warning This function does not automatically wrap messages and insert LF + * characters at the end of the declared screen width (g_screenWidth in + * mminou.h) as print2 does. Tests show that usual terminal emulators break + * up text at the last column, but that may depend on the used + * hard-/software. + */ +extern bool fatalErrorPush(char const* format, ...); + +/*! + * \brief display buffer contents and exit program with code EXIT_FAILURE. + * + * This function does not return. + * + * A NUL terminated message has been prepared in an internal buffer using a + * sequence of \ref fatalErrorPush. This function writes this message to + * stderr (by default tied to the terminal screen like stdout), and exits the + * program afterwards, indicating a failure to the operating system. + * + * A line feed is appended to a prepared non-empty message if it is not its + * last character. This keeps the message and a command prompt following the + * exit on separare lines. It is recommended to avoid this corrective + * measure and supply a terminating line feed as part of the message. + * + * It is possible to call this function without preparing a message. In this + * case nothing is written to stderr, and the program just exits with a failure + * code. + * + * \pre \ref fatalErrorInit has initialized the internal error message + * buffer, possibly followed by a sequence of \ref fatalErrorPush + * filling it with a message. + * \post [noreturn] the program terminates with error code EXIT_FAILURE, after + * writing the buffer contents to stderr. + * \post a line feed is appended to any non-empty message, if it is not + * provided + * \invariant the memory state of the rest of the program is not changed. + * \attention Although this function does not return to the caller, we must not + * assume it is the last piece of program code executing. A function + * registered with \p atexit executes after \p exit is triggered. That is + * why the above invariant is important to keep. + * \warning the output is to stderr, not to stdout. As long as you do not + * redirect stderr to, say, a log file, the error message is displayed to the + * user on the terminal. + * \warning previous versions of Metamath returned the exit code 1. Many + * systems define EXIT_FAILURE to this very value, but that is not mandated + * by the C11 standard. In fact, some systems may interpret 1 as a success + * code, so EXIT_FAILURE is more appropriate. + */ +extern void fatalErrorPrintAndExit(void); + +/*! + * \brief standard error reporting and program exit with failure code. + * + * Convenience function, covering a sequence of \ref fatalErrorInit, + * \ref fatalErrorPush and \ref fatalErrorPrintAndExit in succession. This + * function does not return. + * + * If an error location is given, it is printed first, followed by any + * non-empty message. A line feed is padded to the right of the message if it + * is missing. This is to keep a following command prompt on a new line. It + * is recommended to avoid this corrective measure and supply a line feed as + * part of the message. + * + * If all parameters are NULL or 0, no message is printed, not even the + * automatically supplied line feed, and this function just exits the program + * with an error code. If possible use \ref fatalErrorPrintAndExit directly + * instead. + * + * If you need to concat two or more pieces to form the error message, use + * a sequence of \ref fatalErrorInit, multiple \ref fatalErrorPush and finally + * \ref fatalErrorPrintAndExit instead of this function. + * + * \param file [null] filename of code responsible for calling this function, + * suitable for macro __FILE__. Part of an error location. Ignored in case + * of NULL. + * \param line [unsigned] if greater 0, interpreted as a line number, where + * a call to this function is initiated, suitable for macro __LINE__. + * Part of an error location. + * \param msgWithPlaceholders [null] the error message to display. This + * message may include placeholders in printf style, in which case it must be + * followed by more parameters, corresponding to the values replacing + * placeholders. These values must match in type that of the placeholders, + * and their number must be enough (can be more) to cover all placeholders. + * The details of this process is explained in \ref fatalErrorPush. Ignored + * if NULL. + * \post the program exits with EXIT_FAILURE return code, after writing the + * error location and message to stderr. + * \invariant the memory state of the rest of the program is not changed. + */ +extern void fatalErrorExitAt(char const* file, unsigned line, + char const* msgWithPlaceholders, ...); #ifdef TEST_ENABLE