From ae7c50185dacb005ae1660ce5b2980eb12414993 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 10 Dec 2019 20:44:08 -0800 Subject: [PATCH] Reintroduce sprintf_format for ABI compatibility --- src/format.cc | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/format.cc b/src/format.cc index c373db1c9df1..44ba77f02869 100644 --- a/src/format.cc +++ b/src/format.cc @@ -8,6 +8,119 @@ #include "fmt/format-inl.h" FMT_BEGIN_NAMESPACE +namespace internal { + +template +int format_float(char* buf, std::size_t size, const char* format, int precision, + T value) { +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (precision > 100000) + throw std::runtime_error( + "fuzz mode - avoid large allocation inside snprintf"); +#endif + // Suppress the warning about nonliteral format string. + auto snprintf_ptr = FMT_SNPRINTF; + return precision < 0 ? snprintf_ptr(buf, size, format, value) + : snprintf_ptr(buf, size, format, precision, value); +} +struct sprintf_specs { + int precision; + char type; + bool alt : 1; + + template + constexpr sprintf_specs(basic_format_specs specs) + : precision(specs.precision), type(specs.type), alt(specs.alt) {} + + constexpr bool has_precision() const { return precision >= 0; } +}; + +// This is deprecated and is kept only to preserve ABI compatibility. +template +char* sprintf_format(Double value, internal::buffer& buf, + sprintf_specs specs) { + // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. + FMT_ASSERT(buf.capacity() != 0, "empty buffer"); + + // Build format string. + enum { max_format_size = 10 }; // longest format: %#-*.*Lg + char format[max_format_size]; + char* format_ptr = format; + *format_ptr++ = '%'; + if (specs.alt || !specs.type) *format_ptr++ = '#'; + if (specs.precision >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + if (std::is_same::value) *format_ptr++ = 'L'; + + char type = specs.type; + + if (type == '%') + type = 'f'; + else if (type == 0 || type == 'n') + type = 'g'; +#if FMT_MSC_VER + if (type == 'F') { + // MSVC's printf doesn't support 'F'. + type = 'f'; + } +#endif + *format_ptr++ = type; + *format_ptr = '\0'; + + // Format using snprintf. + char* start = nullptr; + char* decimal_point_pos = nullptr; + for (;;) { + std::size_t buffer_size = buf.capacity(); + start = &buf[0]; + int result = + format_float(start, buffer_size, format, specs.precision, value); + if (result >= 0) { + unsigned n = internal::to_unsigned(result); + if (n < buf.capacity()) { + // Find the decimal point. + auto p = buf.data(), end = p + n; + if (*p == '+' || *p == '-') ++p; + if (specs.type != 'a' && specs.type != 'A') { + while (p < end && *p >= '0' && *p <= '9') ++p; + if (p < end && *p != 'e' && *p != 'E') { + decimal_point_pos = p; + if (!specs.type) { + // Keep only one trailing zero after the decimal point. + ++p; + if (*p == '0') ++p; + while (p != end && *p >= '1' && *p <= '9') ++p; + char* where = p; + while (p != end && *p == '0') ++p; + if (p == end || *p < '0' || *p > '9') { + if (p != end) std::memmove(where, p, to_unsigned(end - p)); + n -= static_cast(p - where); + } + } + } + } + buf.resize(n); + break; // The buffer is large enough - continue with formatting. + } + buf.reserve(n + 1); + } else { + // If result is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buf.reserve(buf.capacity() + 1); + } + } + return decimal_point_pos; +} +} // namespace internal + +template FMT_API char* internal::sprintf_format(double, internal::buffer&, + sprintf_specs); +template FMT_API char* internal::sprintf_format(long double, + internal::buffer&, + sprintf_specs); + template struct FMT_API internal::basic_data; // Workaround a bug in MSVC2013 that prevents instantiation of format_float.