blob: ca4dddfa1a38fbd7917a560803b8d9dacef1f6be [file] [log] [blame]
// Copyright (C) 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2003-2014, International Business Machines
* Corporation and others. All Rights Reserved.
*******************************************************************************
* file name: utrace.c
* encoding: US-ASCII
* tab size: 8 (not used)
* indentation:4
*/
#define UTRACE_IMPL
#include "unicode/utrace.h"
#include "utracimp.h"
#include "cstring.h"
#include "uassert.h"
#include "ucln_cmn.h"
static UTraceEntry *pTraceEntryFunc = NULL;
static UTraceExit *pTraceExitFunc = NULL;
static UTraceData *pTraceDataFunc = NULL;
static const void *gTraceContext = NULL;
U_EXPORT int32_t
utrace_level = UTRACE_ERROR;
U_CAPI void U_EXPORT2
utrace_entry(int32_t fnNumber) {
if (pTraceEntryFunc != NULL) {
(*pTraceEntryFunc)(gTraceContext, fnNumber);
}
}
static const char gExitFmt[] = "Returns.";
static const char gExitFmtValue[] = "Returns %d.";
static const char gExitFmtStatus[] = "Returns. Status = %d.";
static const char gExitFmtValueStatus[] = "Returns %d. Status = %d.";
static const char gExitFmtPtrStatus[] = "Returns %d. Status = %p.";
U_CAPI void U_EXPORT2
utrace_exit(int32_t fnNumber, int32_t returnType, ...) {
if (pTraceExitFunc != NULL) {
va_list args;
const char *fmt;
switch (returnType) {
case 0:
fmt = gExitFmt;
break;
case UTRACE_EXITV_I32:
fmt = gExitFmtValue;
break;
case UTRACE_EXITV_STATUS:
fmt = gExitFmtStatus;
break;
case UTRACE_EXITV_I32 | UTRACE_EXITV_STATUS:
fmt = gExitFmtValueStatus;
break;
case UTRACE_EXITV_PTR | UTRACE_EXITV_STATUS:
fmt = gExitFmtPtrStatus;
break;
default:
U_ASSERT(FALSE);
fmt = gExitFmt;
}
va_start(args, returnType);
(*pTraceExitFunc)(gTraceContext, fnNumber, fmt, args);
va_end(args);
}
}
U_CAPI void U_EXPORT2
utrace_data(int32_t fnNumber, int32_t level, const char *fmt, ...) {
if (pTraceDataFunc != NULL) {
va_list args;
va_start(args, fmt );
(*pTraceDataFunc)(gTraceContext, fnNumber, level, fmt, args);
va_end(args);
}
}
static void outputChar(char c, char *outBuf, int32_t *outIx, int32_t capacity, int32_t indent) {
int32_t i;
/* Check whether a start of line indenting is needed. Three cases:
* 1. At the start of the first line (output index == 0).
* 2. At the start of subsequent lines (preceeding char in buffer == '\n')
* 3. When preflighting buffer len (buffer capacity is exceeded), when
* a \n is output. Ideally we wouldn't do the indent until the following char
* is received, but that won't work because there's no place to remember that
* the preceding char was \n. Meaning that we may overstimate the
* buffer size needed. No harm done.
*/
if (*outIx==0 || /* case 1. */
(c!='\n' && c!=0 && *outIx < capacity && outBuf[(*outIx)-1]=='\n') || /* case 2. */
(c=='\n' && *outIx>=capacity)) /* case 3 */
{
/* At the start of a line. Indent. */
for(i=0; i<indent; i++) {
if (*outIx < capacity) {
outBuf[*outIx] = ' ';
}
(*outIx)++;
}
}
if (*outIx < capacity) {
outBuf[*outIx] = c;
}
if (c != 0) {
/* Nulls only appear as end-of-string terminators. Move them to the output
* buffer, but do not update the length of the buffer, so that any
* following output will overwrite the null. */
(*outIx)++;
}
}
static void outputHexBytes(int64_t val, int32_t charsToOutput,
char *outBuf, int32_t *outIx, int32_t capacity) {
static const char gHexChars[] = "0123456789abcdef";
int32_t shiftCount;
for (shiftCount=(charsToOutput-1)*4; shiftCount >= 0; shiftCount-=4) {
char c = gHexChars[(val >> shiftCount) & 0xf];
outputChar(c, outBuf, outIx, capacity, 0);
}
}
/* Output a pointer value in hex. Work with any size of pointer */
static void outputPtrBytes(void *val, char *outBuf, int32_t *outIx, int32_t capacity) {
int32_t i;
int32_t incVal = 1; /* +1 for big endian, -1 for little endian */
char *p = (char *)&val; /* point to current byte to output in the ptr val */
#if !U_IS_BIG_ENDIAN
/* Little Endian. Move p to most significant end of the value */
incVal = -1;
p += sizeof(void *) - 1;
#endif
/* Loop through the bytes of the ptr as it sits in memory, from
* most significant to least significant end */
for (i=0; i<sizeof(void *); i++) {
outputHexBytes(*p, 2, outBuf, outIx, capacity);
p += incVal;
}
}
static void outputString(const char *s, char *outBuf, int32_t *outIx, int32_t capacity, int32_t indent) {
int32_t i = 0;
char c;
if (s==NULL) {
s = "*NULL*";
}
do {
c = s[i++];
outputChar(c, outBuf, outIx, capacity, indent);
} while (c != 0);
}
static void outputUString(const UChar *s, int32_t len,
char *outBuf, int32_t *outIx, int32_t capacity, int32_t indent) {
int32_t i = 0;
UChar c;
if (s==NULL) {
outputString(NULL, outBuf, outIx, capacity, indent);
return;
}
for (i=0; i<len || len==-1; i++) {
c = s[i];
outputHexBytes(c, 4, outBuf, outIx, capacity);
outputChar(' ', outBuf, outIx, capacity, indent);
if (len == -1 && c==0) {
break;
}
}
}
U_CAPI int32_t U_EXPORT2
utrace_vformat(char *outBuf, int32_t capacity, int32_t indent, const char *fmt, va_list args) {
int32_t outIx = 0;
int32_t fmtIx = 0;
char fmtC;
char c;
int32_t intArg;
int64_t longArg = 0;
char *ptrArg;
/* Loop runs once for each character in the format string.
*/
for (;;) {
fmtC = fmt[fmtIx++];
if (fmtC != '%') {
/* Literal character, not part of a %sequence. Just copy it to the output. */
outputChar(fmtC, outBuf, &outIx, capacity, indent);
if (fmtC == 0) {
/* We hit the null that terminates the format string.
* This is the normal (and only) exit from the loop that
* interprets the format
*/
break;
}
continue;
}
/* We encountered a '%'. Pick up the following format char */
fmtC = fmt[fmtIx++];
switch (fmtC) {
case 'c':
/* single 8 bit char */
c = (char)va_arg(args, int32_t);
outputChar(c, outBuf, &outIx, capacity, indent);
break;
case 's':
/* char * string, null terminated. */
ptrArg = va_arg(args, char *);
outputString((const char *)ptrArg, outBuf, &outIx, capacity, indent);
break;
case 'S':
/* UChar * string, with length, len==-1 for null terminated. */
ptrArg = va_arg(args, void *); /* Ptr */
intArg =(int32_t)va_arg(args, int32_t); /* Length */
outputUString((const UChar *)ptrArg, intArg, outBuf, &outIx, capacity, indent);
break;
case 'b':
/* 8 bit int */
intArg = va_arg(args, int);
outputHexBytes(intArg, 2, outBuf, &outIx, capacity);
break;
case 'h':
/* 16 bit int */
intArg = va_arg(args, int);
outputHexBytes(intArg, 4, outBuf, &outIx, capacity);
break;
case 'd':
/* 32 bit int */
intArg = va_arg(args, int);
outputHexBytes(intArg, 8, outBuf, &outIx, capacity);
break;
case 'l':
/* 64 bit long */
longArg = va_arg(args, int64_t);
outputHexBytes(longArg, 16, outBuf, &outIx, capacity);
break;
case 'p':
/* Pointers. */
ptrArg = va_arg(args, void *);
outputPtrBytes(ptrArg, outBuf, &outIx, capacity);
break;
case 0:
/* Single '%' at end of fmt string. Output as literal '%'.
* Back up index into format string so that the terminating null will be
* re-fetched in the outer loop, causing it to terminate.
*/
outputChar('%', outBuf, &outIx, capacity, indent);
fmtIx--;
break;
case 'v':
{
/* Vector of values, e.g. %vh */
char vectorType;
int32_t vectorLen;
const char *i8Ptr;
int16_t *i16Ptr;
int32_t *i32Ptr;
int64_t *i64Ptr;
void **ptrPtr;
int32_t charsToOutput = 0;
int32_t i;
vectorType = fmt[fmtIx]; /* b, h, d, l, p, etc. */
if (vectorType != 0) {
fmtIx++;
}
i8Ptr = (const char *)va_arg(args, void*);
i16Ptr = (int16_t *)i8Ptr;
i32Ptr = (int32_t *)i8Ptr;
i64Ptr = (int64_t *)i8Ptr;
ptrPtr = (void **)i8Ptr;
vectorLen =(int32_t)va_arg(args, int32_t);
if (ptrPtr == NULL) {
outputString("*NULL* ", outBuf, &outIx, capacity, indent);
} else {
for (i=0; i<vectorLen || vectorLen==-1; i++) {
switch (vectorType) {
case 'b':
charsToOutput = 2;
longArg = *i8Ptr++;
break;
case 'h':
charsToOutput = 4;
longArg = *i16Ptr++;
break;
case 'd':
charsToOutput = 8;
longArg = *i32Ptr++;
break;
case 'l':
charsToOutput = 16;
longArg = *i64Ptr++;
break;
case 'p':
charsToOutput = 0;
outputPtrBytes(*ptrPtr, outBuf, &outIx, capacity);
longArg = *ptrPtr==NULL? 0: 1; /* test for null terminated array. */
ptrPtr++;
break;
case 'c':
charsToOutput = 0;
outputChar(*i8Ptr, outBuf, &outIx, capacity, indent);
longArg = *i8Ptr; /* for test for null terminated array. */
i8Ptr++;
break;
case 's':
charsToOutput = 0;
outputString(*ptrPtr, outBuf, &outIx, capacity, indent);
outputChar('\n', outBuf, &outIx, capacity, indent);
longArg = *ptrPtr==NULL? 0: 1; /* for test for null term. array. */
ptrPtr++;
break;
case 'S':
charsToOutput = 0;
outputUString((const UChar *)*ptrPtr, -1, outBuf, &outIx, capacity, indent);
outputChar('\n', outBuf, &outIx, capacity, indent);
longArg = *ptrPtr==NULL? 0: 1; /* for test for null term. array. */
ptrPtr++;
break;
}
if (charsToOutput > 0) {
outputHexBytes(longArg, charsToOutput, outBuf, &outIx, capacity);
outputChar(' ', outBuf, &outIx, capacity, indent);
}
if (vectorLen == -1 && longArg == 0) {
break;
}
}
}
outputChar('[', outBuf, &outIx, capacity, indent);
outputHexBytes(vectorLen, 8, outBuf, &outIx, capacity);
outputChar(']', outBuf, &outIx, capacity, indent);
}
break;
default:
/* %. in format string, where . is some character not in the set
* of recognized format chars. Just output it as if % wasn't there.
* (Covers "%%" outputing a single '%')
*/
outputChar(fmtC, outBuf, &outIx, capacity, indent);
}
}
outputChar(0, outBuf, &outIx, capacity, indent); /* Make sure that output is null terminated */
return outIx + 1; /* outIx + 1 because outIx does not increment when outputing final null. */
}
U_CAPI int32_t U_EXPORT2
utrace_format(char *outBuf, int32_t capacity,
int32_t indent, const char *fmt, ...) {
int32_t retVal;
va_list args;
va_start(args, fmt );
retVal = utrace_vformat(outBuf, capacity, indent, fmt, args);
va_end(args);
return retVal;
}
U_CAPI void U_EXPORT2
utrace_setFunctions(const void *context,
UTraceEntry *e, UTraceExit *x, UTraceData *d) {
pTraceEntryFunc = e;
pTraceExitFunc = x;
pTraceDataFunc = d;
gTraceContext = context;
}
U_CAPI void U_EXPORT2
utrace_getFunctions(const void **context,
UTraceEntry **e, UTraceExit **x, UTraceData **d) {
*e = pTraceEntryFunc;
*x = pTraceExitFunc;
*d = pTraceDataFunc;
*context = gTraceContext;
}
U_CAPI void U_EXPORT2
utrace_setLevel(int32_t level) {
if (level < UTRACE_OFF) {
level = UTRACE_OFF;
}
if (level > UTRACE_VERBOSE) {
level = UTRACE_VERBOSE;
}
utrace_level = level;
}
U_CAPI int32_t U_EXPORT2
utrace_getLevel() {
return utrace_level;
}
U_CFUNC UBool
utrace_cleanup() {
pTraceEntryFunc = NULL;
pTraceExitFunc = NULL;
pTraceDataFunc = NULL;
utrace_level = UTRACE_OFF;
gTraceContext = NULL;
return TRUE;
}
static const char * const
trFnName[] = {
"u_init",
"u_cleanup",
NULL
};
static const char * const
trConvNames[] = {
"ucnv_open",
"ucnv_openPackage",
"ucnv_openAlgorithmic",
"ucnv_clone",
"ucnv_close",
"ucnv_flushCache",
"ucnv_load",
"ucnv_unload",
NULL
};
static const char * const
trCollNames[] = {
"ucol_open",
"ucol_close",
"ucol_strcoll",
"ucol_getSortKey",
"ucol_getLocale",
"ucol_nextSortKeyPart",
"ucol_strcollIter",
"ucol_openFromShortString",
"ucol_strcollUTF8",
NULL
};
U_CAPI const char * U_EXPORT2
utrace_functionName(int32_t fnNumber) {
if(UTRACE_FUNCTION_START <= fnNumber && fnNumber < UTRACE_FUNCTION_LIMIT) {
return trFnName[fnNumber];
} else if(UTRACE_CONVERSION_START <= fnNumber && fnNumber < UTRACE_CONVERSION_LIMIT) {
return trConvNames[fnNumber - UTRACE_CONVERSION_START];
} else if(UTRACE_COLLATION_START <= fnNumber && fnNumber < UTRACE_COLLATION_LIMIT){
return trCollNames[fnNumber - UTRACE_COLLATION_START];
} else {
return "[BOGUS Trace Function Number]";
}
}