#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) { b_queue_delete(&allocated_errors, &error->err_entry); b_queue_push_back(&free_errors, &error->err_entry); } 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"), B_ERROR_DEFINITION( B_ERR_COMPRESSION_FAILURE, "COMPRESSION_FAILURE", "Compression failure"), B_ERROR_DEFINITION( B_ERR_TYPE_REGISTRATION_FAILURE, "TYPE_REGISTRATION_FAILURE", "Type registration failure"), B_ERROR_DEFINITION( B_ERR_CLASS_INIT_FAILURE, "CLASS_INIT_FAILURE", "Class initialisation failure"), }; 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); }