in src/butil/strings/safe_sprintf.cc [427:649]
ssize_t SafeSNPrintf(char* buf, size_t sz, const char* fmt, const Arg* args,
const size_t max_args) {
// Make sure that at least one NUL byte can be written, and that the buffer
// never overflows kSSizeMax. Not only does that use up most or all of the
// address space, it also would result in a return code that cannot be
// represented.
if (static_cast<ssize_t>(sz) < 1) {
return -1;
} else if (sz > kSSizeMax) {
sz = kSSizeMax;
}
// Iterate over format string and interpret '%' arguments as they are
// encountered.
Buffer buffer(buf, sz);
size_t padding;
char pad;
for (unsigned int cur_arg = 0; *fmt && !buffer.OutOfAddressableSpace(); ) {
if (*fmt++ == '%') {
padding = 0;
pad = ' ';
char ch = *fmt++;
format_character_found:
switch (ch) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
// Found a width parameter. Convert to an integer value and store in
// "padding". If the leading digit is a zero, change the padding
// character from a space ' ' to a zero '0'.
pad = ch == '0' ? '0' : ' ';
for (;;) {
// The maximum allowed padding fills all the available address
// space and leaves just enough space to insert the trailing NUL.
const size_t max_padding = kSSizeMax - 1;
if (padding > max_padding/10 ||
10*padding > max_padding - (ch - '0')) {
DEBUG_CHECK(padding <= max_padding/10 &&
10*padding <= max_padding - (ch - '0'), "");
// Integer overflow detected. Skip the rest of the width until
// we find the format character, then do the normal error handling.
padding_overflow:
padding = max_padding;
while ((ch = *fmt++) >= '0' && ch <= '9') {
}
if (cur_arg < max_args) {
++cur_arg;
}
goto fail_to_expand;
}
padding = 10*padding + ch - '0';
if (padding > max_padding) {
// This doesn't happen for "sane" values of kSSizeMax. But once
// kSSizeMax gets smaller than about 10, our earlier range checks
// are incomplete. Unittests do trigger this artificial corner
// case.
DEBUG_CHECK(padding <= max_padding, "");
goto padding_overflow;
}
ch = *fmt++;
if (ch < '0' || ch > '9') {
// Reached the end of the width parameter. This is where the format
// character is found.
goto format_character_found;
}
}
break;
case 'c': { // Output an ASCII character.
// Check that there are arguments left to be inserted.
if (cur_arg >= max_args) {
DEBUG_CHECK(cur_arg < max_args, "");
goto fail_to_expand;
}
// Check that the argument has the expected type.
const Arg& arg = args[cur_arg++];
if (arg.type != Arg::INT && arg.type != Arg::UINT) {
DEBUG_CHECK(arg.type == Arg::INT || arg.type == Arg::UINT, "");
goto fail_to_expand;
}
// Apply padding, if needed.
buffer.Pad(' ', padding, 1);
// Convert the argument to an ASCII character and output it.
char ch = static_cast<char>(arg.i);
if (!ch) {
goto end_of_output_buffer;
}
buffer.Out(ch);
break; }
case 'd': // Output a possibly signed decimal value.
case 'o': // Output an unsigned octal value.
case 'x': // Output an unsigned hexadecimal value.
case 'X':
case 'p': { // Output a pointer value.
// Check that there are arguments left to be inserted.
if (cur_arg >= max_args) {
DEBUG_CHECK(cur_arg < max_args, "");
goto fail_to_expand;
}
const Arg& arg = args[cur_arg++];
int64_t i;
const char* prefix = NULL;
if (ch != 'p') {
// Check that the argument has the expected type.
if (arg.type != Arg::INT && arg.type != Arg::UINT) {
DEBUG_CHECK(arg.type == Arg::INT || arg.type == Arg::UINT, "");
goto fail_to_expand;
}
i = arg.i;
if (ch != 'd') {
// The Arg() constructor automatically performed sign expansion on
// signed parameters. This is great when outputting a %d decimal
// number, but can result in unexpected leading 0xFF bytes when
// outputting a %x hexadecimal number. Mask bits, if necessary.
// We have to do this here, instead of in the Arg() constructor, as
// the Arg() constructor cannot tell whether we will output a %d
// or a %x. Only the latter should experience masking.
if (arg.width < sizeof(int64_t)) {
i &= (1LL << (8*arg.width)) - 1;
}
}
} else {
// Pointer values require an actual pointer or a string.
if (arg.type == Arg::POINTER) {
i = reinterpret_cast<uintptr_t>(arg.ptr);
} else if (arg.type == Arg::STRING) {
i = reinterpret_cast<uintptr_t>(arg.str);
} else if (arg.type == Arg::INT && arg.width == sizeof(NULL) &&
arg.i == 0) { // Allow C++'s version of NULL
i = 0;
} else {
DEBUG_CHECK(arg.type == Arg::POINTER || arg.type == Arg::STRING, "");
goto fail_to_expand;
}
// Pointers always include the "0x" prefix.
prefix = "0x";
}
// Use IToASCII() to convert to ASCII representation. For decimal
// numbers, optionally print a sign. For hexadecimal numbers,
// distinguish between upper and lower case. %p addresses are always
// printed as upcase. Supports base 8, 10, and 16. Prints padding
// and/or prefixes, if so requested.
buffer.IToASCII(ch == 'd' && arg.type == Arg::INT,
ch != 'x', i,
ch == 'o' ? 8 : ch == 'd' ? 10 : 16,
pad, padding, prefix);
break; }
case 's': {
// Check that there are arguments left to be inserted.
if (cur_arg >= max_args) {
DEBUG_CHECK(cur_arg < max_args, "");
goto fail_to_expand;
}
// Check that the argument has the expected type.
const Arg& arg = args[cur_arg++];
const char *s;
if (arg.type == Arg::STRING) {
s = arg.str ? arg.str : "<NULL>";
} else if (arg.type == Arg::INT && arg.width == sizeof(NULL) &&
arg.i == 0) { // Allow C++'s version of NULL
s = "<NULL>";
} else {
DEBUG_CHECK(arg.type == Arg::STRING, "");
goto fail_to_expand;
}
// Apply padding, if needed. This requires us to first check the
// length of the string that we are outputting.
if (padding) {
size_t len = 0;
for (const char* src = s; *src++; ) {
++len;
}
buffer.Pad(' ', padding, len);
}
// Printing a string involves nothing more than copying it into the
// output buffer and making sure we don't output more bytes than
// available space; Out() takes care of doing that.
for (const char* src = s; *src; ) {
buffer.Out(*src++);
}
break; }
case '%':
// Quoted percent '%' character.
goto copy_verbatim;
fail_to_expand:
// C++ gives us tools to do type checking -- something that snprintf()
// could never really do. So, whenever we see arguments that don't
// match up with the format string, we refuse to output them. But
// since we have to be extremely conservative about being async-
// signal-safe, we are limited in the type of error handling that we
// can do in production builds (in debug builds we can use
// DEBUG_CHECK() and hope for the best). So, all we do is pass the
// format string unchanged. That should eventually get the user's
// attention; and in the meantime, it hopefully doesn't lose too much
// data.
default:
// Unknown or unsupported format character. Just copy verbatim to
// output.
buffer.Out('%');
DEBUG_CHECK(ch, "");
if (!ch) {
goto end_of_format_string;
}
buffer.Out(ch);
break;
}
} else {
copy_verbatim:
buffer.Out(fmt[-1]);
}
}
end_of_format_string:
end_of_output_buffer:
return buffer.GetCount();
}