/* messageobj.c * Provides an external, extensible API for message reporting. * * (c) 2017 HTACG * See tidy.h for the copyright notice. */ #include "messageobj.h" #include "message.h" #include "tidy-int.h" #include "limits.h" #include "tmbstr.h" /********************************************************************* * BuildArgArray Support - declarations and forward declarations *********************************************************************/ /** A record of a single argument and its type. An array these ** represents the arguments supplied to a format string, ordered ** in the same position as they occur in the format string. Because ** older versions of Windows don't support positional arguments, ** Tidy doesn't either. */ #define FORMAT_LENGTH 21 struct printfArg { TidyFormatParameterType type; /* type of the argument */ int formatStart; /* where the format starts */ int formatLength; /* length of the format */ char format[FORMAT_LENGTH]; /* buffer for the format */ union { /* the argument */ int i; uint ui; double d; const char *s; } u; }; /** Returns a pointer to an allocated array of `printfArg` given a format ** string and a va_list, or NULL if not successful or no parameters were ** given. Parameter `rv` will return with the count of zero or more ** parameters if successful, else -1. ** */ static struct printfArg *BuildArgArray( TidyDocImpl *doc, ctmbstr fmt, va_list ap, int *rv ); /********************************************************************* * Tidy Message Object Support *********************************************************************/ /** Create an internal representation of a Tidy message with all of ** the information that that we know about the message. ** ** The function signature doesn't have to stay static and is a good ** place to add instantiation if expanding the API. ** ** We currently know the doc, node, code, line, column, level, and ** args, will pre-calculate all of the other members upon creation. ** This ensures that we can use members directly, immediately, ** without having to use accessors internally. ** ** If any message callback filters are setup by API clients, they ** will be called here. ** ** This version serves as the designated initializer and as such ** requires every known parameter. */ static TidyMessageImpl *tidyMessageCreateInitV( TidyDocImpl *doc, Node *node, uint code, int line, int column, TidyReportLevel level, va_list args ) { TidyMessageImpl *result = TidyDocAlloc(doc, sizeof(TidyMessageImpl)); TidyDoc tdoc = tidyImplToDoc(doc); va_list args_copy; enum { sizeMessageBuf=2048 }; ctmbstr pattern; uint i = 0; /* Things we know... */ result->tidyDoc = doc; result->tidyNode = node; result->code = code; result->line = line; result->column = column; result->level = level; /* Is #719 - set 'muted' before any callbacks. */ result->muted = no; i = 0; while ((doc->muted.list) && (doc->muted.list[i] != 0)) { if (doc->muted.list[i] == code) { result->muted = yes; break; } i++; } /* Things we create... */ va_copy(args_copy, args); result->arguments = BuildArgArray(doc, tidyDefaultString(code), args_copy, &result->argcount); va_end(args_copy); result->messageKey = TY_(tidyErrorCodeAsKey)(code); result->messageFormatDefault = tidyDefaultString(code); result->messageFormat = tidyLocalizedString(code); result->messageDefault = TidyDocAlloc(doc, sizeMessageBuf); va_copy(args_copy, args); TY_(tmbvsnprintf)(result->messageDefault, sizeMessageBuf, result->messageFormatDefault, args_copy); va_end(args_copy); result->message = TidyDocAlloc(doc, sizeMessageBuf); va_copy(args_copy, args); TY_(tmbvsnprintf)(result->message, sizeMessageBuf, result->messageFormat, args_copy); va_end(args_copy); result->messagePosDefault = TidyDocAlloc(doc, sizeMessageBuf); result->messagePos = TidyDocAlloc(doc, sizeMessageBuf); if ( cfgBool(doc, TidyEmacs) && cfgStr(doc, TidyEmacsFile) ) { /* Change formatting to be parsable by GNU Emacs */ TY_(tmbsnprintf)(result->messagePosDefault, sizeMessageBuf, "%s:%d:%d: ", cfgStr(doc, TidyEmacsFile), line, column); TY_(tmbsnprintf)(result->messagePos, sizeMessageBuf, "%s:%d:%d: ", cfgStr(doc, TidyEmacsFile), line, column); } else if ( cfgBool(doc, TidyShowFilename) && cfgStr(doc, TidyEmacsFile) ) { /* Include filename in output */ TY_(tmbsnprintf)(result->messagePosDefault, sizeMessageBuf, tidyDefaultString(FN_LINE_COLUMN_STRING), cfgStr(doc, TidyEmacsFile), line, column); TY_(tmbsnprintf)(result->messagePos, sizeMessageBuf, tidyLocalizedString(FN_LINE_COLUMN_STRING), cfgStr(doc, TidyEmacsFile), line, column); } else { /* traditional format */ TY_(tmbsnprintf)(result->messagePosDefault, sizeMessageBuf, tidyDefaultString(LINE_COLUMN_STRING), line, column); TY_(tmbsnprintf)(result->messagePos, sizeMessageBuf, tidyLocalizedString(LINE_COLUMN_STRING), line, column); } result->messagePrefixDefault = tidyDefaultString(level); result->messagePrefix = tidyLocalizedString(level); if ( line > 0 && column > 0 ) pattern = "%s%s%s"; /* pattern if there's location information */ else pattern = "%.0s%s%s"; /* otherwise if there isn't */ if ( level > TidyFatal ) pattern = "%.0s%.0s%s"; /* dialog doesn't have pos or prefix */ result->messageOutputDefault = TidyDocAlloc(doc, sizeMessageBuf); TY_(tmbsnprintf)(result->messageOutputDefault, sizeMessageBuf, pattern, result->messagePosDefault, result->messagePrefixDefault, result->messageDefault); result->messageOutput = TidyDocAlloc(doc, sizeMessageBuf); TY_(tmbsnprintf)(result->messageOutput, sizeMessageBuf, pattern, result->messagePos, result->messagePrefix, result->message); if ( ( cfgBool(doc, TidyMuteShow) == yes ) && level <= TidyFatal ) { /*\ Issue #655 - Unsafe to use output buffer as one of the va_list * input parameters in some snprintf implementations. \*/ ctmbstr pc = TY_(tidyErrorCodeAsKey)(code); i = TY_(tmbstrlen)(result->messageOutputDefault); if (i < sizeMessageBuf) TY_(tmbsnprintf)(result->messageOutputDefault + i, sizeMessageBuf - i, " (%s)", pc ); i = TY_(tmbstrlen)(result->messageOutput); if (i < sizeMessageBuf) TY_(tmbsnprintf)(result->messageOutput + i, sizeMessageBuf - i, " (%s)", pc ); i = 0; } result->allowMessage = yes; /* reportFilter is a simple error filter that provides minimal information to callback functions, and includes the message buffer in LibTidy's configured localization. As it's a "legacy" API, it does not receive TidyDialogue messages.*/ if ( (result->level <= TidyFatal) && doc->reportFilter ) { result->allowMessage = result->allowMessage & doc->reportFilter( tdoc, result->level, result->line, result->column, result->messageOutput ); } /* reportCallback is intended to allow LibTidy users to localize messages via their own means by providing a key and the parameters to fill it. As it's a "legacy" API, it does not receive TidyDialogue messages. */ if ( (result->level <= TidyFatal) && doc->reportCallback ) { TidyDoc tdoc = tidyImplToDoc( doc ); va_copy(args_copy, args); result->allowMessage = result->allowMessage & doc->reportCallback( tdoc, result->level, result->line, result->column, result->messageKey, args_copy ); va_end(args_copy); } /* messageCallback is the newest interface to interrogate Tidy's emitted messages. */ if ( doc->messageCallback ) { result->allowMessage = result->allowMessage & doc->messageCallback( tidyImplToMessage(result) ); } return result; } TidyMessageImpl *TY_(tidyMessageCreate)( TidyDocImpl *doc, uint code, TidyReportLevel level, ... ) { TidyMessageImpl *result; va_list args; va_start(args, level); result = tidyMessageCreateInitV(doc, NULL, code, 0, 0, level, args); va_end(args); return result; } TidyMessageImpl *TY_(tidyMessageCreateWithNode)( TidyDocImpl *doc, Node *node, uint code, TidyReportLevel level, ... ) { TidyMessageImpl *result; va_list args_copy; int line = ( node ? node->line : ( doc->lexer ? doc->lexer->lines : 0 ) ); int col = ( node ? node->column : ( doc->lexer ? doc->lexer->columns : 0 ) ); va_start(args_copy, level); result = tidyMessageCreateInitV(doc, node, code, line, col, level, args_copy); va_end(args_copy); return result; } TidyMessageImpl *TY_(tidyMessageCreateWithLexer)( TidyDocImpl *doc, uint code, TidyReportLevel level, ... ) { TidyMessageImpl *result; va_list args_copy; int line = ( doc->lexer ? doc->lexer->lines : 0 ); int col = ( doc->lexer ? doc->lexer->columns : 0 ); va_start(args_copy, level); result = tidyMessageCreateInitV(doc, NULL, code, line, col, level, args_copy); va_end(args_copy); return result; } void TY_(tidyMessageRelease)( TidyMessageImpl *message ) { if ( !message ) return; TidyDocFree( tidyDocToImpl(message->tidyDoc), message->arguments ); TidyDocFree( tidyDocToImpl(message->tidyDoc), message->messageDefault ); TidyDocFree( tidyDocToImpl(message->tidyDoc), message->message ); TidyDocFree( tidyDocToImpl(message->tidyDoc), message->messagePosDefault ); TidyDocFree( tidyDocToImpl(message->tidyDoc), message->messagePos ); TidyDocFree( tidyDocToImpl(message->tidyDoc), message->messageOutputDefault ); TidyDocFree( tidyDocToImpl(message->tidyDoc), message->messageOutput ); TidyDocFree(tidyDocToImpl(message->tidyDoc), message); /* Issue #597 - and discard the message structure */ } /********************************************************************* * Modern Message Callback Functions *********************************************************************/ TidyDocImpl* TY_(getMessageDoc)( TidyMessageImpl message ) { return message.tidyDoc; } uint TY_(getMessageCode)( TidyMessageImpl message ) { return message.code; } ctmbstr TY_(getMessageKey)( TidyMessageImpl message ) { return message.messageKey; } int TY_(getMessageLine)( TidyMessageImpl message ) { return message.line; } int TY_(getMessageColumn)( TidyMessageImpl message ) { return message.column; } TidyReportLevel TY_(getMessageLevel)( TidyMessageImpl message ) { return message.level; } Bool TY_(getMessageIsMuted)( TidyMessageImpl message ) { return message.muted; } ctmbstr TY_(getMessageFormatDefault)( TidyMessageImpl message ) { return message.messageFormatDefault; } ctmbstr TY_(getMessageFormat)( TidyMessageImpl message ) { return message.messageFormat; } ctmbstr TY_(getMessageDefault)( TidyMessageImpl message ) { return message.messageDefault; } ctmbstr TY_(getMessage)( TidyMessageImpl message ) { return message.message; } ctmbstr TY_(getMessagePosDefault)( TidyMessageImpl message ) { return message.messagePosDefault; } ctmbstr TY_(getMessagePos)( TidyMessageImpl message ) { return message.messagePos; } ctmbstr TY_(getMessagePrefixDefault)( TidyMessageImpl message ) { return message.messagePrefixDefault; } ctmbstr TY_(getMessagePrefix)( TidyMessageImpl message ) { return message.messagePrefix; } ctmbstr TY_(getMessageOutputDefault)( TidyMessageImpl message ) { return message.messageOutputDefault; } ctmbstr TY_(getMessageOutput)( TidyMessageImpl message ) { return message.messageOutput; } /********************************************************************* * Message Argument Interrogation *********************************************************************/ TidyIterator TY_(getMessageArguments)( TidyMessageImpl message ) { if (message.argcount > 0) return (TidyIterator) (size_t)1; else return (TidyIterator) (size_t)0; } TidyMessageArgument TY_(getNextMessageArgument)( TidyMessageImpl message, TidyIterator* iter ) { size_t item = 0; size_t itemIndex; assert( iter != NULL ); itemIndex = (size_t)*iter; if ( itemIndex >= 1 && itemIndex <= (size_t)message.argcount ) { item = itemIndex; itemIndex++; } /* Just as TidyIterator is really just a dumb, one-based index, the TidyMessageArgument is really just a dumb, zero-based index; however this type of iterator and opaque interrogation is simply how Tidy does things. */ *iter = (TidyIterator)( itemIndex <= (size_t)message.argcount ? itemIndex : (size_t)0 ); return (TidyMessageArgument)item; } TidyFormatParameterType TY_(getArgType)( TidyMessageImpl message, TidyMessageArgument* arg ) { int argNum = (int)(size_t)*arg - 1; assert( argNum <= message.argcount ); return message.arguments[argNum].type; } ctmbstr TY_(getArgFormat)( TidyMessageImpl message, TidyMessageArgument* arg ) { int argNum = (int)(size_t)*arg - 1; assert( argNum <= message.argcount ); return message.arguments[argNum].format; } ctmbstr TY_(getArgValueString)( TidyMessageImpl message, TidyMessageArgument* arg ) { int argNum = (int)(size_t)*arg - 1; assert( argNum <= message.argcount ); assert( message.arguments[argNum].type == tidyFormatType_STRING); return message.arguments[argNum].u.s; } uint TY_(getArgValueUInt)( TidyMessageImpl message, TidyMessageArgument* arg ) { int argNum = (int)(size_t)*arg - 1; assert( argNum <= message.argcount ); assert( message.arguments[argNum].type == tidyFormatType_UINT); return message.arguments[argNum].u.ui; } int TY_(getArgValueInt)( TidyMessageImpl message, TidyMessageArgument* arg ) { int argNum = (int)(size_t)*arg - 1; assert( argNum <= message.argcount ); assert( message.arguments[argNum].type == tidyFormatType_INT); return message.arguments[argNum].u.i; } double TY_(getArgValueDouble)( TidyMessageImpl message, TidyMessageArgument* arg ) { int argNum = (int)(size_t)*arg - 1; assert( argNum <= message.argcount ); assert( message.arguments[argNum].type == tidyFormatType_DOUBLE); return message.arguments[argNum].u.d; } /********************************************************************* * BuildArgArray support * Adapted loosely from Mozilla `prprf.c`, Mozilla Public License: * - https://www.mozilla.org/en-US/MPL/2.0/ *********************************************************************/ /** Returns a pointer to an allocated array of `printfArg` given a format ** string and a va_list, or NULL if not successful or no parameters were ** given. Parameter `rv` will return with the count of zero or more ** parameters if successful, else -1. ** ** We'll also be sure to use the document's allocator if specified, thus ** the requirement to pass in a TidyDocImpl. ** ** Currently Tidy only uses %c, %d, %s, %u, %X, although doubles are ** supported as well. Unsupported arguments will result in failure as ** described above. */ static struct printfArg* BuildArgArray( TidyDocImpl *doc, ctmbstr fmt, va_list ap, int* rv ) { int number = 0; /* the quantity of valid arguments found; returned as rv. */ int cn = -1; /* keeps track of which parameter index is current. */ int i = 0; /* typical index. */ int pos = -1; /* starting position of current argument. */ const char* p; /* current position in format string. */ char c; /* current character. */ struct printfArg* nas; /* first pass: determine number of valid % to allocate space. */ p = fmt; *rv = 0; while( ( c = *p++ ) != 0 ) { if( c != '%' ) continue; if( ( c = *p++ ) == '%' ) /* skip %% case */ continue; else number++; } if( number == 0 ) return NULL; nas = (struct printfArg*)TidyDocAlloc( doc, number * sizeof( struct printfArg ) ); if( !nas ) { *rv = -1; return NULL; } for( i = 0; i < number; i++ ) { nas[i].type = tidyFormatType_UNKNOWN; } /* second pass: set nas[].type and location. */ p = fmt; while( ( c = *p++ ) != 0 ) { if( c != '%' ) continue; if( ( c = *p++ ) == '%' ) continue; /* skip %% case */ pos = p - fmt - 2; /* p already incremented twice */ /* width -- width via parameter */ if (c == '*') { /* not supported feature */ *rv = -1; break; } /* width field -- skip */ while ((c >= '0') && (c <= '9')) { c = *p++; } /* precision */ if (c == '.') { c = *p++; if (c == '*') { /* not supported feature */ *rv = -1; break; } while ((c >= '0') && (c <= '9')) { c = *p++; } } cn++; /* size and format */ nas[cn].type = tidyFormatType_UINT; switch (c) { case 'c': /* unsigned int (char) */ case 'u': /* unsigned int */ case 'X': /* unsigned int as hex */ case 'x': /* unsigned int as hex */ case 'o': /* octal */ nas[cn].u.ui = va_arg( ap, unsigned int ); break; case 'd': /* signed int */ case 'i': /* signed int */ nas[cn].type = tidyFormatType_INT; nas[cn].u.i = va_arg( ap, int ); break; case 's': /* string */ nas[cn].type = tidyFormatType_STRING; nas[cn].u.s = va_arg( ap, char* ); break; case 'e': /* double */ case 'E': /* double */ case 'f': /* double */ case 'F': /* double */ case 'g': /* double */ case 'G': /* double */ nas[cn].type = tidyFormatType_DOUBLE; nas[cn].u.d = va_arg( ap, double ); break; default: nas[cn].type = tidyFormatType_UNKNOWN; *rv = -1; break; } /* position and format */ nas[cn].formatStart = pos; nas[cn].formatLength = (p - fmt) - pos; /* the format string exceeds the buffer length */ if ( nas[cn].formatLength >= FORMAT_LENGTH ) { *rv = -1; break; } else { strncpy(nas[cn].format, fmt + nas[cn].formatStart, nas[cn].formatLength); nas[cn].format[nas[cn].formatLength] = 0; /* Is. #800 - If count <= srcLen, no 0 added! */ } /* Something's not right. */ if( nas[cn].type == tidyFormatType_UNKNOWN ) { *rv = -1; break; } } /* third pass: fill the nas[cn].ap */ if( *rv < 0 ) { TidyDocFree( doc, nas );; return NULL; } *rv = number; return nas; }