ssize_t SafeSNPrintf()

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();
}