blob: e9f4ee2728ad2b42312f08ca827a765ceb3bf5f1 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2018 Google, Inc. All rights reserved.
* Copyright (c) 2002-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* Copyright (c) 2002-2003 Massachusetts Institute of Technology */
/*
* iox.h: i/o routines for both Linux and Windows
*/
#include <limits.h> /* for UCHAR_MAX */
#ifdef IOX_WIDE_CHAR
# define TCHAR wchar_t
# define _T(s) L##s
# define TNAME(n) n##_wide
# define IF_WIDE_ELSE(x, y) x
#else
# define TCHAR char
# define _T(s) s
# define TNAME(n) n
# define IF_WIDE_ELSE(x, y) y
#endif
const static char TNAME(base_letters)[] = { _T('0'), _T('1'), _T('2'), _T('3'),
_T('4'), _T('5'), _T('6'), _T('7'),
_T('8'), _T('9'), _T('a'), _T('b'),
_T('c'), _T('d'), _T('e'), _T('f') };
const static char TNAME(base_letters_cap)[] = { _T('0'), _T('1'), _T('2'), _T('3'),
_T('4'), _T('5'), _T('6'), _T('7'),
_T('8'), _T('9'), _T('A'), _T('B'),
_T('C'), _T('D'), _T('E'), _T('F') };
/* convert uint64 to a string */
/* clang-format off */ /* (work around clang-format newline-after-type bug) */
static TCHAR *
TNAME(uint64_to_str)(uint64 num, int base, TCHAR *buf, int decimal,
bool caps)
/* clang-format on */
{
int cnt;
TCHAR *p = buf;
int end = (43 > decimal ? 43 : decimal);
ASSERT(decimal < BUF_SIZE - 1); /* so don't overflow buf */
buf[end] = '\0';
for (cnt = end - 1; cnt >= 0; cnt--) {
buf[cnt] = caps ? TNAME(base_letters_cap)[(num % base)]
: TNAME(base_letters)[(num % base)];
num /= base;
}
while (*p && *p == _T('0') && end - decimal > 0) {
p++;
decimal++;
}
return p;
}
/* convert ulong to a string */
/* clang-format off */ /* (work around clang-format newline-after-type bug) */
static TCHAR *
TNAME(ulong_to_str)(ulong num, int base, TCHAR *buf, int decimal, bool caps)
/* clang-format on */
{
int cnt;
TCHAR *p = buf;
int end = (22 > decimal ? 22 : decimal); /* room for 64 bits octal */
ASSERT(decimal < BUF_SIZE - 1); /* so don't overflow buf */
buf[end] = '\0';
for (cnt = end - 1; cnt >= 0; cnt--) {
buf[cnt] = caps ? TNAME(base_letters_cap)[(num % base)]
: TNAME(base_letters)[(num % base)];
num /= base;
}
while (*p && *p == _T('0') && end - decimal > 0) {
p++;
decimal++;
}
return p;
}
/* N.B.: when building with /QIfist casting rounds instead of truncating (i#763)!
* Thus, use double2int_trunc() instead of casting.
*/
/* clang-format off */ /* (work around clang-format newline-after-type bug) */
static TCHAR *
TNAME(double_to_str)(double d, int decimal, TCHAR *buf, bool force_dot,
bool suppress_zeros)
/* clang-format on */
{
/* support really big numbers with %f? */
TCHAR tmpbuf[BUF_SIZE];
TCHAR *pre, *post, *c;
long predot, postdot, sub, i;
/* get pre and post dot sections as integers */
if (d < 0)
d = -d;
if (decimal > 0)
predot = double2int_trunc(d);
else
predot = double2int(d);
sub = 1;
for (i = 0; i < decimal; i++)
sub *= 10;
postdot = double2int((d - double2int_trunc(d)) * (double)sub);
if (postdot == sub) {
/* we had a .9* that rounded up! */
postdot = 0;
predot++;
}
pre = TNAME(ulong_to_str)((ulong)predot, 10, tmpbuf, 1, false);
for (i = 0, c = pre; *c; c++)
buf[i++] = *c;
if (force_dot || !(decimal == 0 || (suppress_zeros && postdot == 0))) {
buf[i++] = _T('.');
post = TNAME(ulong_to_str)((ulong)postdot, 10, tmpbuf, decimal, false);
for (c = post; *c; c++)
buf[i++] = *c;
/* remove trailing zeros */
if (suppress_zeros) {
while (buf[i - 1] == _T('0'))
i--;
}
}
buf[i] = '\0';
ASSERT(i < BUF_SIZE); /* make sure don't overflow buffer */
return buf;
}
/* clang-format off */ /* (work around clang-format newline-after-type bug) */
static TCHAR *
TNAME(double_to_exp_str)(double d, int exp, int decimal, TCHAR *buf,
bool force_dot, bool suppress_zeros, bool caps)
/* clang-format on */
{
TCHAR tmp_buf[BUF_SIZE];
TCHAR *tc;
int i = 0;
uint abval;
tc = TNAME(double_to_str)(d, decimal, tmp_buf, force_dot, suppress_zeros);
while (*tc) {
buf[i++] = *tc++;
}
if (caps)
buf[i++] = _T('E');
else
buf[i++] = _T('e');
if (exp < 0) {
buf[i++] = _T('-');
abval = -exp;
} else {
buf[i++] = _T('+');
abval = exp;
}
/* exp value always printed as at least 2 characters */
tc = TNAME(ulong_to_str)((ulong)abval, 10, tmp_buf, 2, false);
while (*tc) {
buf[i++] = *tc++;
}
buf[i] = '\0';
ASSERT(i < BUF_SIZE); /* make sure don't overflow buffer */
return buf;
}
/* i#386: separated out to avoid floating-point instrs in d_r_vsnprintf */
/* clang-format off */ /* (work around clang-format newline-after-type bug) */
static const TCHAR *
TNAME(d_r_vsnprintf_float)(double val, const TCHAR *c,
TCHAR prefixbuf[3], TCHAR buf[BUF_SIZE],
int decimal, bool space_flag,
bool plus_flag, bool pound_flag)
/* clang-format on */
{
const TCHAR *str;
bool caps = (*c == _T('E')) || (*c == _T('G'));
double d = val;
int exp = 0;
bool is_g = (*c == _T('g') || *c == _T('G'));
/* i#1213: we must mask all fpu exceptions prior to running this code,
* as it assumes div-by-zero won't raise an exception.
* The caller must have already saved the app's full fpu state.
*/
dr_fpu_exception_init();
/* check for NaN */
if (val != val) {
if (caps)
str = _T("NAN");
else
str = _T("nan");
if (space_flag)
prefixbuf[0] = _T(' ');
return str;
}
if (decimal == -1)
decimal = 6; /* default */
if (val >= 0 && space_flag)
prefixbuf[0] = _T(' '); /* get prefix */
if (val >= 0 && plus_flag)
prefixbuf[0] = _T('+');
if (val < 0)
prefixbuf[0] = _T('-');
/* check for inf */
if (val == pos_inf || val == neg_inf) {
if (caps)
str = _T("INF");
else
str = _T("inf");
return str;
}
if (*c == _T('f')) { /* ready to generate string now for f */
str = TNAME(double_to_str)(val, decimal, buf, pound_flag, false);
return str;
}
/* get exponent value */
while (d >= 10.0 || d <= -10.0) {
exp++;
d = d / 10.0;
}
while (d < 1.0 && d > -1.0 && d != 0.0) {
exp--;
d = d * 10.0;
}
if (is_g)
decimal--; /* g/G precision is number of signifigant digits */
if (is_g && exp >= -4 && exp <= decimal) {
/* exp is small enough for f, print without exponent */
str = TNAME(double_to_str)(val, decimal, buf, pound_flag, !pound_flag);
} else {
/* print with exponent */
str = TNAME(double_to_exp_str)(d, exp, decimal, buf, pound_flag,
is_g && !pound_flag, caps);
}
return str;
}
/* Returns number of chars printed, not including the null terminator.
* If number is larger than max,
* prints max (without null) and returns -1.
* For %S on Windows, converts between UTF-8 and UTF-16, and returns -1
* if passed an invalid encoding.
* (Thus, matches Windows snprintf, not Linux.)
*/
/* clang-format off */ /* (work around clang-format newline-after-type bug) */
int
TNAME(d_r_vsnprintf)(TCHAR *s, size_t max, const TCHAR *fmt, va_list ap)
/* clang-format on */
{
const TCHAR *c;
const TCHAR *str = NULL;
TCHAR *start = s;
TCHAR buf[BUF_SIZE];
if (fmt == NULL)
return 0;
if (max == 0)
goto max_reached;
c = fmt;
while (*c) {
if (*c == _T('%')) {
int fill = 0;
TCHAR filler = _T(' ');
int decimal = -1; /* codes defaults (6 int, 1 float, all string) */
TCHAR charbuf[2] = { _T('\0'), _T('\0') };
TCHAR prefixbuf[3] = { _T('\0'), _T('\0'), _T('\0') };
TCHAR *prefix;
bool minus_flag = false;
bool plus_flag = false;
bool pound_flag = false;
bool space_flag = false;
bool h_type = false;
bool l_type = false;
bool ll_type = false;
#ifdef IOX_WIDE_CHAR
char *wstr = NULL;
#else
wchar_t *wstr = NULL;
#endif
prefix = prefixbuf;
c++;
ASSERT(*c);
/* Collect flags -, +, #, 0, */
while (*c == _T('0') || *c == _T('-') || *c == _T('#') || *c == _T('+') ||
*c == _T(' ')) {
if (*c == _T('0'))
filler = _T('0');
if (*c == _T('-'))
minus_flag = true;
if (*c == _T('+'))
plus_flag = true;
if (*c == _T('#'))
pound_flag = true;
if (*c == _T(' '))
space_flag = true;
c++;
ASSERT(*c);
}
if (minus_flag)
filler = _T(' ');
if (plus_flag)
space_flag = false;
/* get field width */
if (*c == _T('*')) {
fill = va_arg(ap, int);
if (fill < 0) {
minus_flag = true;
fill = -fill;
}
c++;
ASSERT(*c);
} else {
while (*c >= _T('0') && *c <= _T('9')) {
fill *= 10;
fill += *c - _T('0');
c++;
ASSERT(*c);
}
}
/* get precision */
if (*c == _T('.')) {
c++;
ASSERT(*c);
decimal = 0;
if (*c == _T('*')) {
decimal = va_arg(ap, int);
c++;
ASSERT(*c);
} else {
while (*c >= _T('0') && *c <= _T('9')) {
decimal *= 10;
decimal += *c - _T('0');
c++;
ASSERT(*c);
}
}
}
/* get size modifiers l, h, ll/L, z */
if (*c == _T('l') || *c == _T('L') || *c == _T('h') || *c == _T('z')) {
if (*c == _T('L'))
ll_type = true;
if (*c == _T('h'))
h_type = true;
if (*c == _T('z')) {
#if defined(WINDOWS) && defined(X64)
ll_type = true;
#else
l_type = true;
#endif
}
if (*c == _T('l')) {
c++;
ASSERT(*c);
if (*c == _T('l')) {
ll_type = true;
c++;
ASSERT(*c);
} else {
l_type = true;
}
} else {
c++;
ASSERT(*c);
}
} else if (*c == _T('I')) { /* %I64 or %I32, to match Win32 */
if (*(c + 1) == _T('6') && *(c + 2) == _T('4')) {
ll_type = true;
c += 3;
ASSERT(*c);
} else if (*(c + 1) == _T('3') && *(c + 2) == _T('2')) {
l_type = true;
c += 3;
ASSERT(*c);
} else
ASSERT(false && "unsupported printf code");
}
/* dispatch */
switch (*c) {
case _T('%'):
charbuf[0] = _T('%');
str = charbuf;
break;
case _T('d'):
case _T('i'): {
long val;
ulong abval = 0;
int64 val64;
uint64 abval64 = 0;
bool negative = false;
if (decimal == -1)
decimal = 1; /* defaults */
else
filler = _T(' ');
if (ll_type) {
val64 = va_arg(ap, int64); /* get arg */
negative = (val64 < 0);
if (negative)
abval64 = -val64;
else
abval64 = val64;
} else {
if (l_type)
val = va_arg(ap, long); /* get arg */
else if (h_type)
val = (long)va_arg(ap, int); /* short is promoted to int */
else
val = (long)va_arg(ap, int);
negative = (val < 0);
if (negative)
abval = -val;
else
abval = val;
}
if (!negative && space_flag)
prefixbuf[0] = _T(' '); /* set prefix */
if (!negative && plus_flag)
prefixbuf[0] = _T('+');
if (negative)
prefixbuf[0] = _T('-');
/* generate string */
if (ll_type)
str = TNAME(uint64_to_str)(abval64, 10, buf, decimal, false);
else
str = TNAME(ulong_to_str)(abval, 10, buf, decimal, false);
break;
}
case _T('u'):
/* handle long long u type */
if (decimal == -1)
decimal = 1;
else
filler = _T(' ');
if (ll_type) {
str = TNAME(uint64_to_str)((uint64)va_arg(ap, uint64), 10, buf,
decimal, false);
break;
}
/* note no break */
case _T('x'):
case _T('X'):
case _T('o'):
case _T('p'): {
ptr_uint_t val;
bool caps = *c == _T('X');
int base = 10;
if (decimal == -1)
decimal = 1; /* defaults */
else
filler = _T(' ');
if (*c == _T('p'))
decimal = 2 * sizeof(void *); /* pointer precision */
/* generate prefix */
if ((pound_flag && *c != _T('u')) || (*c == _T('p'))) {
prefixbuf[0] = _T('0');
if (*c == _T('x') || *c == _T('p'))
prefixbuf[1] = _T('x');
if (*c == _T('X'))
prefixbuf[1] = _T('X');
}
if (*c == _T('o'))
base = 8; /* determine base */
if (*c == _T('x') || *c == _T('X') || *c == _T('p'))
base = 16;
ASSERT(sizeof(void *) == sizeof(val));
if (*c == _T('p')) {
val = (ptr_uint_t)va_arg(ap, void *); /* get val */
#ifdef X64
str = TNAME(uint64_to_str)((uint64)val, base, buf, decimal, caps);
break;
#endif
} else if (l_type)
val = (ptr_uint_t)va_arg(ap, ulong);
else if (h_type)
val = (ptr_uint_t)va_arg(ap, uint); /* ushort promoted */
else if (ll_type) {
str = TNAME(uint64_to_str)((uint64)va_arg(ap, uint64), base, buf,
decimal, caps);
break;
} else
val = (ptr_uint_t)va_arg(ap, uint);
/* generate string */
ASSERT(sizeof(val) >= sizeof(ulong));
str = TNAME(ulong_to_str)((ulong)val, base, buf, decimal, caps);
break;
}
case _T('c'):
/* FIXME: using int instead of char seems to work for RH7.2 as
* well as 8.0, but using char crashes 8.0 but not 7.2
*/
#ifdef VA_ARG_CHAR2INT
charbuf[0] = (TCHAR)va_arg(ap, int); /* char -> int in va_list */
#else
charbuf[0] = va_arg(ap, TCHAR);
#endif
str = charbuf;
break;
case _T('s'):
if (!IF_WIDE_ELSE(h_type, l_type)) {
str = va_arg(ap, TCHAR *);
break;
}
/* fall-through */
case _T('S'):
#ifdef IOX_WIDE_CHAR
h_type = true;
wstr = va_arg(ap, char *);
#else
l_type = true;
wstr = va_arg(ap, wchar_t *);
#endif
break;
case _T('g'):
case _T('G'):
if (decimal == 0 || decimal == -1)
decimal = 1; /* default */
/* no break */
case _T('e'):
case _T('E'):
case _T('f'): {
/* pretty sure will always be promoted to a double in arg list */
double val = va_arg(ap, double);
str = TNAME(d_r_vsnprintf_float)(val, c, prefixbuf, buf, decimal,
space_flag, plus_flag, pound_flag);
break;
}
case _T('n'): {
/* save num of chars printed so far in address specified */
uint num_char = (uint)(s - start);
/* yes, snprintf on all platforms returns int, not ssize_t */
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_int(s - start)));
if (l_type) {
long *val = va_arg(ap, long *);
*val = (long)num_char;
} else if (h_type) {
short *val = va_arg(ap, short *);
*val = (short)num_char;
} else {
int *val = va_arg(ap, int *);
*val = num_char;
}
buf[0] = '\0';
str = buf;
break;
}
/* FIXME : support the following? */
case _T('a'):
case _T('A'):
default: ASSERT_NOT_REACHED();
}
/* if filler is 0 fill after prefix, else fill before prefix */
/* if - flag then fill after str and ignore filler type */
/* calculate number of fill characters */
if (fill > 0) {
size_t plen = IF_WIDE_ELSE(wcslen, strlen)(prefix);
if (wstr != NULL) {
/* XXX: this doesn't take into account UTF-16 or UTF-8
* multi-byte chars. For now we just don't support
* properly filling those. It should only matter
* for pretty-printing.
*/
size_t wlen = IF_WIDE_ELSE(strlen, wcslen)(wstr);
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(wlen + plen)));
fill -= (uint)(wlen + plen);
} else {
size_t len = IF_WIDE_ELSE(wcslen, strlen)(str);
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(len + plen)));
fill -= (uint)(len + plen);
}
}
/* insert prefix if filler is 0, filler won't be 0 if - flag is set */
if (filler == _T('0')) {
while (*prefix) {
if ((size_t)(s - start) >= max)
goto max_reached;
*s = *prefix;
s++;
prefix++;
}
}
/* fill now if not left justified */
if (fill > 0 && !minus_flag) {
int i;
for (i = 0; i < fill; i++) {
if ((size_t)(s - start) >= max)
goto max_reached;
*s = filler;
s++;
}
}
/* insert prefix if not 0 filling */
if (filler != _T('0')) {
while (*prefix) {
if ((size_t)(s - start) >= max)
goto max_reached;
*s = *prefix;
s++;
prefix++;
}
}
/* insert the actual str representation */
if (wstr != NULL) {
#ifdef WINDOWS
/* We follow Linux sprintf which has precision on multi-byte
* transformation as specifying bytes, not unicode chars.
* MSDN docs say "characters", but Windows %S doesn't support
* any conversion other than truncating to ascii (or 0 if not
* ascii).
*/
ssize_t els;
size_t max_bytes = max - (s - start);
/* string precision */
if ((*c == _T('s') || *c == _T('S')) && decimal >= 0 &&
(size_t)decimal < max_bytes)
max_bytes = decimal;
els = IF_WIDE_ELSE(utf8_to_utf16, utf16_to_utf8)(s, max_bytes, wstr, 0,
NULL);
if (els < 0)
return -1;
s += els;
if ((size_t)(s - start) >= max)
goto max_reached;
#else
while (*wstr) {
if ((size_t)(s - start) >= max)
goto max_reached;
if ((*c == _T('s') || *c == _T('S')) && decimal == 0)
break; /* check string precision */
decimal--;
/* we only support ascii */
ASSERT((unsigned short)(*wstr) <= UCHAR_MAX);
*s = (TCHAR)*wstr;
s++;
wstr++;
}
#endif
} else {
if (str == NULL)
str = _T("<NULL>");
while (*str) {
if ((size_t)(s - start) >= max)
goto max_reached;
if (*c == _T('s') && decimal == 0)
break; /* check string precision */
decimal--;
*s = *str;
s++;
str++;
}
}
/* if left justified do the fill now after the actual string */
if (fill > 0 && minus_flag) {
int i;
for (i = 0; i < fill; i++) {
if ((size_t)(s - start) >= max)
goto max_reached;
*s = filler;
s++;
}
}
c++;
} else {
const TCHAR *cstart = c;
int nbytes = 0;
while (*c && *c != _T('%')) {
nbytes++;
c++;
}
while (cstart < c) {
if ((size_t)(s - start) >= max)
goto max_reached;
*s = *cstart;
s++;
cstart++;
}
}
}
if (max == 0 || (size_t)(s - start) < max)
*s = '\0';
/* yes, snprintf on all platforms returns int, not ssize_t */
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_int(s - start)));
return (int)(s - start);
max_reached:
return -1;
}
/* Returns number of chars printed. If number is larger than max,
* prints max (without null) and returns -1.
* (Thus, matches Windows snprintf, not Linux.)
*/
/* clang-format off */ /* (work around clang-format newline-after-type bug) */
int
TNAME(d_r_snprintf)(TCHAR *s, size_t max, const TCHAR *fmt, ...)
/* clang-format on */
{
int res;
va_list ap;
ASSERT(s);
va_start(ap, fmt);
res = TNAME(d_r_vsnprintf)(s, max, fmt, ap);
va_end(ap);
return res;
}
#undef TCHAR
#undef _T
#undef TNAME
#undef IF_WIDE_ELSE