/* * ***************************************************************************** * * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018-2021 Gavin D. Howard and contributors. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * ***************************************************************************** * * All bc status codes and cross-platform portability. * */ #ifndef BC_STATUS_H #define BC_STATUS_H #include // This is used by configure.sh to test for OpenBSD. #ifdef BC_TEST_OPENBSD #ifdef __OpenBSD__ #error On OpenBSD without _BSD_SOURCE #endif // __OpenBSD__ #endif // BC_TEST_OPENBSD #ifndef BC_ENABLED #define BC_ENABLED (1) #endif // BC_ENABLED #ifndef DC_ENABLED #define DC_ENABLED (1) #endif // DC_ENABLED // This is error checking for fuzz builds. #if BC_ENABLE_AFL #ifndef __AFL_HAVE_MANUAL_CONTROL #error Must compile with afl-clang-fast or afl-clang-lto for fuzzing #endif // __AFL_HAVE_MANUAL_CONTROL #endif // BC_ENABLE_AFL #ifndef BC_ENABLE_MEMCHECK #define BC_ENABLE_MEMCHECK (0) #endif // BC_ENABLE_MEMCHECK /** * Mark a variable as unused. * @param e The variable to mark as unused. */ #define BC_UNUSED(e) ((void) (e)) // If users want, they can define this to something like __builtin_expect(e, 1). // It might give a performance improvement. #ifndef BC_LIKELY /** * Mark a branch expression as likely. * @param e The expression to mark as likely. */ #define BC_LIKELY(e) (e) #endif // BC_LIKELY // If users want, they can define this to something like __builtin_expect(e, 0). // It might give a performance improvement. #ifndef BC_UNLIKELY /** * Mark a branch expression as unlikely. * @param e The expression to mark as unlikely. */ #define BC_UNLIKELY(e) (e) #endif // BC_UNLIKELY /** * Mark a branch expression as an error, if true. * @param e The expression to mark as an error, if true. */ #define BC_ERR(e) BC_UNLIKELY(e) /** * Mark a branch expression as not an error, if true. * @param e The expression to mark as not an error, if true. */ #define BC_NO_ERR(s) BC_LIKELY(s) // Disable extra debug code by default. #ifndef BC_DEBUG_CODE #define BC_DEBUG_CODE (0) #endif // BC_DEBUG_CODE // We want to be able to use _Noreturn on C11 compilers. #if __STDC_VERSION__ >= 201100L #include #define BC_NORETURN _Noreturn #define BC_C11 (1) #else // __STDC_VERSION__ #define BC_NORETURN #define BC_MUST_RETURN #define BC_C11 (0) #endif // __STDC_VERSION__ #define BC_HAS_UNREACHABLE (0) #define BC_HAS_COMPUTED_GOTO (0) // GCC and Clang complain if fallthroughs are not marked with their special // attribute. Jerks. This creates a define for marking the fallthroughs that is // nothing on other compilers. #if defined(__clang__) || defined(__GNUC__) #if defined(__has_attribute) #if __has_attribute(fallthrough) #define BC_FALLTHROUGH __attribute__((fallthrough)); #else // __has_attribute(fallthrough) #define BC_FALLTHROUGH #endif // __has_attribute(fallthrough) #ifdef __GNUC__ #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) #undef BC_HAS_UNREACHABLE #define BC_HAS_UNREACHABLE (1) #endif // __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) #else // __GNUC__ #if __clang_major__ >= 4 #undef BC_HAS_UNREACHABLE #define BC_HAS_UNREACHABLE (1) #endif // __clang_major__ >= 4 #endif // __GNUC__ #else // defined(__has_attribute) #define BC_FALLTHROUGH #endif // defined(__has_attribute) #else // defined(__clang__) || defined(__GNUC__) #define BC_FALLTHROUGH #endif // defined(__clang__) || defined(__GNUC__) #if BC_HAS_UNREACHABLE #define BC_UNREACHABLE __builtin_unreachable(); #else // BC_HAS_UNREACHABLE #ifdef _WIN32 #define BC_UNREACHABLE __assume(0); #else // _WIN32 #define BC_UNREACHABLE #endif // _WIN32 #endif // BC_HAS_UNREACHABLE #ifdef __GNUC__ #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) #undef BC_HAS_COMPUTED_GOTO #define BC_HAS_COMPUTED_GOTO (1) #endif // __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) #endif // __GNUC__ #ifdef __clang__ #if __clang_major__ >= 4 #undef BC_HAS_COMPUTED_GOTO #define BC_HAS_COMPUTED_GOTO (1) #endif // __clang_major__ >= 4 #endif // __GNUC__ #ifdef BC_NO_COMPUTED_GOTO #undef BC_HAS_COMPUTED_GOTO #define BC_HAS_COMPUTED_GOTO (0) #endif // BC_NO_COMPUTED_GOTO #ifdef __GNUC__ #ifdef __OpenBSD__ // The OpenBSD GCC doesn't like inline. #define inline #endif // __OpenBSD__ #endif // __GNUC__ // Workarounds for AIX's POSIX incompatibility. #ifndef SIZE_MAX #define SIZE_MAX __SIZE_MAX__ #endif // SIZE_MAX #ifndef UINTMAX_C #define UINTMAX_C __UINTMAX_C #endif // UINTMAX_C #ifndef UINT32_C #define UINT32_C __UINT32_C #endif // UINT32_C #ifndef UINT_FAST32_MAX #define UINT_FAST32_MAX __UINT_FAST32_MAX__ #endif // UINT_FAST32_MAX #ifndef UINT16_MAX #define UINT16_MAX __UINT16_MAX__ #endif // UINT16_MAX #ifndef SIG_ATOMIC_MAX #define SIG_ATOMIC_MAX __SIG_ATOMIC_MAX__ #endif // SIG_ATOMIC_MAX // Yes, this has to be here. #include // All of these set defaults for settings. #if BC_ENABLED #ifndef BC_DEFAULT_BANNER #define BC_DEFAULT_BANNER (0) #endif // BC_DEFAULT_BANNER #endif // BC_ENABLED #ifndef BC_DEFAULT_SIGINT_RESET #define BC_DEFAULT_SIGINT_RESET (1) #endif // BC_DEFAULT_SIGINT_RESET #ifndef BC_DEFAULT_TTY_MODE #define BC_DEFAULT_TTY_MODE (1) #endif // BC_DEFAULT_TTY_MODE #ifndef BC_DEFAULT_PROMPT #define BC_DEFAULT_PROMPT BC_DEFAULT_TTY_MODE #endif // BC_DEFAULT_PROMPT // All of these set defaults for settings. #ifndef DC_DEFAULT_SIGINT_RESET #define DC_DEFAULT_SIGINT_RESET (1) #endif // DC_DEFAULT_SIGINT_RESET #ifndef DC_DEFAULT_TTY_MODE #define DC_DEFAULT_TTY_MODE (0) #endif // DC_DEFAULT_TTY_MODE #ifndef DC_DEFAULT_HISTORY #define DC_DEFAULT_HISTORY DC_DEFAULT_TTY_MODE #endif // DC_DEFAULT_HISTORY #ifndef DC_DEFAULT_PROMPT #define DC_DEFAULT_PROMPT DC_DEFAULT_TTY_MODE #endif // DC_DEFAULT_PROMPT /// Statuses, which mark either which category of error happened, or some other /// status that matters. typedef enum BcStatus { /// Normal status. BC_STATUS_SUCCESS = 0, /// Math error. BC_STATUS_ERROR_MATH, /// Parse (and lex) error. BC_STATUS_ERROR_PARSE, /// Runtime error. BC_STATUS_ERROR_EXEC, /// Fatal error. BC_STATUS_ERROR_FATAL, /// EOF status. BC_STATUS_EOF, /// Quit status. This means that bc/dc is in the process of quitting. BC_STATUS_QUIT, } BcStatus; /// Errors, which are more specific errors. typedef enum BcErr { // Math errors. /// Negative number used when not allowed. BC_ERR_MATH_NEGATIVE, /// Non-integer used when not allowed. BC_ERR_MATH_NON_INTEGER, /// Conversion to a hardware integer would overflow. BC_ERR_MATH_OVERFLOW, /// Divide by zero. BC_ERR_MATH_DIVIDE_BY_ZERO, // Fatal errors. /// An allocation or reallocation failed. BC_ERR_FATAL_ALLOC_ERR, /// I/O failure. BC_ERR_FATAL_IO_ERR, /// File error, such as permissions or file does not exist. BC_ERR_FATAL_FILE_ERR, /// File is binary, not text, error. BC_ERR_FATAL_BIN_FILE, /// Attempted to read a directory as a file error. BC_ERR_FATAL_PATH_DIR, /// Invalid option error. BC_ERR_FATAL_OPTION, /// Option with required argument not given an argument. BC_ERR_FATAL_OPTION_NO_ARG, /// Option with no argument given an argument. BC_ERR_FATAL_OPTION_ARG, /// Option argument is invalid. BC_ERR_FATAL_ARG, // Runtime errors. /// Invalid ibase value. BC_ERR_EXEC_IBASE, /// Invalid obase value. BC_ERR_EXEC_OBASE, /// Invalid scale value. BC_ERR_EXEC_SCALE, /// Invalid expression parsed by read(). BC_ERR_EXEC_READ_EXPR, /// read() used within an expression given to a read() call. BC_ERR_EXEC_REC_READ, /// Type error. BC_ERR_EXEC_TYPE, /// Stack has too few elements error. BC_ERR_EXEC_STACK, /// Register stack has too few elements error. BC_ERR_EXEC_STACK_REGISTER, /// Wrong number of arguments error. BC_ERR_EXEC_PARAMS, /// Undefined function error. BC_ERR_EXEC_UNDEF_FUNC, /// Void value used in an expression error. BC_ERR_EXEC_VOID_VAL, // Parse (and lex errors). /// EOF encountered when not expected error. BC_ERR_PARSE_EOF, /// Invalid character error. BC_ERR_PARSE_CHAR, /// Invalid string (no ending quote) error. BC_ERR_PARSE_STRING, /// Invalid comment (no end found) error. BC_ERR_PARSE_COMMENT, /// Invalid token encountered error. BC_ERR_PARSE_TOKEN, #if BC_ENABLED /// Invalid expression error. BC_ERR_PARSE_EXPR, /// Expression is empty error. BC_ERR_PARSE_EMPTY_EXPR, /// Print statement is invalid error. BC_ERR_PARSE_PRINT, /// Function definition is invalid error. BC_ERR_PARSE_FUNC, /// Assignment is invalid error. BC_ERR_PARSE_ASSIGN, /// No auto identifiers given for an auto statement error. BC_ERR_PARSE_NO_AUTO, /// Duplicate local (parameter or auto) error. BC_ERR_PARSE_DUP_LOCAL, /// Invalid block (within braces) error. BC_ERR_PARSE_BLOCK, /// Invalid return statement for void functions. BC_ERR_PARSE_RET_VOID, /// Reference attached to a variable, not an array, error. BC_ERR_PARSE_REF_VAR, // POSIX-only errors. /// Name length greater than 1 error. BC_ERR_POSIX_NAME_LEN, /// Non-POSIX comment used error. BC_ERR_POSIX_COMMENT, /// Non-POSIX keyword error. BC_ERR_POSIX_KW, /// Non-POSIX . (last) error. BC_ERR_POSIX_DOT, /// Non-POSIX return error. BC_ERR_POSIX_RET, /// Non-POSIX boolean operator used error. BC_ERR_POSIX_BOOL, /// POSIX relation operator used outside if, while, or for statements error. BC_ERR_POSIX_REL_POS, /// Multiple POSIX relation operators used in an if, while, or for statement /// error. BC_ERR_POSIX_MULTIREL, /// Empty statements in POSIX for loop error. BC_ERR_POSIX_FOR, /// Non-POSIX exponential (scientific or engineering) number used error. BC_ERR_POSIX_EXP_NUM, /// Non-POSIX array reference error. BC_ERR_POSIX_REF, /// Non-POSIX void error. BC_ERR_POSIX_VOID, /// Non-POSIX brace position used error. BC_ERR_POSIX_BRACE, /// String used in expression. BC_ERR_POSIX_EXPR_STRING, #endif // BC_ENABLED // Number of elements. BC_ERR_NELEMS, #if BC_ENABLED /// A marker for the start of POSIX errors. BC_ERR_POSIX_START = BC_ERR_POSIX_NAME_LEN, /// A marker for the end of POSIX errors. BC_ERR_POSIX_END = BC_ERR_POSIX_EXPR_STRING, #endif // BC_ENABLED } BcErr; // The indices of each category of error in bc_errs[], and used in bc_err_ids[] // to associate actual errors with their categories. /// Math error category. #define BC_ERR_IDX_MATH (0) /// Parse (and lex) error category. #define BC_ERR_IDX_PARSE (1) /// Runtime error category. #define BC_ERR_IDX_EXEC (2) /// Fatal error category. #define BC_ERR_IDX_FATAL (3) /// Number of categories. #define BC_ERR_IDX_NELEMS (4) // If bc is enabled, we add an extra category for POSIX warnings. #if BC_ENABLED /// POSIX warning category. #define BC_ERR_IDX_WARN (BC_ERR_IDX_NELEMS) #endif // BC_ENABLED /// Do a longjmp(). This is what to use when activating an "exception", i.e., a /// longjmp(). With debug code, it will print the name of the function it jumped /// from. #if BC_DEBUG_CODE #define BC_JMP bc_vm_jmp(__func__) #else // BC_DEBUG_CODE #define BC_JMP bc_vm_jmp() #endif // BC_DEBUG_CODE /// Returns true if an exception is in flight, false otherwise. #define BC_SIG_EXC \ BC_UNLIKELY(vm.status != (sig_atomic_t) BC_STATUS_SUCCESS || vm.sig) /// Returns true if there is *no* exception in flight, false otherwise. #define BC_NO_SIG_EXC \ BC_LIKELY(vm.status == (sig_atomic_t) BC_STATUS_SUCCESS && !vm.sig) #ifndef NDEBUG /// Assert that signals are locked. There are non-async-signal-safe functions in /// bc, and they *must* have signals locked. Other functions are expected to /// *not* have signals locked, for reasons. So this is a pre-built assert /// (no-op in non-debug mode) that check that signals are locked. #define BC_SIG_ASSERT_LOCKED do { assert(vm.sig_lock); } while (0) /// Assert that signals are unlocked. There are non-async-signal-safe functions /// in bc, and they *must* have signals locked. Other functions are expected to /// *not* have signals locked, for reasons. So this is a pre-built assert /// (no-op in non-debug mode) that check that signals are unlocked. #define BC_SIG_ASSERT_NOT_LOCKED do { assert(vm.sig_lock == 0); } while (0) #else // NDEBUG /// Assert that signals are locked. There are non-async-signal-safe functions in /// bc, and they *must* have signals locked. Other functions are expected to /// *not* have signals locked, for reasons. So this is a pre-built assert /// (no-op in non-debug mode) that check that signals are locked. #define BC_SIG_ASSERT_LOCKED /// Assert that signals are unlocked. There are non-async-signal-safe functions /// in bc, and they *must* have signals locked. Other functions are expected to /// *not* have signals locked, for reasons. So this is a pre-built assert /// (no-op in non-debug mode) that check that signals are unlocked. #define BC_SIG_ASSERT_NOT_LOCKED #endif // NDEBUG /// Locks signals. #define BC_SIG_LOCK \ do { \ BC_SIG_ASSERT_NOT_LOCKED; \ vm.sig_lock = 1; \ } while (0) /// Unlocks signals. If a signal happened, then this will cause a jump. #define BC_SIG_UNLOCK \ do { \ BC_SIG_ASSERT_LOCKED; \ vm.sig_lock = 0; \ if (vm.sig) BC_JMP; \ } while (0) /// Locks signals, regardless of if they are already locked. This is really only /// used after labels that longjmp() goes to after the jump because the cleanup /// code must have signals locked, and BC_LONGJMP_CONT will unlock signals if it /// doesn't jump. #define BC_SIG_MAYLOCK \ do { \ vm.sig_lock = 1; \ } while (0) /// Unlocks signals, regardless of if they were already unlocked. If a signal /// happened, then this will cause a jump. #define BC_SIG_MAYUNLOCK \ do { \ vm.sig_lock = 0; \ if (vm.sig) BC_JMP; \ } while (0) /* * Locks signals, but stores the old lock state, to be restored later by * BC_SIG_TRYUNLOCK. * @param v The variable to store the old lock state to. */ #define BC_SIG_TRYLOCK(v) \ do { \ v = vm.sig_lock; \ vm.sig_lock = 1; \ } while (0) /* Restores the previous state of a signal lock, and if it is now unlocked, * initiates an exception/jump. * @param v The old lock state. */ #define BC_SIG_TRYUNLOCK(v) \ do { \ vm.sig_lock = (v); \ if (!(v) && vm.sig) BC_JMP; \ } while (0) /** * Sets a jump, and sets it up as well so that if a longjmp() happens, bc will * immediately goto a label where some cleanup code is. This one assumes that * signals are not locked and will lock them, set the jump, and unlock them. * Setting the jump also includes pushing the jmp_buf onto the jmp_buf stack. * This grows the jmp_bufs vector first to prevent a fatal error from happening * after the setjmp(). This is done because BC_SETJMP(l) is assumed to be used * *before* the actual initialization calls that need the setjmp(). * param l The label to jump to on a longjmp(). */ #define BC_SETJMP(l) \ do { \ sigjmp_buf sjb; \ BC_SIG_LOCK; \ bc_vec_grow(&vm.jmp_bufs, 1); \ if (sigsetjmp(sjb, 0)) { \ assert(BC_SIG_EXC); \ goto l; \ } \ bc_vec_push(&vm.jmp_bufs, &sjb); \ BC_SIG_UNLOCK; \ } while (0) /** * Sets a jump like BC_SETJMP, but unlike BC_SETJMP, it assumes signals are * locked and will just set the jump. This does *not* have a call to * bc_vec_grow() because it is assumed that BC_SETJMP_LOCKED(l) is used *after* * the initializations that need the setjmp(). * param l The label to jump to on a longjmp(). */ #define BC_SETJMP_LOCKED(l) \ do { \ sigjmp_buf sjb; \ BC_SIG_ASSERT_LOCKED; \ if (sigsetjmp(sjb, 0)) { \ assert(BC_SIG_EXC); \ goto l; \ } \ bc_vec_push(&vm.jmp_bufs, &sjb); \ } while (0) /// Used after cleanup labels set by BC_SETJMP and BC_SETJMP_LOCKED to jump to /// the next place. This is what continues the stack unwinding. This basically /// copies BC_SIG_UNLOCK into itself, but that is because its condition for /// jumping is BC_SIG_EXC, not just that a signal happened. #define BC_LONGJMP_CONT \ do { \ BC_SIG_ASSERT_LOCKED; \ if (!vm.sig_pop) bc_vec_pop(&vm.jmp_bufs); \ vm.sig_lock = 0; \ if (BC_SIG_EXC) BC_JMP; \ } while (0) /// Unsets a jump. It always assumes signals are locked. This basically just /// pops a jmp_buf off of the stack of jmp_bufs, and since the jump mechanism /// always jumps to the location at the top of the stack, this effectively /// undoes a setjmp(). #define BC_UNSETJMP \ do { \ BC_SIG_ASSERT_LOCKED; \ bc_vec_pop(&vm.jmp_bufs); \ } while (0) /// Stops a stack unwinding. Technically, a stack unwinding needs to be done /// manually, but it will always be done unless certain flags are cleared. This /// clears the flags. #define BC_LONGJMP_STOP \ do { \ vm.sig_pop = 0; \ vm.sig = 0; \ } while (0) // Various convenience macros for calling the bc's error handling routine. #if BC_ENABLE_LIBRARY /** * Call bc's error handling routine. * @param e The error. * @param l The line of the script that the error happened. * @param ... Extra arguments for error messages as necessary. */ #define bc_error(e, l, ...) (bc_vm_handleError((e))) /** * Call bc's error handling routine. * @param e The error. */ #define bc_err(e) (bc_vm_handleError((e))) /** * Call bc's error handling routine. * @param e The error. */ #define bc_verr(e, ...) (bc_vm_handleError((e))) #else // BC_ENABLE_LIBRARY /** * Call bc's error handling routine. * @param e The error. * @param l The line of the script that the error happened. * @param ... Extra arguments for error messages as necessary. */ #define bc_error(e, l, ...) (bc_vm_handleError((e), (l), __VA_ARGS__)) /** * Call bc's error handling routine. * @param e The error. */ #define bc_err(e) (bc_vm_handleError((e), 0)) /** * Call bc's error handling routine. * @param e The error. */ #define bc_verr(e, ...) (bc_vm_handleError((e), 0, __VA_ARGS__)) #endif // BC_ENABLE_LIBRARY /** * Returns true if status @a s is an error, false otherwise. * @param s The status to test. * @return True if @a s is an error, false otherwise. */ #define BC_STATUS_IS_ERROR(s) \ ((s) >= BC_STATUS_ERROR_MATH && (s) <= BC_STATUS_ERROR_FATAL) // Convenience macros that can be placed at the beginning and exits of functions // for easy marking of where functions are entered and exited. #if BC_DEBUG_CODE #define BC_FUNC_ENTER \ do { \ size_t bc_func_enter_i; \ for (bc_func_enter_i = 0; bc_func_enter_i < vm.func_depth; \ ++bc_func_enter_i) \ { \ bc_file_puts(&vm.ferr, bc_flush_none, " "); \ } \ vm.func_depth += 1; \ bc_file_printf(&vm.ferr, "Entering %s\n", __func__); \ bc_file_flush(&vm.ferr, bc_flush_none); \ } while (0); #define BC_FUNC_EXIT \ do { \ size_t bc_func_enter_i; \ vm.func_depth -= 1; \ for (bc_func_enter_i = 0; bc_func_enter_i < vm.func_depth; \ ++bc_func_enter_i) \ { \ bc_file_puts(&vm.ferr, bc_flush_none, " "); \ } \ bc_file_printf(&vm.ferr, "Leaving %s\n", __func__); \ bc_file_flush(&vm.ferr, bc_flush_none); \ } while (0); #else // BC_DEBUG_CODE #define BC_FUNC_ENTER #define BC_FUNC_EXIT #endif // BC_DEBUG_CODE #endif // BC_STATUS_H