/*************************************************************************/
/* Copyright (c) 2004 */
/* Daniel Sleator, David Temperley, and John Lafferty */
/* Copyright (c) 2009 Linas Vepstas */
/* All rights reserved */
/* */
/* Use of the link grammar parsing system is subject to the terms of the */
/* license set forth in the LICENSE file included with this software. */
/* This license allows free redistribution and use in source and binary */
/* forms, with or without modification, subject to certain conditions. */
/* */
/*************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "error.h"
#include "api-structures.h" // For Sentence_s
#include "print/print.h" // For print_sentence_context()
#include "print/print-util.h" // For append_string()
static void default_error_handler(lg_errinfo *, void *);
static TLS struct
{
lg_error_handler handler;
void *handler_data;
lg_errinfo *errmsg;
} lg_error = { default_error_handler };
/* This list should match enum lg_error_severity. */
#define MAX_SEVERITY_LABEL_SIZE 64 /* In bytes. */
const char *severity_label_by_level[] =
{
"Fatal error", "Error", "Warning", "Info", "Debug", "Trace", /*lg_None*/"",
NULL
};
/* Name to prepend to messages. */
static const char libname[] = "link-grammar";
/* === Error queue utilities ======================================== */
static lg_errinfo *error_queue_resize(lg_errinfo *lge, int len)
{
lge = realloc(lge, (len+2) * sizeof(lg_errinfo));
lge[len+1].text = NULL;
return lge;
}
static int error_queue_len(lg_errinfo *lge)
{
size_t len = 0;
if (lge)
while (NULL != lge[len].text) len++;
return len;
}
static void error_queue_append(lg_errinfo **lge, lg_errinfo *current_error)
{
int n = error_queue_len(*lge);
*lge = error_queue_resize(*lge, n);
current_error->text = strdup(current_error->text);
(*lge)[n] = *current_error;
}
/* ==================================================================*/
/**
* Return the error severity according to the start of the error string.
* If an error severity is not found - return None.
*/
static lg_error_severity message_error_severity(const char *msgtext)
{
for (const char **llp = severity_label_by_level; NULL != *llp; llp++)
{
for (const char *s = *llp, *t = msgtext; ; s++, t++)
{
if ((':' == *t) && (t > msgtext))
{
return (int)(llp - severity_label_by_level + 1);
}
if ((*s != *t) || ('\0' == *s)) break;
}
}
return lg_None;
}
static void lg_error_msg_free(lg_errinfo *lge)
{
free((void *)lge->text);
free((void *)lge->severity_label);
}
/* === API functions ================================================*/
/**
* Set the error handler function to the given one.
* @param lg_error_handler New error handler function
* @param data Argument for the error handler function
*/
lg_error_handler lg_error_set_handler(lg_error_handler f, void *data)
{
const lg_error_handler oldf = lg_error.handler;
lg_error.handler = f;
lg_error.handler_data = data;
return oldf;
}
const void *lg_error_set_handler_data(void * data)
{
const char *old_data = lg_error.handler_data;
lg_error.handler_data = data;
return old_data;
}
/**
* Print the error queue and free it.
* @param f Error handler function
* @param data Argument for the error handler function
* @return Number of errors
*/
int lg_error_printall(lg_error_handler f, void *data)
{
int n = error_queue_len(lg_error.errmsg);
if (0 == n) return 0;
for (lg_errinfo *lge = &lg_error.errmsg[n-1]; lge >= lg_error.errmsg; lge--)
{
if (NULL == f)
default_error_handler(lge, data);
else
f(lg_error.errmsg, data);
lg_error_msg_free(lge);
}
free(lg_error.errmsg);
lg_error.errmsg = NULL;
return n;
}
/**
* Clear the error queue. Free all of its memory.
* @return Number of errors
*/
int lg_error_clearall(void)
{
if (NULL == lg_error.errmsg) return 0;
int nerrors = 0;
for (lg_errinfo *lge = lg_error.errmsg; NULL != lge->text; lge++)
{
nerrors++;
lg_error_msg_free(lge);
}
free(lg_error.errmsg);
lg_error.errmsg = NULL;
return nerrors;
}
/**
* Format the given raw error message.
* Create a complete error message, ready to be printed.
* If the severity is not lg_None, add the library name.
* Also add the severity label.
* @param lge The raw error message.
* @return The complete error message. The caller needs to free the memory.
*/
char *lg_error_formatmsg(lg_errinfo *lge)
{
dyn_str *s = dyn_str_new();
/* Prepend libname to messages with higher severity than Debug. */
if (lge->severity < lg_Debug)
append_string(s, "%s: ", libname);
if ((NULL != lge->severity_label) && ('\0' != lge->severity_label[0]))
append_string(s, "%s: ", lge->severity_label);
append_string(s, "%s", lge->text);
return dyn_str_take(s);
}
static TLS dyn_str *outbuf = NULL;
/**
* Flush a partial error message if exists.
* Return true iff a message has been actually flushed.
*
* (Just using prt_error("\n") also flushes a buffered partial error
* message, but if there is no such message an empty message is generated).
*/
bool lg_error_flush(void)
{
if (outbuf == NULL) return false;
prt_error("\n");
return true;
}
/* ================================================================== */
/**
* The default error handler callback function.
* @param lge The raw error message.
*/
static void default_error_handler(lg_errinfo *lge, void *data)
{
FILE *outfile = stdout;
if (((NULL == data) && (lge->severity < lg_Debug)) ||
((NULL != data) && (lge->severity < *(lg_error_severity *)(int *)data) &&
(lg_None != lge->severity)))
{
fflush(stdout); /* Make sure that stdout has been written out first. */
outfile = stderr;
}
char *msgtext = lg_error_formatmsg(lge);
/* On MINGW64, fprintf() somehow doesn't follow fd redirection in Python
* (see divert_start() in tests.py), but there is no such a problem with
* fputs(). */
#if 0
fprintf(outfile, "%s", msgtext);
#else
fputs(msgtext, outfile);
#endif
dyn_str_release(msgtext); // For malloc debug.
fflush(outfile); /* Also stderr, in case some OS does some strange thing */
}
/**
* Convert a numerical severity level to its corresponding string.
*/
static const char *error_severity_label(lg_error_severity sev)
{
char *sevlabel = alloca(MAX_SEVERITY_LABEL_SIZE);
if (lg_None == sev)
{
sevlabel[0] = '\0';
}
else if ((sev < 1) || (sev > lg_None))
{
snprintf(sevlabel, MAX_SEVERITY_LABEL_SIZE, "Message severity %d", (int)sev);
}
else
{
sevlabel = (char *)severity_label_by_level[sev-1];
}
return strdup(sevlabel);
}
static void verr_msg(err_ctxt *ec, lg_error_severity sev, const char *fmt, va_list args)
GNUC_PRINTF(3,0);
static void verr_msg(err_ctxt *ec, lg_error_severity sev, const char *fmt, va_list args)
{
if (NULL == outbuf) outbuf = dyn_str_new();
/*
* If the message is a complete one, it ends with a newline. Else the
* message is buffered in msg_buf until it is complete. A complete line
* which is not a complete message is marked with a \ at its end (after
* its newline), which is removed here. The newline and \ should be
* specified only in the format string.
*/
char *nfmt;
bool partline = false;
const int fmtlen = strlen(fmt);
if ('\n' != fmt[fmtlen-1])
{
partline = true;
if ('\\' == fmt[fmtlen-1])
{
nfmt = strdupa(fmt);
nfmt[fmtlen-1] = '\0';
fmt = nfmt;
}
}
vappend_string(outbuf, fmt, args);
if (partline) return;
if ((NULL != ec) && (NULL != ec->sent))
print_sentence_context(ec->sent, outbuf);
lg_errinfo current_error;
/* current_error.ec = *ec; */
const char *error_text = outbuf->str;
lg_error_severity msg_sev = message_error_severity(error_text);
if (lg_None != msg_sev)
{
/* Strip off the error severity label, for consistency.
* lg_error_format() will reconstruct it. */
error_text = strchr(error_text, ':') + 1;
error_text += strspn(error_text, " \t");
}
current_error.text = error_text;
current_error.severity = ((lg_None == msg_sev) && (0 != sev)) ? sev : msg_sev;
current_error.severity_label = error_severity_label(current_error.severity);
if (NULL == lg_error.handler)
{
error_queue_append(&lg_error.errmsg, ¤t_error);
}
else
{
lg_error.handler(¤t_error, lg_error.handler_data);
free((void *)current_error.severity_label);
}
dyn_str_delete(outbuf);
outbuf = NULL;
}
void err_msgc(err_ctxt *ec, lg_error_severity sev, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
verr_msg(ec, sev, fmt, args);
va_end(args);
}
/**
* Issue the given message.
* This is an API function.
*
* Usage notes:
* The severity can be specified as an initial string in the message,
* such as "Error: Rest of message". For known severity names see
* \link severity_label_by_level List of severity strings. \endlink.
* See \link verr_msg \endlink for how the severity is handled
* if it is not specified.
*
* @param fmt printf()-like format.
* @param ... printf()-like arguments.
* @return Always 0, not to be used. This is needed so prt_error()
* can be used in complex macros that have to use the comma operator.
*/
int prt_error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
verr_msg(NULL, 0, fmt, args);
va_end(args);
return 0;
}
/**
* Check whether the given feature is enabled. It is considered
* enabled if it is found in the comma delimited list of features.
* This list, if not empty, has a leading and a trailing commas.
* Return NULL if not enabled, else ",". If the feature appears
* as "feature:param", return a pointer to the ":" before param.
* @param list Comma delimited list of features (start/end commas too).
* @param ... List of features to check.
* @return If not enabled - NULL; Else "," or the feature param if exists.
*/
const char *feature_enabled(const char * list, ...)
{
const char *feature;
va_list given_features;
va_start(given_features, list);
while (NULL != (feature = va_arg(given_features, char *)))
{
if ('\0' == feature[0]) continue;
size_t len = strlen(feature);
char *buff = alloca(len + 2 + 1); /* leading comma + comma/colon + NUL */
/* The "feature" variable may contain a full/relative file path.
* If so, extract the file name from it. On Windows first try the
* native separator \, but also try /. */
const char *dir_sep = NULL;
#ifdef _WIN32
dir_sep = strrchr(feature, '\\');
#endif
if (NULL == dir_sep) dir_sep = strrchr(feature, '/');
if (NULL != dir_sep) feature = dir_sep + 1;
buff[0] = ',';
strcpy(buff+1, feature);
strcat(buff, ",");
if (NULL != strstr(list, buff))
{
va_end(given_features);
return ",";
}
buff[len+1] = ':'; /* check for "feature:param" */
if (NULL != strstr(list, buff))
{
va_end(given_features);
return strstr(list, buff) + len + 1;
}
if (list[0] == ':')
{
/* Check for a parameter in ":param1:param2," */
buff[0] = ':';
bool found = (NULL != strstr(list, buff));
if (!found)
{
buff[len+1] = ',';
found = (NULL != strstr(list, buff));
}
if (found)
{
va_end(given_features);
return strstr(list, buff) + strlen(buff) + 2;
}
}
}
va_end(given_features);
return NULL;
}
#ifdef _WIN32
#define DEBUG_TRAP (*((volatile int*) 0x0) = 42)
#elif defined __GNUC__ || defined __clang_analyzer__
#define DEBUG_TRAP __builtin_trap()
#else
#define DEBUG_TRAP abort()
#endif
/**
* Issue the assert() macro (see error.h) error message.
*/
void (*lg_library_failure_hook)(void);
void assert_failure(const char cond_str[], const char func[],
const char *src_location, const char *fmt, ...)
{
va_list args;
const char sevfmt[] = "Fatal error: \nAssertion (%s) failed in %s() (%s): ";
fflush(stdout);
lg_error_flush();
va_start(args, fmt);
if ((lg_error.handler == default_error_handler) ||
(lg_error.handler == NULL))
{
fprintf(stderr, sevfmt, cond_str, func, src_location);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n"); \
fflush(stderr); \
}
else
{
prt_error(sevfmt, cond_str, func, src_location);
verr_msg(NULL, lg_Fatal, fmt, args);
prt_error("\n");
}
va_end(args);
if (lg_library_failure_hook == NULL)
DEBUG_TRAP; /* leave stack trace in debugger */ \
else
lg_library_failure_hook();
exit(1);
}
/*
* Implement lgdebug() and verbosity_level() (see these macros and their
* comments in error.h).
*/
bool verbosity_check(int level, int v, char print_func , const char func[],
const char file[], const char *filter)
{
if ((((D_SPEC >= v) && (v >= level)) || (v == level)) &&
((level <= 1) || !((level <= D_USER_MAX) && (v > D_USER_MAX))) &&
((debug[0] == '\0') || feature_enabled(debug, func, file, filter, NULL)))
{
if (print_func == '+') err_msg(0, "%s: ", func);
return true;
}
return false;
}
void debug_msg(int level, int v, char print_func, const char func[],
const char file[], const char *fmt, ...)
{
va_list args;
if (verbosity_check(level, v, print_func, func, file, ""))
{
va_start(args, fmt);
verr_msg(NULL, lg_Trace, fmt, args);
va_end(args);
}
}
const char *syserror_msg(int errnum)
{
TLS static char errbuf[64];
lg_strerror(errnum, errbuf, sizeof(errbuf));
return errbuf;
}
void lg_lib_failure(void)
{
if (lg_library_failure_hook != NULL)
lg_library_failure_hook();
exit(1);
}