1 | // © 2016 and later: Unicode, Inc. and others. |
2 | // License & terms of use: http://www.unicode.org/copyright.html |
3 | /* |
4 | ******************************************************************************* |
5 | * Copyright (C) 2003-2014, International Business Machines |
6 | * Corporation and others. All Rights Reserved. |
7 | ******************************************************************************* |
8 | * file name: utrace.c |
9 | * encoding: UTF-8 |
10 | * tab size: 8 (not used) |
11 | * indentation:4 |
12 | */ |
13 | |
14 | #include "unicode/utrace.h" |
15 | #include "utracimp.h" |
16 | #include "cstring.h" |
17 | #include "uassert.h" |
18 | #include "ucln_cmn.h" |
19 | |
20 | |
21 | static UTraceEntry *pTraceEntryFunc = nullptr; |
22 | static UTraceExit *pTraceExitFunc = nullptr; |
23 | static UTraceData *pTraceDataFunc = nullptr; |
24 | static const void *gTraceContext = nullptr; |
25 | |
26 | /** |
27 | * \var utrace_level |
28 | * Trace level variable. Negative for "off". |
29 | */ |
30 | static int32_t |
31 | utrace_level = UTRACE_ERROR; |
32 | |
33 | U_CAPI void U_EXPORT2 |
34 | utrace_entry(int32_t fnNumber) { |
35 | if (pTraceEntryFunc != nullptr) { |
36 | (*pTraceEntryFunc)(gTraceContext, fnNumber); |
37 | } |
38 | } |
39 | |
40 | |
41 | static const char gExitFmt[] = "Returns." ; |
42 | static const char gExitFmtValue[] = "Returns %d." ; |
43 | static const char gExitFmtStatus[] = "Returns. Status = %d." ; |
44 | static const char gExitFmtValueStatus[] = "Returns %d. Status = %d." ; |
45 | static const char gExitFmtPtrStatus[] = "Returns %d. Status = %p." ; |
46 | |
47 | U_CAPI void U_EXPORT2 |
48 | utrace_exit(int32_t fnNumber, int32_t returnType, ...) { |
49 | if (pTraceExitFunc != nullptr) { |
50 | va_list args; |
51 | const char *fmt; |
52 | |
53 | switch (returnType) { |
54 | case 0: |
55 | fmt = gExitFmt; |
56 | break; |
57 | case UTRACE_EXITV_I32: |
58 | fmt = gExitFmtValue; |
59 | break; |
60 | case UTRACE_EXITV_STATUS: |
61 | fmt = gExitFmtStatus; |
62 | break; |
63 | case UTRACE_EXITV_I32 | UTRACE_EXITV_STATUS: |
64 | fmt = gExitFmtValueStatus; |
65 | break; |
66 | case UTRACE_EXITV_PTR | UTRACE_EXITV_STATUS: |
67 | fmt = gExitFmtPtrStatus; |
68 | break; |
69 | default: |
70 | UPRV_UNREACHABLE_EXIT; |
71 | } |
72 | |
73 | va_start(args, returnType); |
74 | (*pTraceExitFunc)(gTraceContext, fnNumber, fmt, args); |
75 | va_end(args); |
76 | } |
77 | } |
78 | |
79 | |
80 | |
81 | U_CAPI void U_EXPORT2 |
82 | utrace_data(int32_t fnNumber, int32_t level, const char *fmt, ...) { |
83 | if (pTraceDataFunc != nullptr) { |
84 | va_list args; |
85 | va_start(args, fmt ); |
86 | (*pTraceDataFunc)(gTraceContext, fnNumber, level, fmt, args); |
87 | va_end(args); |
88 | } |
89 | } |
90 | |
91 | |
92 | static void outputChar(char c, char *outBuf, int32_t *outIx, int32_t capacity, int32_t indent) { |
93 | int32_t i; |
94 | /* Check whether a start of line indenting is needed. Three cases: |
95 | * 1. At the start of the first line (output index == 0). |
96 | * 2. At the start of subsequent lines (preceding char in buffer == '\n') |
97 | * 3. When preflighting buffer len (buffer capacity is exceeded), when |
98 | * a \n is output. Ideally we wouldn't do the indent until the following char |
99 | * is received, but that won't work because there's no place to remember that |
100 | * the preceding char was \n. Meaning that we may overstimate the |
101 | * buffer size needed. No harm done. |
102 | */ |
103 | if (*outIx==0 || /* case 1. */ |
104 | (c!='\n' && c!=0 && *outIx < capacity && outBuf[(*outIx)-1]=='\n') || /* case 2. */ |
105 | (c=='\n' && *outIx>=capacity)) /* case 3 */ |
106 | { |
107 | /* At the start of a line. Indent. */ |
108 | for(i=0; i<indent; i++) { |
109 | if (*outIx < capacity) { |
110 | outBuf[*outIx] = ' '; |
111 | } |
112 | (*outIx)++; |
113 | } |
114 | } |
115 | |
116 | if (*outIx < capacity) { |
117 | outBuf[*outIx] = c; |
118 | } |
119 | if (c != 0) { |
120 | /* NULs only appear as end-of-string terminators. Move them to the output |
121 | * buffer, but do not update the length of the buffer, so that any |
122 | * following output will overwrite the NUL. */ |
123 | (*outIx)++; |
124 | } |
125 | } |
126 | |
127 | static void outputHexBytes(int64_t val, int32_t charsToOutput, |
128 | char *outBuf, int32_t *outIx, int32_t capacity) { |
129 | static const char gHexChars[] = "0123456789abcdef" ; |
130 | int32_t shiftCount; |
131 | for (shiftCount=(charsToOutput-1)*4; shiftCount >= 0; shiftCount-=4) { |
132 | char c = gHexChars[(val >> shiftCount) & 0xf]; |
133 | outputChar(c, outBuf, outIx, capacity, 0); |
134 | } |
135 | } |
136 | |
137 | /* Output a pointer value in hex. Work with any size of pointer */ |
138 | static void outputPtrBytes(void *val, char *outBuf, int32_t *outIx, int32_t capacity) { |
139 | uint32_t i; |
140 | int32_t incVal = 1; /* +1 for big endian, -1 for little endian */ |
141 | char *p = (char *)&val; /* point to current byte to output in the ptr val */ |
142 | |
143 | #if !U_IS_BIG_ENDIAN |
144 | /* Little Endian. Move p to most significant end of the value */ |
145 | incVal = -1; |
146 | p += sizeof(void *) - 1; |
147 | #endif |
148 | |
149 | /* Loop through the bytes of the ptr as it sits in memory, from |
150 | * most significant to least significant end */ |
151 | for (i=0; i<sizeof(void *); i++) { |
152 | outputHexBytes(*p, 2, outBuf, outIx, capacity); |
153 | p += incVal; |
154 | } |
155 | } |
156 | |
157 | static void outputString(const char *s, char *outBuf, int32_t *outIx, int32_t capacity, int32_t indent) { |
158 | int32_t i = 0; |
159 | char c; |
160 | if (s==nullptr) { |
161 | s = "*NULL*" ; |
162 | } |
163 | do { |
164 | c = s[i++]; |
165 | outputChar(c, outBuf, outIx, capacity, indent); |
166 | } while (c != 0); |
167 | } |
168 | |
169 | |
170 | |
171 | static void outputUString(const char16_t *s, int32_t len, |
172 | char *outBuf, int32_t *outIx, int32_t capacity, int32_t indent) { |
173 | int32_t i = 0; |
174 | char16_t c; |
175 | if (s==nullptr) { |
176 | outputString(nullptr, outBuf, outIx, capacity, indent); |
177 | return; |
178 | } |
179 | |
180 | for (i=0; i<len || len==-1; i++) { |
181 | c = s[i]; |
182 | outputHexBytes(c, 4, outBuf, outIx, capacity); |
183 | outputChar(' ', outBuf, outIx, capacity, indent); |
184 | if (len == -1 && c==0) { |
185 | break; |
186 | } |
187 | } |
188 | } |
189 | |
190 | U_CAPI int32_t U_EXPORT2 |
191 | utrace_vformat(char *outBuf, int32_t capacity, int32_t indent, const char *fmt, va_list args) { |
192 | int32_t outIx = 0; |
193 | int32_t fmtIx = 0; |
194 | char fmtC; |
195 | char c; |
196 | int32_t intArg; |
197 | int64_t longArg = 0; |
198 | char *ptrArg; |
199 | |
200 | /* Loop runs once for each character in the format string. |
201 | */ |
202 | for (;;) { |
203 | fmtC = fmt[fmtIx++]; |
204 | if (fmtC != '%') { |
205 | /* Literal character, not part of a %sequence. Just copy it to the output. */ |
206 | outputChar(fmtC, outBuf, &outIx, capacity, indent); |
207 | if (fmtC == 0) { |
208 | /* We hit the NUL that terminates the format string. |
209 | * This is the normal (and only) exit from the loop that |
210 | * interprets the format |
211 | */ |
212 | break; |
213 | } |
214 | continue; |
215 | } |
216 | |
217 | /* We encountered a '%'. Pick up the following format char */ |
218 | fmtC = fmt[fmtIx++]; |
219 | |
220 | switch (fmtC) { |
221 | case 'c': |
222 | /* single 8 bit char */ |
223 | c = (char)va_arg(args, int32_t); |
224 | outputChar(c, outBuf, &outIx, capacity, indent); |
225 | break; |
226 | |
227 | case 's': |
228 | /* char * string, NUL terminated. */ |
229 | ptrArg = va_arg(args, char *); |
230 | outputString((const char *)ptrArg, outBuf, &outIx, capacity, indent); |
231 | break; |
232 | |
233 | case 'S': |
234 | /* char16_t * string, with length, len==-1 for NUL terminated. */ |
235 | ptrArg = va_arg(args, char *); /* Ptr */ |
236 | intArg =(int32_t)va_arg(args, int32_t); /* Length */ |
237 | outputUString((const char16_t *)ptrArg, intArg, outBuf, &outIx, capacity, indent); |
238 | break; |
239 | |
240 | case 'b': |
241 | /* 8 bit int */ |
242 | intArg = va_arg(args, int); |
243 | outputHexBytes(intArg, 2, outBuf, &outIx, capacity); |
244 | break; |
245 | |
246 | case 'h': |
247 | /* 16 bit int */ |
248 | intArg = va_arg(args, int); |
249 | outputHexBytes(intArg, 4, outBuf, &outIx, capacity); |
250 | break; |
251 | |
252 | case 'd': |
253 | /* 32 bit int */ |
254 | intArg = va_arg(args, int); |
255 | outputHexBytes(intArg, 8, outBuf, &outIx, capacity); |
256 | break; |
257 | |
258 | case 'l': |
259 | /* 64 bit long */ |
260 | longArg = va_arg(args, int64_t); |
261 | outputHexBytes(longArg, 16, outBuf, &outIx, capacity); |
262 | break; |
263 | |
264 | case 'p': |
265 | /* Pointers. */ |
266 | ptrArg = va_arg(args, char *); |
267 | outputPtrBytes(ptrArg, outBuf, &outIx, capacity); |
268 | break; |
269 | |
270 | case 0: |
271 | /* Single '%' at end of fmt string. Output as literal '%'. |
272 | * Back up index into format string so that the terminating NUL will be |
273 | * re-fetched in the outer loop, causing it to terminate. |
274 | */ |
275 | outputChar('%', outBuf, &outIx, capacity, indent); |
276 | fmtIx--; |
277 | break; |
278 | |
279 | case 'v': |
280 | { |
281 | /* Vector of values, e.g. %vh */ |
282 | char vectorType; |
283 | int32_t vectorLen; |
284 | const char *i8Ptr; |
285 | int16_t *i16Ptr; |
286 | int32_t *i32Ptr; |
287 | int64_t *i64Ptr; |
288 | void **ptrPtr; |
289 | int32_t charsToOutput = 0; |
290 | int32_t i; |
291 | |
292 | vectorType = fmt[fmtIx]; /* b, h, d, l, p, etc. */ |
293 | if (vectorType != 0) { |
294 | fmtIx++; |
295 | } |
296 | i8Ptr = (const char *)va_arg(args, void*); |
297 | i16Ptr = (int16_t *)i8Ptr; |
298 | i32Ptr = (int32_t *)i8Ptr; |
299 | i64Ptr = (int64_t *)i8Ptr; |
300 | ptrPtr = (void **)i8Ptr; |
301 | vectorLen =(int32_t)va_arg(args, int32_t); |
302 | if (ptrPtr == nullptr) { |
303 | outputString("*NULL* " , outBuf, &outIx, capacity, indent); |
304 | } else { |
305 | for (i=0; i<vectorLen || vectorLen==-1; i++) { |
306 | switch (vectorType) { |
307 | case 'b': |
308 | charsToOutput = 2; |
309 | longArg = *i8Ptr++; |
310 | break; |
311 | case 'h': |
312 | charsToOutput = 4; |
313 | longArg = *i16Ptr++; |
314 | break; |
315 | case 'd': |
316 | charsToOutput = 8; |
317 | longArg = *i32Ptr++; |
318 | break; |
319 | case 'l': |
320 | charsToOutput = 16; |
321 | longArg = *i64Ptr++; |
322 | break; |
323 | case 'p': |
324 | charsToOutput = 0; |
325 | outputPtrBytes(*ptrPtr, outBuf, &outIx, capacity); |
326 | longArg = *ptrPtr==nullptr? 0: 1; /* test for nullptr terminated array. */ |
327 | ptrPtr++; |
328 | break; |
329 | case 'c': |
330 | charsToOutput = 0; |
331 | outputChar(*i8Ptr, outBuf, &outIx, capacity, indent); |
332 | longArg = *i8Ptr; /* for test for nullptr terminated array. */ |
333 | i8Ptr++; |
334 | break; |
335 | case 's': |
336 | charsToOutput = 0; |
337 | outputString((const char *)*ptrPtr, outBuf, &outIx, capacity, indent); |
338 | outputChar('\n', outBuf, &outIx, capacity, indent); |
339 | longArg = *ptrPtr==nullptr? 0: 1; /* for test for nullptr term. array. */ |
340 | ptrPtr++; |
341 | break; |
342 | |
343 | case 'S': |
344 | charsToOutput = 0; |
345 | outputUString((const char16_t *)*ptrPtr, -1, outBuf, &outIx, capacity, indent); |
346 | outputChar('\n', outBuf, &outIx, capacity, indent); |
347 | longArg = *ptrPtr==nullptr? 0: 1; /* for test for nullptr term. array. */ |
348 | ptrPtr++; |
349 | break; |
350 | |
351 | |
352 | } |
353 | if (charsToOutput > 0) { |
354 | outputHexBytes(longArg, charsToOutput, outBuf, &outIx, capacity); |
355 | outputChar(' ', outBuf, &outIx, capacity, indent); |
356 | } |
357 | if (vectorLen == -1 && longArg == 0) { |
358 | break; |
359 | } |
360 | } |
361 | } |
362 | outputChar('[', outBuf, &outIx, capacity, indent); |
363 | outputHexBytes(vectorLen, 8, outBuf, &outIx, capacity); |
364 | outputChar(']', outBuf, &outIx, capacity, indent); |
365 | } |
366 | break; |
367 | |
368 | |
369 | default: |
370 | /* %. in format string, where . is some character not in the set |
371 | * of recognized format chars. Just output it as if % wasn't there. |
372 | * (Covers "%%" outputting a single '%') |
373 | */ |
374 | outputChar(fmtC, outBuf, &outIx, capacity, indent); |
375 | } |
376 | } |
377 | outputChar(0, outBuf, &outIx, capacity, indent); /* Make sure that output is NUL terminated */ |
378 | return outIx + 1; /* outIx + 1 because outIx does not increment when outputting final NUL. */ |
379 | } |
380 | |
381 | |
382 | |
383 | |
384 | U_CAPI int32_t U_EXPORT2 |
385 | utrace_format(char *outBuf, int32_t capacity, |
386 | int32_t indent, const char *fmt, ...) { |
387 | int32_t retVal; |
388 | va_list args; |
389 | va_start(args, fmt ); |
390 | retVal = utrace_vformat(outBuf, capacity, indent, fmt, args); |
391 | va_end(args); |
392 | return retVal; |
393 | } |
394 | |
395 | |
396 | U_CAPI void U_EXPORT2 |
397 | utrace_setFunctions(const void *context, |
398 | UTraceEntry *e, UTraceExit *x, UTraceData *d) { |
399 | pTraceEntryFunc = e; |
400 | pTraceExitFunc = x; |
401 | pTraceDataFunc = d; |
402 | gTraceContext = context; |
403 | } |
404 | |
405 | |
406 | U_CAPI void U_EXPORT2 |
407 | utrace_getFunctions(const void **context, |
408 | UTraceEntry **e, UTraceExit **x, UTraceData **d) { |
409 | *e = pTraceEntryFunc; |
410 | *x = pTraceExitFunc; |
411 | *d = pTraceDataFunc; |
412 | *context = gTraceContext; |
413 | } |
414 | |
415 | U_CAPI void U_EXPORT2 |
416 | utrace_setLevel(int32_t level) { |
417 | if (level < UTRACE_OFF) { |
418 | level = UTRACE_OFF; |
419 | } |
420 | if (level > UTRACE_VERBOSE) { |
421 | level = UTRACE_VERBOSE; |
422 | } |
423 | utrace_level = level; |
424 | } |
425 | |
426 | U_CAPI int32_t U_EXPORT2 |
427 | utrace_getLevel() { |
428 | return utrace_level; |
429 | } |
430 | |
431 | |
432 | U_CFUNC UBool |
433 | utrace_cleanup() { |
434 | pTraceEntryFunc = nullptr; |
435 | pTraceExitFunc = nullptr; |
436 | pTraceDataFunc = nullptr; |
437 | utrace_level = UTRACE_OFF; |
438 | gTraceContext = nullptr; |
439 | return true; |
440 | } |
441 | |
442 | |
443 | static const char * const |
444 | trFnName[] = { |
445 | "u_init" , |
446 | "u_cleanup" , |
447 | nullptr |
448 | }; |
449 | |
450 | |
451 | static const char * const |
452 | trConvNames[] = { |
453 | "ucnv_open" , |
454 | "ucnv_openPackage" , |
455 | "ucnv_openAlgorithmic" , |
456 | "ucnv_clone" , |
457 | "ucnv_close" , |
458 | "ucnv_flushCache" , |
459 | "ucnv_load" , |
460 | "ucnv_unload" , |
461 | nullptr |
462 | }; |
463 | |
464 | |
465 | static const char * const |
466 | trCollNames[] = { |
467 | "ucol_open" , |
468 | "ucol_close" , |
469 | "ucol_strcoll" , |
470 | "ucol_getSortKey" , |
471 | "ucol_getLocale" , |
472 | "ucol_nextSortKeyPart" , |
473 | "ucol_strcollIter" , |
474 | "ucol_openFromShortString" , |
475 | "ucol_strcollUTF8" , |
476 | nullptr |
477 | }; |
478 | |
479 | |
480 | static const char* const |
481 | trResDataNames[] = { |
482 | "resc" , |
483 | "bundle-open" , |
484 | "file-open" , |
485 | "res-open" , |
486 | nullptr |
487 | }; |
488 | |
489 | |
490 | U_CAPI const char * U_EXPORT2 |
491 | utrace_functionName(int32_t fnNumber) { |
492 | if(UTRACE_FUNCTION_START <= fnNumber && fnNumber < UTRACE_FUNCTION_LIMIT) { |
493 | return trFnName[fnNumber]; |
494 | } else if(UTRACE_CONVERSION_START <= fnNumber && fnNumber < UTRACE_CONVERSION_LIMIT) { |
495 | return trConvNames[fnNumber - UTRACE_CONVERSION_START]; |
496 | } else if(UTRACE_COLLATION_START <= fnNumber && fnNumber < UTRACE_COLLATION_LIMIT){ |
497 | return trCollNames[fnNumber - UTRACE_COLLATION_START]; |
498 | } else if(UTRACE_UDATA_START <= fnNumber && fnNumber < UTRACE_RES_DATA_LIMIT){ |
499 | return trResDataNames[fnNumber - UTRACE_UDATA_START]; |
500 | } else { |
501 | return "[BOGUS Trace Function Number]" ; |
502 | } |
503 | } |
504 | |
505 | |