diff --git a/core/error.c b/core/error.c new file mode 100644 index 0000000..f6da334 --- /dev/null +++ b/core/error.c @@ -0,0 +1,1036 @@ +#include "error.h" + +#include +#include +#include +#include + +static void default_error_reporter( + const struct b_error *error, enum b_error_report_flags flags); +static void free_error(struct b_error *error); +static void do_free_error(struct b_error *error); + +static const struct b_error_vendor builtin_vendor; + +/* TODO protect these with locks */ +static struct b_queue free_errors = B_QUEUE_INIT; +static struct b_queue allocated_errors = B_QUEUE_INIT; +static b_error_report_function error_reporter = default_error_reporter; +static enum b_error_report_flags error_reporter_flags = B_ERROR_REPORT_DEFAULT; + +static void error_cleanup(void) +{ + struct b_queue_entry *entry = b_queue_pop_back(&allocated_errors); + while (entry) { + struct b_error *error = b_unbox(struct b_error, entry, err_entry); + do_free_error(error); + free(error); + + entry = b_queue_pop_back(&allocated_errors); + } + + entry = b_queue_pop_back(&free_errors); + while (entry) { + struct b_error *error = b_unbox(struct b_error, entry, err_entry); + free(error); + + entry = b_queue_pop_back(&free_errors); + } +} + +const struct b_error_vendor *b_error_vendor_get_builtin(void) +{ + return &builtin_vendor; +} + +const struct b_error_definition *b_error_vendor_get_error_definition( + const struct b_error_vendor *vendor, b_error_status_code code) +{ + const struct b_error_definition *error_def = NULL; + + if (vendor->v_status_get_definition) { + error_def = vendor->v_status_get_definition(vendor, code); + } + + if (error_def || !vendor->v_error_definitions) { + return error_def; + } + + size_t nr_errors = vendor->v_error_definitions_length / sizeof *error_def; + if (code >= 0 && code < nr_errors) { + error_def = &vendor->v_error_definitions[code]; + } + + if (error_def && !error_def->err_name) { + return NULL; + } + + return error_def; +} + +const char *b_error_vendor_get_status_code_name( + const struct b_error_vendor *vendor, b_error_status_code code) +{ + const struct b_error_definition *error_def + = b_error_vendor_get_error_definition(vendor, code); + return error_def ? error_def->err_name : NULL; +} + +const char *b_error_vendor_get_status_code_description( + const struct b_error_vendor *vendor, b_error_status_code code) +{ + const struct b_error_definition *error_def + = b_error_vendor_get_error_definition(vendor, code); + return error_def ? error_def->err_message : NULL; +} + +const struct b_error_msg *b_error_vendor_get_msg( + const struct b_error_vendor *vendor, b_error_msg_id msg_id) +{ + const struct b_error_msg *msg = NULL; + + if (vendor->v_msg_get_definition) { + msg = vendor->v_msg_get_definition(vendor, msg_id); + } + + if (msg || !vendor->v_msg) { + return msg; + } + + size_t nr_msg = vendor->v_msg_length / sizeof *msg; + if (msg_id < nr_msg) { + msg = &vendor->v_msg[msg_id]; + } + + if (msg && !msg->msg_message) { + return NULL; + } + + return msg; +} + +static struct b_error *allocate_error(void) +{ + if (b_queue_empty(&allocated_errors) && b_queue_empty(&free_errors)) { + atexit(error_cleanup); + } + + struct b_queue_entry *entry = b_queue_pop_back(&free_errors); + if (entry) { + struct b_error *error = b_unbox(struct b_error, entry, err_entry); + memset(error, 0x0, sizeof *error); + b_queue_push_back(&allocated_errors, entry); + return error; + } + + struct b_error *error = malloc(sizeof *error); + if (!error) { + return NULL; + } + + memset(error, 0x0, sizeof *error); + + b_queue_push_back(&allocated_errors, &error->err_entry); + return error; +} + +static void do_free_error(struct b_error *error) +{ + struct b_queue_entry *entry = b_queue_pop_back(&error->err_submsg); + while (entry) { + struct b_error_submsg *msg + = b_unbox(struct b_error_submsg, entry, msg_entry); + + if (msg->msg_content) { + free(msg->msg_content); + } + + free(msg); + entry = b_queue_pop_back(&error->err_submsg); + } + + entry = b_queue_pop_back(&error->err_stack); + while (entry) { + struct b_error_stack_frame *frame + = b_unbox(struct b_error_stack_frame, entry, f_entry); + + free(frame); + entry = b_queue_pop_back(&error->err_stack); + } + + size_t nr_param = sizeof error->err_params / sizeof error->err_params[0]; + for (size_t i = 0; i < nr_param; i++) { + const struct b_error_template_parameter_definition *param_def + = error->err_params[i].__param_def; + + if (!param_def) { + continue; + } + + if (param_def->param_type == B_ERROR_TEMPLATE_PARAM_STRING) { + free((void *)error->err_params[i].param_value); + } + } + + if (error->err_description) { + free(error->err_description); + } + + if (error->err_caused_by) { + free_error(error->err_caused_by); + } +} + +static void free_error(struct b_error *error) +{ + b_queue_delete(&allocated_errors, &error->err_entry); + + do_free_error(error); + + b_queue_push_back(&free_errors, &error->err_entry); +} + +struct b_error *z__b_error_create_string( + const b_error_vendor *vendor, b_error_status_code code, + struct b_error *caused_by, const char *file, unsigned int line, + const char *func, const char *description, va_list arg) +{ + struct b_error *error = allocate_error(); + if (!error) { + return NULL; + } + + error->err_vendor = vendor; + error->err_code = code; + error->err_caused_by = caused_by; + + if (description) { + b_stringstream desc; + b_stringstream_begin_dynamic(&desc); + b_stringstream_addvf(&desc, description, arg); + error->err_description = b_stringstream_end(&desc); + } + + return z__b_error_propagate(error, file, line, func); +} + +struct b_error *z__b_error_create_msg( + const struct b_error_vendor *vendor, b_error_status_code code, + struct b_error *caused_by, const char *file, unsigned int line, + const char *func, b_error_msg_id msg_id, + const b_error_template_parameter params[]) +{ + const struct b_error_definition *error_def + = b_error_vendor_get_error_definition(vendor, code); + if (!error_def) { + fprintf(stderr, "Undefined %s error %lu\n", vendor->v_name, code); + abort(); + } + + const struct b_error_msg *msg = b_error_vendor_get_msg(vendor, msg_id); + if (!msg) { + fprintf(stderr, "Undefined %s error msg %lu\n", vendor->v_name, + msg_id); + abort(); + } + + struct b_error *error = allocate_error(); + if (!error) { + return NULL; + } + + error->err_vendor = vendor; + error->err_code = code; + error->err_msg = msg; + error->err_caused_by = caused_by; + + for (size_t i = 0; params[i].param_name; i++) { + const struct b_error_template_parameter *param = ¶ms[i]; + + const struct b_error_template_parameter_definition *param_def + = b_error_msg_get_template_parameter(msg, param->param_name); + if (!param_def) { + continue; + } + + memcpy(&error->err_params[i], param, sizeof *param); + error->err_params[i].__param_def = param_def; + if (param_def->param_type == B_ERROR_TEMPLATE_PARAM_STRING) { + const char *s + = (const char *)error->err_params[i].param_value; + size_t len = strlen(s); + char *s2 = malloc(len + 1); + if (!s2) { + error->err_params[i].param_value = 0; + continue; + } + + memcpy(s2, s, len); + s2[len] = 0; + + error->err_params[i].param_value = (uintptr_t)s2; + } + } + + return z__b_error_propagate(error, file, line, func); +} + +struct b_error *z__b_error_create_template( + const struct b_error_vendor *vendor, b_error_status_code code, + struct b_error *caused_by, const char *file, unsigned int line, + const char *func, const b_error_template_parameter params[]) +{ + const struct b_error_definition *error_def + = b_error_vendor_get_error_definition(vendor, code); + if (!error_def) { + fprintf(stderr, "Undefined %s error %lu\n", vendor->v_name, code); + return NULL; + } + + struct b_error *error = allocate_error(); + if (!error) { + return NULL; + } + + error->err_vendor = vendor; + error->err_code = code; + error->err_caused_by = caused_by; + + for (size_t i = 0; params[i].param_name; i++) { + const struct b_error_template_parameter *param = ¶ms[i]; + + const struct b_error_template_parameter_definition *param_def + = b_error_definition_get_template_parameter( + error_def, param->param_name); + if (!param_def) { + continue; + } + + memcpy(&error->err_params[i], param, sizeof *param); + error->err_params[i].__param_def = param_def; + if (param_def->param_type == B_ERROR_TEMPLATE_PARAM_STRING) { + const char *s + = (const char *)error->err_params[i].param_value; + size_t len = strlen(s); + char *s2 = malloc(len + 1); + if (!s2) { + error->err_params[i].param_value = 0; + continue; + } + + memcpy(s2, s, len); + s2[len] = 0; + + error->err_params[i].param_value = (uintptr_t)s2; + } + } + + return z__b_error_propagate(error, file, line, func); +} + +struct b_error *z__b_error_propagate( + struct b_error *error, const char *file, unsigned int line, + const char *function) +{ + if (!error) { + return error; + } + + if (!file && !function && line == 0) { + return error; + } + + struct b_error_stack_frame *frame = malloc(sizeof *frame); + if (!frame) { + return error; + } + + frame->f_file = file; + frame->f_line_number = line; + frame->f_function = function; + + b_queue_push_back(&error->err_stack, &frame->f_entry); + + return error; +} + +struct b_error *z__b_error_caused_by(struct b_error *error, struct b_error *caused_by) +{ + error->err_caused_by = caused_by; + return error; +} + +struct b_error *z__b_error_caused_by_b_status( + struct b_error *error, enum b_status status) +{ + error->err_caused_by = z__b_error_create( + &builtin_vendor, status, NULL, NULL, 0, NULL, NULL); + return error; +} + +enum b_status b_error_add_submsg_string( + struct b_error *error, b_error_submsg_type type, const char *msg, ...) +{ + struct b_error_submsg *submsg = malloc(sizeof *submsg); + if (!submsg) { + return B_ERR_NO_MEMORY; + } + + char buf[512]; + va_list arg; + va_start(arg, msg); + size_t len = vsnprintf(buf, sizeof buf, msg, arg); + va_end(arg); + + if (len >= sizeof buf) { + len = sizeof buf - 1; + } + + char *content = malloc(len + 1); + + if (!content) { + free(submsg); + return B_ERR_NO_MEMORY; + } + + memcpy(content, buf, len); + content[len] = 0; + + submsg->msg_type = type; + submsg->msg_content = content; + + b_queue_push_back(&error->err_submsg, &submsg->msg_entry); + + return B_SUCCESS; +} + +enum b_status z__b_error_add_submsg_template( + struct b_error *error, enum b_error_submsg_type type, + b_error_msg_id msg_id, struct b_error_template_parameter params[]) +{ + const struct b_error_msg *msg + = b_error_vendor_get_msg(error->err_vendor, msg_id); + if (!msg) { + fprintf(stderr, "Undefined %s error message %lu\n", + error->err_vendor->v_name, msg_id); + abort(); + } + + struct b_error_submsg *submsg = malloc(sizeof *submsg); + if (!submsg) { + return B_ERR_NO_MEMORY; + } + + submsg->msg_type = type; + submsg->msg_msg = msg; + + for (size_t i = 0; params[i].param_name; i++) { + const struct b_error_template_parameter *param = ¶ms[i]; + + const struct b_error_template_parameter_definition *param_def + = b_error_msg_get_template_parameter(msg, param->param_name); + if (!param_def) { + continue; + } + + memcpy(&submsg->msg_params[i], param, sizeof *param); + submsg->msg_params[i].__param_def = param_def; + if (param_def->param_type == B_ERROR_TEMPLATE_PARAM_STRING) { + const char *s + = (const char *)submsg->msg_params[i].param_value; + size_t len = strlen(s); + char *s2 = malloc(len + 1); + if (!s2) { + submsg->msg_params[i].param_value = 0; + continue; + } + + memcpy(s2, s, len); + s2[len] = 0; + + submsg->msg_params[i].param_value = (uintptr_t)s2; + } + } + + b_queue_push_back(&error->err_submsg, &submsg->msg_entry); + + return B_SUCCESS; +} + +void b_error_release(struct b_error *error) +{ + struct b_queue_entry *entry = b_queue_pop_back(&error->err_submsg); + while (entry) { + struct b_error_submsg *msg + = b_unbox(struct b_error_submsg, entry, msg_entry); + + if (msg->msg_content) { + free(msg->msg_content); + } + + free(msg); + entry = b_queue_pop_back(&error->err_submsg); + } + + if (error->err_description) { + free(error->err_description); + } + + free(error); +} + +void z__b_error_throw( + struct b_error *error, const char *file, unsigned int line, const char *func) +{ + if (!error) { + return; + } + + error = z__b_error_propagate(error, file, line, func); + error_reporter(error, error_reporter_flags); + free_error(error); +} + +b_error_status_code b_error_get_status_code(const struct b_error *error) +{ + return error ? error->err_code : 0; +} + +const struct b_error_vendor *b_error_get_vendor(const struct b_error *error) +{ + return error->err_vendor; +} + +const struct b_error_definition *b_error_get_definition(const struct b_error *error) +{ + return error->err_def; +} + +const struct b_error_template_parameter *b_error_get_template_parameter( + const struct b_error *error, const char *param_name) +{ + const size_t nr_params + = sizeof error->err_params / sizeof error->err_params[0]; + for (size_t i = 0; i < nr_params; i++) { + if (!error->err_params[i].param_name) { + break; + } + + if (!strcmp(error->err_params[i].param_name, param_name)) { + return &error->err_params[i]; + } + } + + return NULL; +} + +const struct b_error_template_parameter *b_error_get_template_parameters( + const struct b_error *error) +{ + return error->err_params; +} + +const char *b_error_get_description(const struct b_error *error) +{ + return error->err_description; +} + +const struct b_error_msg *b_error_get_msg(const b_error *error) +{ + return error->err_msg; +} + +const struct b_error_submsg *b_error_get_first_submsg(const struct b_error *error) +{ + struct b_queue_entry *entry = b_queue_first(&error->err_submsg); + if (!entry) { + return NULL; + } + + return b_unbox(struct b_error_submsg, entry, msg_entry); +} + +const struct b_error_submsg *b_error_get_next_submsg( + const struct b_error *error, const struct b_error_submsg *msg) +{ + struct b_queue_entry *entry = b_queue_next(&msg->msg_entry); + if (!entry) { + return NULL; + } + + return b_unbox(struct b_error_submsg, entry, msg_entry); +} + +const struct b_error_stack_frame *b_error_get_first_stack_frame( + const struct b_error *error) +{ + struct b_queue_entry *entry = b_queue_first(&error->err_stack); + if (!entry) { + return NULL; + } + + return b_unbox(struct b_error_stack_frame, entry, f_entry); +} + +const struct b_error_stack_frame *b_error_get_next_stack_frame( + const struct b_error *error, const struct b_error_stack_frame *frame) +{ + struct b_queue_entry *entry = b_queue_next(&frame->f_entry); + if (!entry) { + return NULL; + } + + return b_unbox(struct b_error_stack_frame, entry, f_entry); +} + +const b_error *b_error_get_caused_by(const struct b_error *error) +{ + return error->err_caused_by; +} + +enum b_error_submsg_type b_error_submsg_get_type(const struct b_error_submsg *msg) +{ + return msg->msg_type; +} + +const char *b_error_submsg_get_content(const struct b_error_submsg *msg) +{ + return msg->msg_content; +} + +const struct b_error_msg *b_error_submsg_get_msg(const b_error_submsg *msg) +{ + return msg->msg_msg; +} + +const struct b_error_template_parameter *b_error_submsg_get_template_parameters( + const struct b_error_submsg *msg) +{ + return msg->msg_params; +} + +const char *b_error_stack_frame_get_filepath(const struct b_error_stack_frame *frame) +{ + return frame->f_file; +} + +unsigned int b_error_stack_frame_get_line_number( + const struct b_error_stack_frame *frame) +{ + return frame->f_line_number; +} + +const char *b_error_stack_frame_get_function_name( + const struct b_error_stack_frame *frame) +{ + return frame->f_function; +} + +const struct b_error_template_parameter_definition *b_error_definition_get_template_parameter( + const struct b_error_definition *error_def, const char *param_name) +{ + size_t nr_param + = sizeof error_def->err_params / sizeof error_def->err_params[0]; + + for (size_t i = 0; i < nr_param; i++) { + if (!error_def->err_params[i].param_name) { + break; + } + + if (!strcmp(error_def->err_params[i].param_name, param_name)) { + return &error_def->err_params[i]; + } + } + + return NULL; +} + +const char *b_error_msg_get_content(const struct b_error_msg *msg) +{ + return msg->msg_message; +} + +const struct b_error_template_parameter_definition *b_error_msg_get_template_parameter( + const struct b_error_msg *msg, const char *param_name) +{ + size_t nr_param = sizeof msg->msg_params / sizeof msg->msg_params[0]; + + for (size_t i = 0; i < nr_param; i++) { + if (!msg->msg_params[i].param_name) { + break; + } + + if (!strcmp(msg->msg_params[i].param_name, param_name)) { + return &msg->msg_params[i]; + } + } + + return NULL; +} + +void b_set_error_report_function( + b_error_report_function func, enum b_error_report_flags flags) +{ + if (!func) { + func = default_error_reporter; + } + + if (!flags) { + flags = B_ERROR_REPORT_DEFAULT; + } + + error_reporter = func; + error_reporter_flags = flags; +} + +static const struct b_error_definition builtin_errors[] = { + B_ERROR_DEFINITION(B_SUCCESS, "SUCCESS", "Success"), + B_ERROR_DEFINITION(B_ERR_NO_MEMORY, "NO_MEMORY", "Out of memory"), + B_ERROR_DEFINITION( + B_ERR_OUT_OF_BOUNDS, "OUT_OF_BOUNDS", "Argument out of bounds"), + B_ERROR_DEFINITION( + B_ERR_INVALID_ARGUMENT, "INVALID_ARGUMENT", "Invalid argument"), + B_ERROR_DEFINITION( + B_ERR_NAME_EXISTS, "NAME_EXISTS", "Name already exists"), + B_ERROR_DEFINITION( + B_ERR_NOT_SUPPORTED, "NOT_SUPPORTED", "Operation not supported"), + B_ERROR_DEFINITION(B_ERR_BAD_STATE, "BAD_STATE", "Bad state"), + B_ERROR_DEFINITION(B_ERR_NO_ENTRY, "NO_ENTRY", "Name does not exist"), + B_ERROR_DEFINITION(B_ERR_NO_DATA, "NO_DATA", "No data available"), + B_ERROR_DEFINITION(B_ERR_NO_SPACE, "NO_DATA", "No space available"), + B_ERROR_DEFINITION( + B_ERR_UNKNOWN_FUNCTION, "UNKNOWN_FUNCTION", "Unknown function"), + B_ERROR_DEFINITION(B_ERR_BAD_FORMAT, "BAD_FORMAT", "Bad format"), + B_ERROR_DEFINITION(B_ERR_IO_FAILURE, "IO_FAILURE", "I/O failure"), + B_ERROR_DEFINITION( + B_ERR_IS_DIRECTORY, "IS_DIRECTORY", "Item is a directory"), + B_ERROR_DEFINITION( + B_ERR_NOT_DIRECTORY, "NOT_DIRECTORY", "Item is not a directory"), + B_ERROR_DEFINITION( + B_ERR_PERMISSION_DENIED, "PERMISSION_DENIED", + "Permission denied"), + B_ERROR_DEFINITION(B_ERR_BUSY, "BUSY", "Resource busy or locked"), +}; + +static const struct b_error_vendor builtin_vendor = { + .v_name = "Blue", + .v_error_definitions = builtin_errors, + .v_error_definitions_length = sizeof builtin_errors, +}; + +static void get_error_id( + const struct b_error_vendor *vendor, const struct b_error *error, + char *out, size_t max) +{ + const char *vendor_name = NULL; + const char *error_name = NULL; + b_error_status_code code = b_error_get_status_code(error); + + if (vendor) { + vendor_name = vendor->v_name; + error_name = b_error_vendor_get_status_code_name(vendor, code); + } + + b_stringstream id; + b_stringstream_begin(&id, out, max); + + if (vendor_name) { + b_stringstream_add(&id, vendor_name); + } + + if (error_name) { + if (vendor_name) { + b_stringstream_add(&id, "."); + } + + b_stringstream_add(&id, error_name); + } else { + if (vendor_name) { + b_stringstream_add(&id, "#"); + } + + b_stringstream_addf(&id, "%ld", code); + } +} + +static void print_template_parameter( + const struct b_error_template_parameter *params, size_t nr_params, + const char *param_name) +{ + const struct b_error_template_parameter *param = NULL; + + for (size_t i = 0; i < nr_params; i++) { + if (!params[i].param_name) { + break; + } + + if (!strcmp(params[i].param_name, param_name)) { + param = ¶ms[i]; + break; + } + } + + if (!param) { + return; + } + + const char *format = param->__param_def->param_format; + + switch (param->__param_def->param_type) { + case B_ERROR_TEMPLATE_PARAM_STRING: { + const char *s = (const char *)param->param_value; + fprintf(stderr, format, s); + break; + } + case B_ERROR_TEMPLATE_PARAM_CHAR: { + char v = (char)param->param_value; + fprintf(stderr, format, v); + break; + } + case B_ERROR_TEMPLATE_PARAM_INT: { + int v = (int)param->param_value; + fprintf(stderr, format, v); + break; + } + case B_ERROR_TEMPLATE_PARAM_UINT: { + unsigned int v = (unsigned int)param->param_value; + fprintf(stderr, format, v); + break; + } + case B_ERROR_TEMPLATE_PARAM_LONG: { + long v = (long)param->param_value; + fprintf(stderr, format, v); + break; + } + case B_ERROR_TEMPLATE_PARAM_ULONG: { + unsigned long v = (unsigned long)param->param_value; + fprintf(stderr, format, v); + break; + } + case B_ERROR_TEMPLATE_PARAM_LONGLONG: { + long long v = (long long)param->param_value; + fprintf(stderr, format, v); + break; + } + case B_ERROR_TEMPLATE_PARAM_ULONGLONG: { + unsigned long long v = (unsigned long long)param->param_value; + fprintf(stderr, format, v); + break; + } + case B_ERROR_TEMPLATE_PARAM_PTR: { + const void *p = (const void *)param->param_value; + fprintf(stderr, format, p); + break; + } + case B_ERROR_TEMPLATE_PARAM_INTPTR: { + intptr_t v = (intptr_t)param->param_value; + fprintf(stderr, format, v); + break; + } + case B_ERROR_TEMPLATE_PARAM_UINTPTR: { + uintptr_t v = (uintptr_t)param->param_value; + fprintf(stderr, format, v); + break; + } + default: + return; + } +} + +static void print_content( + const struct b_error_template_parameter *params, size_t nr_params, + const char *s) +{ + char modifier = 0; + char buf[128]; + size_t buf_len = 0; + + size_t i; + for (i = 0; s[i];) { + char c = s[i]; + + if (c != '@') { + fputc(c, stderr); + i++; + continue; + } + + i++; + modifier = s[i]; + + if (!modifier) { + break; + } + + if (modifier != '[' && modifier != '{') { + i++; + } + + if (s[i] == '@') { + fputc(c, stderr); + break; + } + + bool template_param; + char end = 0; + + switch (s[i]) { + case '{': + template_param = false; + end = '}'; + break; + case '[': + template_param = true; + end = ']'; + break; + default: + return; + } + + buf_len = 0; + i++; + while (s[i] && s[i] != end) { + buf[buf_len++] = s[i]; + buf[buf_len] = 0; + + i++; + } + + if (template_param) { + print_template_parameter(params, nr_params, buf); + } else { + fprintf(stderr, "%s", buf); + } + + if (s[i] != 0) { + i++; + } + } +} + +static void print_submsg(const struct b_error *error) +{ + const struct b_error_definition *error_def = b_error_get_definition(error); + const struct b_error_submsg *msg = b_error_get_first_submsg(error); + + while (msg) { + enum b_error_submsg_type type = b_error_submsg_get_type(msg); + const char *content = b_error_submsg_get_content(msg); + + fprintf(stderr, " > "); + + print_content( + msg->msg_params, + sizeof msg->msg_params / sizeof msg->msg_params[0], + content); + fprintf(stderr, "\n"); + + msg = b_error_get_next_submsg(error, msg); + } +} + +const char *get_short_filepath(const char *path) +{ + size_t len = strlen(path); + unsigned int sep_count = 0; + + for (size_t i = len - 1; i > 0; i--) { + if (path[i] != '/' && path[i] != '\\') { + continue; + } + + sep_count++; + if (sep_count == 2) { + return path + i + 1; + } + } + + return path; +} + +static void print_stack_trace(const struct b_error *error) +{ + const struct b_error_stack_frame *frame + = b_error_get_first_stack_frame(error); + while (frame) { + const char *file, *func; + unsigned int line; + + file = b_error_stack_frame_get_filepath(frame); + line = b_error_stack_frame_get_line_number(frame); + func = b_error_stack_frame_get_function_name(frame); + + file = get_short_filepath(file); + + fprintf(stderr, " at %s() (%s:%u)\n", func, file, line); + + frame = b_error_get_next_stack_frame(error, frame); + } +} + +static void report_error( + const b_error *error, b_error_report_flags flags, bool caused_by) +{ + const b_error_vendor *vendor = b_error_get_vendor(error); + + char error_id[128]; + get_error_id(vendor, error, error_id, sizeof error_id); + + const struct b_error_definition *error_def = b_error_get_definition(error); + b_error_status_code code = b_error_get_status_code(error); + const char *description = b_error_get_description(error); + + if (!description && vendor) { + description = b_error_vendor_get_status_code_description( + vendor, code); + } + + if (caused_by) { + fprintf(stderr, " -> caused by ERROR "); + } else { + fprintf(stderr, "==> ERROR "); + } + + if (flags & B_ERROR_REPORT_STATUS) { + fprintf(stderr, "%s", error_id); + } + + if (flags & B_ERROR_REPORT_DESCRIPTION) { + if (error->err_msg) { + fprintf(stderr, ": "); + print_content( + error->err_params, + sizeof error->err_params + / sizeof error->err_params[0], + error->err_msg->msg_message); + } else if (description) { + fprintf(stderr, ": "); + print_content( + error->err_params, + sizeof error->err_params + / sizeof error->err_params[0], + description); + } + } + + fprintf(stderr, "\n"); + + if (flags & B_ERROR_REPORT_SUBMSG) { + print_submsg(error); + } + + if (flags & B_ERROR_REPORT_STACK_TRACE) { + print_stack_trace(error); + } + + const struct b_error *cause = b_error_get_caused_by(error); + if (cause && (flags & B_ERROR_REPORT_CAUSE)) { + report_error(cause, flags, true); + } +} + +static void default_error_reporter( + const struct b_error *error, enum b_error_report_flags flags) +{ + report_error(error, flags, false); +} diff --git a/core/error.h b/core/error.h new file mode 100644 index 0000000..050fd72 --- /dev/null +++ b/core/error.h @@ -0,0 +1,36 @@ +#ifndef _BLUELIB_ERROR_H_ +#define _BLUELIB_ERROR_H_ + +#include + +struct b_error_stack_frame { + b_queue_entry f_entry; + const char *f_file; + unsigned int f_line_number; + const char *f_function; +}; + +struct b_error_submsg { + b_queue_entry msg_entry; + b_error_submsg_type msg_type; + char *msg_content; + const struct b_error_msg *msg_msg; + struct b_error_template_parameter msg_params[B_ERROR_TEMPLATE_PARAMETER_MAX]; +}; + +struct b_error { + const struct b_error_vendor *err_vendor; + b_error_status_code err_code; + + const struct b_error_definition *err_def; + const struct b_error_msg *err_msg; + char *err_description; + struct b_error_template_parameter err_params[B_ERROR_TEMPLATE_PARAMETER_MAX]; + + struct b_queue err_submsg; + struct b_queue err_stack; + b_queue_entry err_entry; + struct b_error *err_caused_by; +}; + +#endif diff --git a/core/include/blue/core/error.h b/core/include/blue/core/error.h new file mode 100644 index 0000000..8ec3071 --- /dev/null +++ b/core/include/blue/core/error.h @@ -0,0 +1,411 @@ +#ifndef BLUELIB_CORE_ERROR_H_ +#define BLUELIB_CORE_ERROR_H_ + +#include +#include +#include +#include + +#define B_ERROR_TEMPLATE_PARAMETER_MAX 4 +#define B_ERROR_MSG_ID_INVALID ((unsigned long)-1) + +#define B_CATCH(err, expr) ((err = (expr)) != NULL) + +#define b_result_is_error(result) ((result) != NULL) +#define b_result_is_success(result) ((result) == NULL) + +#define B_RESULT_SUCCESS ((b_result)NULL) +#define B_RESULT_ERR(err_name) \ + b_error_with_code(b_error_vendor_get_builtin(), B_ERR_##err_name) +#define B_RESULT_ERR_WITH_STRING(err_name, ...) \ + b_error_with_string( \ + b_error_vendor_get_builtin(), B_ERR_##err_name, __VA_ARGS__) +#define B_RESULT_STATUS(code) \ + ((code) == B_SUCCESS \ + ? B_RESULT_SUCCESS \ + : (b_error_with_code(b_error_vendor_get_builtin(), code))) +#define B_RESULT_STATUS_WITH_STRING(code, ...) \ + ((code) == B_SUCCESS \ + ? B_RESULT_SUCCESS \ + : (b_error_with_string( \ + b_error_vendor_get_builtin(), code, __VA_ARGS__))) + +#define B_ERRORS_BUILTIN (b_error_vendor_get_builtin()) +#define B_ERRORS_ERRNO (b_error_vendor_get_errno()) + +#define B_ERROR_PARAM(name, value) \ + (b_error_template_parameter) \ + { \ + .param_name = (name), .param_value = (uintptr_t)(value), \ + } + +#define B_ERROR_TEMPLATE_PARAM(name, type, format) \ + (b_error_template_parameter_definition) \ + { \ + .param_name = (name), .param_type = (type), \ + .param_format = (format), \ + } +#define B_ERROR_DEFINITION(code, name, msg) \ + [code] = (b_error_definition) \ + { \ + .err_name = (name), .err_message = (msg), \ + } +#define B_ERROR_DEFINITION_TEMPLATE(code, name, msg, ...) \ + [code] = (b_error_definition) \ + { \ + .err_name = (name), .err_message = (msg), \ + .err_params = __VA_ARGS__, \ + } + +#define B_ERROR_MSG(id, content) \ + [id] = (b_error_msg) \ + { \ + .msg_message = (content), \ + } +#define B_ERROR_MSG_TEMPLATE(id, content, ...) \ + [id] = (b_error_msg) \ + { \ + .msg_message = (content), .msg_params = __VA_ARGS__, \ + } + +#define z__b_error_create_status(status_code) \ + (z__b_error_create( \ + b_error_vendor_get_builtin(), status_code, NULL, NULL, 0, \ + NULL, NULL)) + +/* Error creation macros */ +#define b_error_with_code(vendor, code) \ + (z__b_error_create( \ + vendor, code, NULL, __FILE__, __LINE__, __FUNCTION__, NULL)) +#define b_error_caused_by_error(vendor, code, cause_error) \ + (z__b_error_create( \ + vendor, code, cause_error, __FILE__, __LINE__, __FUNCTION__, NULL)) +#define b_error_caused_by_status(vendor, code, cause_status) \ + (z__b_error_create( \ + vendor, code, z__b_error_create_status(cause_status), \ + __FILE__, __LINE__, __FUNCTION__, NULL)) +#define b_error_caused_by_code(vendor, code, cause_vendor, cause_code) \ + (z__b_error_create( \ + vendor, code, z__b_error_with_code(cause_vendor, cause_code), \ + __FILE__, __LINE__, __FUNCTION__, NULL)) +#define b_error_with_string(vendor, code, ...) \ + (z__b_error_create( \ + vendor, code, NULL, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)) +#define b_error_with_string_caused_by_error(vendor, code, cause_error, ...) \ + (z__b_error_create( \ + vendor, code, cause_error, __FILE__, __LINE__, __FUNCTION__, \ + __VA_ARGS__)) +#define b_error_with_string_caused_by_status(vendor, code, cause_status, ...) \ + (z__b_error_create( \ + vendor, code, z__b_error_create_status(cause_status), \ + __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)) +#define b_error_with_msg(vendor, code, msg_id) \ + (z__b_error_create_msg( \ + vendor, code, NULL, __FILE__, __LINE__, __FUNCTION__, msg_id, \ + (b_error_template_parameter[]) {{}})) +#define b_error_with_msg_caused_by_error(vendor, code, cause_error, msg_id) \ + (z__b_error_create_msg( \ + vendor, code, cause_error, __FILE__, __LINE__, __FUNCTION__, \ + msg_id, (b_error_template_parameter[]) {{}})) +#define b_error_with_msg_caused_by_status(vendor, code, cause_status, msg_id) \ + (z__b_error_create_msg( \ + vendor, code, z__b_error_create_status(cause_status), \ + __FILE__, __LINE__, __FUNCTION__, msg_id, \ + (b_error_template_parameter[]) {{}})) +#define b_error_with_msg_template(vendor, code, msg_id, ...) \ + (z__b_error_create_msg( \ + vendor, code, NULL, __FILE__, __LINE__, __FUNCTION__, msg_id, \ + (b_error_template_parameter[]) {__VA_ARGS__, {}})) +#define b_error_with_msg_template_caused_by_error( \ + vendor, code, cause_error, msg_id, ...) \ + (z__b_error_create_msg( \ + vendor, code, cause_error, __FILE__, __LINE__, __FUNCTION__, \ + msg_id, (b_error_template_parameter[]) {__VA_ARGS__, {}})) +#define b_error_with_msg_template_caused_by_status( \ + vendor, code, cause_status, msg_id, ...) \ + (z__b_error_create_msg( \ + vendor, code, z__b_error_create_status(cause_status), \ + __FILE__, __LINE__, __FUNCTION__, msg_id, \ + (b_error_template_parameter[]) {__VA_ARGS__, {}})) +#define b_error_with_template(vendor, code, ...) \ + (z__b_error_create_template( \ + vendor, code, NULL, __FILE__, __LINE__, __FUNCTION__, \ + (b_error_template_parameter[]) {__VA_ARGS__, {}})) +#define b_error_with_template_caused_by_error(vendor, code, cause_error, ...) \ + (z__b_error_create_template( \ + vendor, code, cause_error, __FILE__, __LINE__, __FUNCTION__, \ + (b_error_template_parameter[]) {__VA_ARGS__, {}})) +#define b_error_with_template_caused_by_status(vendor, code, cause_status, ...) \ + (z__b_error_create_template( \ + vendor, code, z__b_error_create_status(cause_status), \ + __FILE__, __LINE__, __FUNCTION__, \ + (b_error_template_parameter[]) {__VA_ARGS__, {}})) + +/* Error propagation macros */ +#define b_result_propagate(err) \ + (z__b_error_propagate(err, __FILE__, __LINE__, __FUNCTION__)) +#define b_error_caused_by(err, caused_by) (z__b_error_caused_by(err, caused_by)) +#define b_error_caused_by_b_status(err, status) \ + (z__b_error_caused_by_b_status(err, status)) +#define b_error_replace(err, caused_by) \ + (z__b_error_propagate(err, __FILE__, __LINE__, __FUNCTION__)) + +/* Error throw macros */ +#define z__b_throw(err) (z__b_error_throw(err, NULL, 0, NULL)) +#define b_throw(err) (z__b_error_throw(err, __FILE__, __LINE__, __FUNCTION__)) +#define b_throw_status(status) \ + (z__b_error_throw( \ + z__b_error_create( \ + b_error_vendor_get_builtin(), status, NULL, NULL, 0, \ + NULL, NULL), \ + __FILE__, __LINE__, __FUNCTION__)) + +#define b_throw_status_string(status, ...) \ + (z__b_error_throw( \ + z__b_error_create( \ + b_error_vendor_get_builtin(), status, NULL, NULL, 0, \ + NULL, __VA_ARGS__), \ + __FILE__, __LINE__, __FUNCTION__)) +#define b_throw_error_code(vendor, code) \ + z__b_throw(b_error_with_code(vendor, code)) +#define b_throw_error_caused_by_error(vendor, code, cause) \ + z__b_throw(b_error_caused_by_error(vendor, code, cause)) +#define b_throw_error_caused_by_status(vendor, code, cause) \ + z__b_throw(b_error_caused_by_status(vendor, code, cause)) +#define b_throw_error_with_string(vendor, code, ...) \ + z__b_throw(b_error_with_string(vendor, code, __VA_ARGS__)) +#define b_throw_error_with_string_caused_by_error(vendor, code, cause, ...) \ + z__b_throw(b_error_with_string_caused_by_error( \ + vendor, code, cause, __VA_ARGS__)) +#define b_throw_error_with_string_caused_by_status(vendor, code, cause, ...) \ + z__b_throw(b_error_with_string_caused_by_status( \ + vendor, code, cause, __VA_ARGS__)) + +#define b_throw_error_with_msg(vendor, code, msg_id) \ + z__b_throw(b_error_with_msg(vendor, code, msg_id)) +#define b_throw_error_with_msg_caused_by_error(vendor, code, cause, msg_id) \ + z__b_throw(b_error_with_msg_caused_by_error(vendor, code, cause, msg_id)) +#define b_throw_error_with_msg_caused_by_status(vendor, code, cause, msg_id) \ + z__b_throw(b_error_with_msg_caused_by_status(vendor, code, cause, msg_id)) + +#define b_throw_error_with_msg_template(vendor, code, msg_id, ...) \ + z__b_throw(b_error_with_msg_template(vendor, code, msg_id, __VA_ARGS__)) +#define b_throw_error_with_msg_template_caused_by_error( \ + vendor, code, cause, msg_id, ...) \ + z__b_throw(b_error_with_msg_template_caused_by_error( \ + vendor, code, cause, msg_id, __VA_ARGS__)) +#define b_throw_error_with_msg_template_caused_by_status( \ + vendor, code, cause, msg_id, ...) \ + z__b_throw(b_error_with_msg_template_caused_by_status( \ + vendor, code, cause, msg_id, __VA_ARGS__)) + +#define b_throw_error_with_template(vendor, code, ...) \ + z__b_throw(b_error_with_template(vendor, code, __VA_ARGS__)) +#define b_throw_error_with_template_caused_by_error(vendor, code, cause, ...) \ + z__b_throw(b_error_with_template_caused_by_error( \ + vendor, code, cause, __VA_ARGS__)) +#define b_throw_error_with_template_caused_by_status(vendor, code, cause, ...) \ + z__b_throw(b_error_with_template_caused_by_status( \ + vendor, code, cause, __VA_ARGS__)) + +#define B_ERR_MSG(s) \ + { \ + .msg_type = B_ERROR_MESSAGE_ERROR, .msg_content = (s), \ + } +#define B_ERR_MSG_WARN(s) \ + { \ + .msg_type = B_ERROR_MESSAGE_WARN, .msg_content = (s), \ + } +#define B_ERR_MSG_INFO(s) \ + { \ + .msg_type = B_ERROR_MESSAGE_INFO, .msg_content = (s), \ + } +#define B_ERR_MSG_END(s) \ + { \ + .msg_type = B_ERROR_MESSAGE_NONE, .msg_content = NULL, \ + } + +typedef enum b_error_submsg_type { + B_ERROR_SUBMSG_NONE = 0, + B_ERROR_SUBMSG_ERROR, + B_ERROR_SUBMSG_WARNING, + B_ERROR_SUBMSG_INFO, +} b_error_submsg_type; + +typedef enum b_error_report_flags { + B_ERROR_REPORT_NONE = 0, + B_ERROR_REPORT_STATUS = 0x01u, + B_ERROR_REPORT_DESCRIPTION = 0x02u, + B_ERROR_REPORT_SUBMSG = 0x04u, + B_ERROR_REPORT_STACK_TRACE = 0x08u, + B_ERROR_REPORT_CAUSE = 0x10u, + + B_ERROR_REPORT_MINIMAL = B_ERROR_REPORT_STATUS | B_ERROR_REPORT_DESCRIPTION, + B_ERROR_REPORT_DEFAULT = B_ERROR_REPORT_MINIMAL | B_ERROR_REPORT_SUBMSG + | B_ERROR_REPORT_CAUSE, + B_ERROR_REPORT_ALL = B_ERROR_REPORT_DEFAULT | B_ERROR_REPORT_STACK_TRACE, +} b_error_report_flags; + +typedef enum b_error_template_parameter_type { + B_ERROR_TEMPLATE_PARAM_NONE = 0, + B_ERROR_TEMPLATE_PARAM_STRING, + B_ERROR_TEMPLATE_PARAM_CHAR, + B_ERROR_TEMPLATE_PARAM_INT, + B_ERROR_TEMPLATE_PARAM_UINT, + B_ERROR_TEMPLATE_PARAM_LONG, + B_ERROR_TEMPLATE_PARAM_ULONG, + B_ERROR_TEMPLATE_PARAM_LONGLONG, + B_ERROR_TEMPLATE_PARAM_ULONGLONG, + B_ERROR_TEMPLATE_PARAM_SIZE_T, + B_ERROR_TEMPLATE_PARAM_INTPTR, + B_ERROR_TEMPLATE_PARAM_UINTPTR, + B_ERROR_TEMPLATE_PARAM_PTR, +} b_error_template_parameter_type; + +typedef struct b_error_template_parameter_definition { + const char *param_name; + b_error_template_parameter_type param_type; + const char *param_format; +} b_error_template_parameter_definition; + +typedef struct b_error_template_parameter { + const char *param_name; + uintptr_t param_value; + const struct b_error_template_parameter_definition *__param_def; +} b_error_template_parameter; + +struct b_error_vendor; + +typedef struct b_error b_error; +typedef struct b_error *b_result; +typedef struct b_error_submsg b_error_submsg; +typedef struct b_error_stack_frame b_error_stack_frame; +typedef long b_error_status_code; +typedef unsigned long b_error_msg_id; + +typedef struct b_error_definition { + const char *err_name; + const char *err_message; + const b_error_template_parameter_definition err_params[B_ERROR_TEMPLATE_PARAMETER_MAX]; +} b_error_definition; + +typedef struct b_error_msg { + const char *msg_message; + const b_error_template_parameter_definition msg_params[B_ERROR_TEMPLATE_PARAMETER_MAX]; +} b_error_msg; + +typedef const b_error_definition *(*b_error_status_code_get_definition)( + const struct b_error_vendor *, b_error_status_code); +typedef const b_error_msg *(*b_error_msg_get_definition)( + const struct b_error_vendor *, b_error_msg_id); +typedef void (*b_error_report_function)( + const struct b_error *, b_error_report_flags); + +typedef struct b_error_vendor { + const char *v_name; + + b_error_status_code_get_definition v_status_get_definition; + b_error_msg_get_definition v_msg_get_definition; + + const b_error_definition *v_error_definitions; + size_t v_error_definitions_length; + + const b_error_msg *v_msg; + size_t v_msg_length; +} b_error_vendor; + +BLUE_API b_error *z__b_error_create_template( + const b_error_vendor *, b_error_status_code, b_error *, const char *, + unsigned int, const char *, const b_error_template_parameter[]); +BLUE_API b_error *z__b_error_create_string( + const b_error_vendor *, b_error_status_code, b_error *, const char *, + unsigned int, const char *, const char *, va_list); +BLUE_API b_error *z__b_error_create_msg( + const b_error_vendor *, b_error_status_code, b_error *, const char *, + unsigned int, const char *, b_error_msg_id, + const b_error_template_parameter[]); +BLUE_API b_error *z__b_error_propagate( + b_error *, const char *, unsigned int, const char *); +BLUE_API b_error *z__b_error_caused_by(b_error *, b_error *); +BLUE_API b_error *z__b_error_caused_by_b_status(b_error *, b_status); +BLUE_API void z__b_error_throw(b_error *, const char *, unsigned int, const char *); + +BLUE_API const b_error_vendor *b_error_vendor_get_builtin(void); +BLUE_API const b_error_vendor *b_error_vendor_get_errno(void); +BLUE_API const b_error_definition *b_error_vendor_get_error_definition( + const b_error_vendor *vendor, b_error_status_code code); +BLUE_API const char *b_error_vendor_get_status_code_name( + const b_error_vendor *vendor, b_error_status_code code); +BLUE_API const char *b_error_vendor_get_status_code_description( + const b_error_vendor *vendor, b_error_status_code code); +BLUE_API const b_error_msg *b_error_vendor_get_msg( + const b_error_vendor *vendor, b_error_msg_id msg_id); + +static inline b_error *z__b_error_create( + const b_error_vendor *v, b_error_status_code c, b_error *c2, + const char *f0, unsigned int l, const char *f1, const char *d, ...) +{ + va_list arg; + va_start(arg, d); + b_error *err = z__b_error_create_string(v, c, c2, f0, l, f1, d, arg); + va_end(arg); + return err; +} + +BLUE_API enum b_status b_error_add_submsg_string( + b_error *error, b_error_submsg_type type, const char *msg, ...); +BLUE_API enum b_status z__b_error_add_submsg_template( + b_error *error, b_error_submsg_type type, b_error_msg_id msg_id, + b_error_template_parameter param[]); +#define b_error_add_submsg(error, type, msg_id) \ + (z__b_error_add_submsg_template( \ + error, type, msg_id, (b_error_template_parameter[]) {{}})) +#define b_error_add_submsg_template(error, type, msg_id, ...) \ + (z__b_error_add_submsg_template( \ + error, type, msg_id, \ + (b_error_template_parameter[]) {__VA_ARGS__, {}})) + +BLUE_API void b_error_release(b_error *error); + +BLUE_API b_error_status_code b_error_get_status_code(const b_error *error); +BLUE_API const b_error_vendor *b_error_get_vendor(const b_error *error); +BLUE_API const b_error_definition *b_error_get_definition(const b_error *error); +BLUE_API const b_error_template_parameter *b_error_get_template_parameter( + const b_error *error, const char *param_name); +BLUE_API const b_error_template_parameter *b_error_get_template_parameters( + const b_error *error); +BLUE_API const char *b_error_get_description(const b_error *error); +BLUE_API const b_error_msg *b_error_get_msg(const b_error *error); +BLUE_API const b_error_submsg *b_error_get_first_submsg(const b_error *error); +BLUE_API const b_error_submsg *b_error_get_next_submsg( + const b_error *error, const b_error_submsg *msg); +BLUE_API const b_error_stack_frame *b_error_get_first_stack_frame( + const b_error *error); +BLUE_API const b_error_stack_frame *b_error_get_next_stack_frame( + const b_error *error, const b_error_stack_frame *frame); +BLUE_API const b_error *b_error_get_caused_by(const b_error *error); + +BLUE_API b_error_submsg_type b_error_submsg_get_type(const b_error_submsg *msg); +BLUE_API const char *b_error_submsg_get_content(const b_error_submsg *msg); +BLUE_API const b_error_msg *b_error_submsg_get_msg(const b_error_submsg *msg); +BLUE_API const b_error_template_parameter *b_error_submsg_get_template_parameters( + const b_error_submsg *msg); + +BLUE_API const char *b_error_stack_frame_get_filepath( + const b_error_stack_frame *frame); +BLUE_API unsigned int b_error_stack_frame_get_line_number( + const b_error_stack_frame *frame); +BLUE_API const char *b_error_stack_frame_get_function_name( + const b_error_stack_frame *frame); + +BLUE_API const b_error_template_parameter_definition *b_error_definition_get_template_parameter( + const b_error_definition *error_def, const char *param_name); + +BLUE_API const char *b_error_msg_get_content(const b_error_msg *msg); +BLUE_API const b_error_template_parameter_definition *b_error_msg_get_template_parameter( + const b_error_msg *msg, const char *param_name); + +BLUE_API void b_set_error_report_function( + b_error_report_function func, b_error_report_flags flags); + +#endif