Affix
view release on metacpan or search on metacpan
infix/src/core/error.c view on Meta::CPAN
/**
* Copyright (c) 2025 Sanko Robinson
*
* This source code is dual-licensed under the Artistic License 2.0 or the MIT License.
* You may choose to use this code under the terms of either license.
*
* SPDX-License-Identifier: (Artistic-2.0 OR MIT)
*
* The documentation blocks within this file are licensed under the
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file error.c
* @brief Implements the thread-local error reporting system.
* @ingroup internal_core
*
* @details This module provides the infrastructure for robust and thread-safe error
* handling within the `infix` library.
*
* The core principle is that all detailed error information is stored in
* **thread-local storage (TLS)**. This means that an error occurring in one thread
* will never interfere with or be accidentally reported by an operation in another
* thread.
*
* The workflow is as follows:
* 1. Every public API function calls `_infix_clear_error()` upon entry to reset the
* error state for the current thread.
* 2. If an internal function encounters an error, it calls `_infix_set_error()` or
* `_infix_set_system_error()` to record detailed diagnostic information, including
* an error code, category, and a descriptive message.
* 3. For parser errors, `_infix_set_error()` generates a rich, multi-line diagnostic
* message with a code snippet and a caret pointing to the error location, similar
* to a compiler error.
* 4. The user can call the public `infix_get_last_error()` function at any time to
* retrieve a copy of the last error that occurred on their thread.
*/
#include "common/infix_internals.h"
#include <infix/infix.h>
#include <stdarg.h>
#include <stdio.h> // For snprintf
#include <string.h>
// Use a portable mechanism for thread-local storage (TLS).
// The order of checks is critical for cross-platform compatibility.
#if defined(INFIX_OS_OPENBSD)
// OpenBSD has known issues with TLS cleanup in some linking scenarios (segfault on exit).
// Disable TLS entirely on this platform to ensure stability, at the cost of thread-safety.
#define INFIX_TLS
#elif defined(INFIX_COMPILER_MSVC)
// Microsoft Visual C++
#define INFIX_TLS __declspec(thread)
#elif defined(INFIX_OS_WINDOWS) && defined(INFIX_COMPILER_CLANG)
// Clang on Windows: check if behaving like MSVC or GCC.
// If using MSVC codegen/headers, use declspec.
#define INFIX_TLS __declspec(thread)
#elif defined(INFIX_COMPILER_GCC)
// MinGW (GCC on Windows) and standard GCC/Clang on *nix.
// MinGW prefers __thread or _Thread_local over __declspec(thread).
#define INFIX_TLS __thread
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
// Fallback to C11 standard
#define INFIX_TLS _Thread_local
#else
// Fallback for compilers that do not support TLS. This is not thread-safe.
#warning "Compiler does not support thread-local storage; error handling will not be thread-safe."
#define INFIX_TLS
#endif
// A portable macro for safe string copying to prevent buffer overflows.
#if defined(INFIX_COMPILER_MSVC)
#define _INFIX_SAFE_STRNCPY(dest, src, count) strncpy_s(dest, sizeof(dest), src, count)
#else
#define _INFIX_SAFE_STRNCPY(dest, src, count) \
do { \
strncpy(dest, src, (count)); \
(dest)[(sizeof(dest)) - 1] = '\0'; \
} while (0)
#endif
/**
* @var g_infix_last_error
* @brief The thread-local variable that stores the details of the last error.
*
* @details Each thread gets its own independent instance of this variable. It is
* initialized to a "no error" state.
*/
static INFIX_TLS infix_error_details_t g_infix_last_error = {INFIX_CATEGORY_NONE, INFIX_CODE_SUCCESS, 0, 0, {0}};
/**
* @var g_infix_last_signature_context
* @brief A thread-local pointer to the full signature string being parsed.
*
* @details This is set by the high-level API functions (`infix_type_from_signature`, etc.)
* before parsing begins. If a parser error occurs, `_infix_set_error` uses this
infix/src/core/error.c view on Meta::CPAN
size_t caret_pos = position - start + start_indicator_len;
snprintf(pointer, sizeof(pointer), "%*s^", (int)caret_pos, "");
// Build the final multi-line message piece by piece to avoid buffer overflows.
char * p = g_infix_last_error.message;
size_t remaining = sizeof(g_infix_last_error.message);
int written;
// Write the snippet and pointer lines.
written = snprintf(p, remaining, "\n\n %s\n %s", snippet, pointer);
if (written < 0 || (size_t)written >= remaining) {
// Fallback to a simple message on snprintf failure or buffer overflow.
const char * msg = _get_error_message_for_code(code);
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, msg, sizeof(g_infix_last_error.message) - 1);
return;
}
p += written;
remaining -= written;
// Append the standard error description.
snprintf(p, remaining, "\n\nError: %s", _get_error_message_for_code(code));
}
else {
// For non-parser errors, just copy the standard message.
const char * msg = _get_error_message_for_code(code);
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, msg, sizeof(g_infix_last_error.message) - 1);
}
}
/**
* @internal
* @brief Sets a detailed system error with a platform-specific error code and message.
*
* @details This is used for errors originating from OS-level functions like `dlopen`,
* `mmap`, or `VirtualAlloc`. It records both the `infix` error code and the
* underlying system error code (`errno` or `GetLastError`).
*
* @param category The category of the error.
* @param code The `infix` error code that corresponds to the failure.
* @param system_code The OS-specific error code (e.g., from `errno` or `GetLastError`).
* @param msg An optional custom message from the OS (e.g., from `dlerror`). If `nullptr`, the default message for
* `code` is used.
*/
void _infix_set_system_error(infix_error_category_t category,
infix_error_code_t code,
long system_code,
const char * msg) {
g_infix_last_error.category = category;
g_infix_last_error.code = code;
g_infix_last_error.position = 0;
g_infix_last_error.system_error_code = system_code;
if (msg)
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, msg, sizeof(g_infix_last_error.message) - 1);
else {
const char * default_msg = _get_error_message_for_code(code);
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, default_msg, sizeof(g_infix_last_error.message) - 1);
}
}
/**
* @internal
* @brief Resets the error state for the current thread to "no error".
*
* This should be called at the beginning of every public API function to ensure
* that a prior error from an unrelated call on the same thread is not accidentally
* returned to the user.
*/
void _infix_clear_error(void) {
g_infix_last_error.category = INFIX_CATEGORY_NONE;
g_infix_last_error.code = INFIX_CODE_SUCCESS;
g_infix_last_error.position = 0;
g_infix_last_error.system_error_code = 0;
g_infix_last_error.message[0] = '\0';
g_infix_last_signature_context = nullptr;
}
/**
* @brief Retrieves detailed information about the last error that occurred on the current thread.
* @return A copy of the last error details structure. This function is thread-safe.
*/
INFIX_API infix_error_details_t infix_get_last_error(void) { return g_infix_last_error; }
( run in 0.466 second using v1.01-cache-2.11-cpan-39bf76dae61 )