// // corecrt_internal_stdio_output.h // // Copyright (c) Microsoft Corporation. All rights reserved. // // This file defines the core implementation of the formatted output functions, // including printf and its many variants (sprintf, fprintf, etc.). // #include #include #include #include #include #include #include #include #include namespace __crt_stdio_output { //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Argument Handling // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // These provide a very basic encapsulation over a va_list. The 'read' function // reads the next argument of type T from the varargs list and updates the // va_list, just like va_arg does. The 'peek' function returns the next argument // of type T, but does not modify the va_list. template T read_va_arg(va_list& arglist) throw() { return va_arg(arglist, T); } template T peek_va_arg(va_list arglist) throw() { return va_arg(arglist, T); } //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Output Adapters // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // The actual write operations are different depending on whether the target is // a stream or the console. We handle these differences via output adapters. // The stream and console I/O functions pass an adapter to the core output // function, and that function calls the various write members of the adapter to // perform the write operations. template class output_adapter_common { public: void write_character(Character const c, int* const count_written, __crt_cached_ptd_host& ptd) const throw() { if (static_cast(this)->write_character_without_count_update(c, ptd)) { ++*count_written; } else { *count_written = -1; } } protected: void write_string_impl( Character const* const string, int const length, int* const count_written, __crt_cached_ptd_host& ptd ) const throw() { auto const reset_errno = ptd.get_errno().create_guard(); Character const* const string_last{string + length}; for (Character const* it{string}; it != string_last; ++it) { if (static_cast(this)->write_character_without_count_update(*it, ptd)) { ++*count_written; } else { // Non-standard extension: EILSEQ errors are recoverable. // Standard behavior when we've encountered an 'illegal sequence' error // (i.e. EILSEQ) is to set 'errno' to EILSEQ and return -1. // Instead, we write '?' and continue writing the string. if (!ptd.get_errno().check(EILSEQ)) { // *printf returns the number of characters written // set count written to -1 to indicate an error occurred *count_written = -1; break; } write_character('?', count_written, ptd); } } } }; template class console_output_adapter : public output_adapter_common> { public: typedef __acrt_stdio_char_traits char_traits; bool validate(__crt_cached_ptd_host&) const throw() { return true; } bool write_character_without_count_update(Character const c, __crt_cached_ptd_host& ptd) const throw() { return char_traits::puttch_nolock_internal(c, ptd) != char_traits::eof; } void write_string( Character const* const string, int const length, int* const count_written, __crt_cached_ptd_host& ptd ) const throw() { write_string_impl(string, length, count_written, ptd); } }; template class stream_output_adapter : public output_adapter_common> { public: typedef __acrt_stdio_char_traits char_traits; stream_output_adapter(FILE* const public_stream) throw() : _stream{public_stream} { } bool validate(__crt_cached_ptd_host& ptd) const throw() { _UCRT_VALIDATE_RETURN(ptd, _stream.valid(), EINVAL, false); return char_traits::validate_stream_is_ansi_if_required(_stream.public_stream()); } bool write_character_without_count_update(Character const c, __crt_cached_ptd_host& ptd) const throw() { if (_stream.is_string_backed() && _stream->_base == nullptr) { return true; } return char_traits::puttc_nolock_internal(c, _stream.public_stream(), ptd) != char_traits::eof; } void write_string( Character const* const string, int const length, int* const count_written, __crt_cached_ptd_host& ptd ) const throw() { if (_stream.is_string_backed() && _stream->_base == nullptr) { *count_written += length; return; } write_string_impl(string, length, count_written, ptd); } private: __crt_stdio_stream _stream; }; template struct string_output_adapter_context { Character* _buffer; size_t _buffer_count; size_t _buffer_used; bool _continue_count; }; template class string_output_adapter { public: typedef __acrt_stdio_char_traits char_traits; typedef string_output_adapter_context context_type; string_output_adapter(context_type* const context) throw() : _context(context) { } bool validate(__crt_cached_ptd_host& ptd) const throw() { _UCRT_VALIDATE_RETURN(ptd, _context != nullptr, EINVAL, false); return true; } __forceinline bool write_character(Character const c, int* const count_written, __crt_cached_ptd_host&) const throw() { if (_context->_buffer_used == _context->_buffer_count) { if (_context->_continue_count) { ++*count_written; } else { *count_written = -1; } return _context->_continue_count; } ++*count_written; ++_context->_buffer_used; *_context->_buffer++ = c; return true; } void write_string( Character const* const string, int const length, int* const count_written, __crt_cached_ptd_host& ptd ) const throw() { // This function does not perform any operations that might reset errno, // so we don't need to use __crt_errno_guard as we do in other output // adapters. UNREFERENCED_PARAMETER(ptd); if (length == 0) { return; } if (_context->_buffer_used == _context->_buffer_count) { if (_context->_continue_count) { *count_written += length; } else { *count_written = -1; } return; } size_t const space_available = _context->_buffer_count - _context->_buffer_used; size_t const elements_to_copy = __min(space_available, static_cast(length)); // Performance note: This is hot code. Profiling has shown the extra // validation done by memcpy_s to be quite expensive. The validation is // unnecessary, so we call memcpy directly here: memcpy( _context->_buffer, string, elements_to_copy * sizeof(Character)); _context->_buffer += elements_to_copy; _context->_buffer_used += elements_to_copy; if (_context->_continue_count) { *count_written += length; } else if (elements_to_copy != static_cast(length)) { *count_written = -1; } else { *count_written += static_cast(elements_to_copy); } } private: context_type* _context; }; template __forceinline void write_multiple_characters( OutputAdapter const& adapter, Character const c, int const count, int* const count_written, __crt_cached_ptd_host& ptd ) throw() { for (int i{0}; i < count; ++i) { adapter.write_character(c, count_written, ptd); if (*count_written == -1) break; } } //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Formatting Buffer // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // This type encapsulates the buffer to be used when formatting numbers. It // contains as a data member a large, statically-sized buffer, which is used // at first. If a larger buffer is required a new, larger buffer is dynamically // allocated. class formatting_buffer { public: enum { member_buffer_size = 1024, }; static_assert(member_buffer_size >= (_CVTBUFSIZE + 6) * 2, "Buffer is too small"); formatting_buffer() throw() : _dynamic_buffer_size{0} { } template bool ensure_buffer_is_big_enough(size_t const count, __crt_cached_ptd_host& ptd) throw() { constexpr size_t max_count = SIZE_MAX / sizeof(T) / 2; // avoid runtime division _UCRT_VALIDATE_RETURN_NOEXC(ptd, max_count >= count, ENOMEM, false); size_t const required_size{count * sizeof(T) * 2}; // Once we allocate a dynamic buffer, we no longer use the member buffer if (!_dynamic_buffer && required_size <= member_buffer_size) { return true; } if (required_size <= _dynamic_buffer_size) { return true; } __crt_unique_heap_ptr new_buffer{_malloc_crt_t(char, required_size)}; if (!new_buffer) { return false; } _dynamic_buffer = static_cast<__crt_unique_heap_ptr&&>(new_buffer); _dynamic_buffer_size = required_size; return true; } template T* data() throw() { if (!_dynamic_buffer) return reinterpret_cast(_member_buffer); return reinterpret_cast(_dynamic_buffer.get()); } template T* scratch_data() throw() { if (!_dynamic_buffer) return reinterpret_cast(_member_buffer) + count(); return reinterpret_cast(_dynamic_buffer.get()) + count(); } template size_t count() const throw() { if (!_dynamic_buffer) return member_buffer_size / sizeof(T) / 2; return _dynamic_buffer_size / sizeof(T) / 2; } template size_t scratch_count() const throw() { return count(); } private: char _member_buffer[member_buffer_size]; size_t _dynamic_buffer_size; __crt_unique_heap_ptr _dynamic_buffer; }; // This function forces a decimal point in floating point output. It is called // if '#' flag is given and precision is 0, so we know the number has no '.' in // its current representation. We insert the '.' and move everything back one // position until '\0' is seen. This function updates the buffer in place. inline void __cdecl force_decimal_point(_Inout_z_ char* buffer, _locale_t const locale) throw() { if (_tolower_fast_internal(static_cast(*buffer), locale) != 'e') { do { ++buffer; } while (_isdigit_fast_internal(static_cast(*buffer), locale)); } // Check if the buffer is in hexadecimal format (cfr %a or %A and fp_format_a): if (_tolower_fast_internal(*buffer, locale) == 'x') { // The buffer is in the form: [-]0xhP+d, and buffer points to the 'x': // we want to put the decimal point after the h digit: [-]0xh.P+d buffer += 2; // REVIEW This can skip terminal nul? } char holdchar = *buffer; *buffer++ = *locale->locinfo->lconv->decimal_point; do { char const nextchar = *buffer; *buffer = holdchar; holdchar = nextchar; } while (*buffer++); } // This function removes trailing zeroes (after the '.') from a floating point // number. This function is called only when formatting in the %g mode when // there is no '#' flag and precision is nonzero. This function updates the // buffer in-place. // // This function changes the contents of the buffer from: // [-] digit [digit...] [ . [digits...] [0...] ] [(exponent part)] // to: // [-] digit [digit...] [ . digit [digits...] ] [(exponent part)] // or: // [-] digit [digit...] [(exponent part)] inline void __cdecl crop_zeroes(_Inout_z_ char* buffer, _locale_t const locale) throw() { char const decimal_point = *locale->locinfo->lconv->decimal_point; while (*buffer && *buffer != decimal_point) ++buffer; if (*buffer++) { while (*buffer && *buffer != 'e' && *buffer != 'E') ++buffer; char* stop = buffer--; while (*buffer == '0') --buffer; if (*buffer == decimal_point) --buffer; while((*++buffer = *stop++) != '\0') { } } } //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // States and the State Transition Tables // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // These tables hold the state transition data for the format processor. The // lower nibble of each byte is the character class of the character. The upper // nibble of each byte gives the next state to enter. // The states of the format parsing state machine. These are encoded into the // transition tables and are referenced throughout the format processor. enum class state : unsigned char { normal, // Normal state; outputting literal chars percent, // Just read '%' flag, // Just read flag character width, // Just read width specifier dot, // Just read '.' precision, // Just read precision specifier size, // Just read size specifier type, // Just read type specifier invalid // Invalid format }; // Manifest constants to represent the different types of characters that may // be encountered in the format string. These are not referenced anywhere in // the source anymore, but their values are encoded into the state table, so // we have retained the definition for reference. enum class character_type : unsigned char { other, // character with no special meaning percent, // '%' dot, // '.' star, // '*' zero, // '0' digit, // '1'..'9' flag, // ' ', '+', '-', '#' size, // 'h', 'l', 'L', 'N', 'F', 'w' type // type specifying character }; struct state_transition_pair { state next_state; character_type current_class; }; template class spectre_mitigated_lookup_table { public: static size_t const mask = Size - 1; static_assert((Size & mask) == 0, "Size must be a power of two."); // Instead of using an lfence mitigation, we can fill the table to a power of two, // then bitwise-and all values used to index into the array. __declspec(spectre(nomitigation)) T const& operator[](size_t const index) const { return m_array[index & mask]; } T m_array[Size]; }; using printf_state_transition_table = spectre_mitigated_lookup_table; // Note, the state transition and character type are unrelated data - they just occupy the same table. // This table skips the 'invalid' state. extern __declspec(selectany) printf_state_transition_table const standard_lookup_table_spectre { /* prev state -> cur char -> new state character type */ /* normal -> other -> */ state::normal, /* ' ' */ character_type::flag, /* percent -> other -> */ state::normal, /* '!' */ character_type::other, /* flag -> other -> */ state::normal, /* '"' */ character_type::other, /* width -> other -> */ state::normal, /* '#' */ character_type::flag, /* dot -> other -> */ state::normal, /* '$' */ character_type::other, /* precision -> other -> */ state::normal, /* '%' */ character_type::percent, /* size -> other -> */ state::normal, /* '&' */ character_type::other, /* type -> other -> */ state::normal, /* ''' */ character_type::other, /* normal -> percent -> */ state::percent, /* '(' */ character_type::other, /* percent -> percent -> */ state::normal, /* ')' */ character_type::other, /* flag -> percent -> */ state::normal, /* '*' */ character_type::star, /* width -> percent -> */ state::normal, /* '+' */ character_type::flag, /* dot -> percent -> */ state::normal, /* ',' */ character_type::other, /* precision -> percent -> */ state::normal, /* '-' */ character_type::flag, /* size -> percent -> */ state::normal, /* '.' */ character_type::dot, /* type -> percent -> */ state::percent, /* '/' */ character_type::other, /* normal -> dot -> */ state::normal, /* '0' */ character_type::zero, /* percent -> dot -> */ state::dot, /* '1' */ character_type::digit, /* flag -> dot -> */ state::dot, /* '2' */ character_type::digit, /* width -> dot -> */ state::dot, /* '3' */ character_type::digit, /* dot -> dot -> */ state::normal, /* '4' */ character_type::digit, /* precision -> dot -> */ state::normal, /* '5' */ character_type::digit, /* size -> dot -> */ state::normal, /* '6' */ character_type::digit, /* type -> dot -> */ state::normal, /* '7' */ character_type::digit, /* normal -> star -> */ state::normal, /* '8' */ character_type::digit, /* percent -> star -> */ state::width, /* '9' */ character_type::digit, /* flag -> star -> */ state::width, /* ':' */ character_type::other, /* width -> star -> */ state::normal, /* ';' */ character_type::other, /* dot -> star -> */ state::precision, /* '<' */ character_type::other, /* precision -> star -> */ state::normal, /* '=' */ character_type::other, /* size -> star -> */ state::normal, /* '>' */ character_type::other, /* type -> star -> */ state::normal, /* '?' */ character_type::other, /* normal -> zero -> */ state::normal, /* '@' */ character_type::other, /* percent -> zero -> */ state::flag, /* 'A' */ character_type::type, /* flag -> zero -> */ state::flag, /* 'B' */ character_type::other, /* width -> zero -> */ state::width, /* 'C' */ character_type::type, /* dot -> zero -> */ state::precision, /* 'D' */ character_type::other, /* precision -> zero -> */ state::precision, /* 'E' */ character_type::type, /* size -> zero -> */ state::normal, /* 'F' */ character_type::size, /* type -> zero -> */ state::normal, /* 'G' */ character_type::type, /* normal -> digit -> */ state::normal, /* 'H' */ character_type::other, /* percent -> digit -> */ state::width, /* 'I' */ character_type::size, /* flag -> digit -> */ state::width, /* 'J' */ character_type::other, /* width -> digit -> */ state::width, /* 'K' */ character_type::other, /* dot -> digit -> */ state::precision, /* 'L' */ character_type::size, /* precision -> digit -> */ state::precision, /* 'M' */ character_type::other, /* size -> digit -> */ state::normal, /* 'N' */ character_type::size, /* type -> digit -> */ state::normal, /* 'O' */ character_type::other, /* normal -> flag -> */ state::normal, /* 'P' */ character_type::other, /* percent -> flag -> */ state::flag, /* 'Q' */ character_type::other, /* flag -> flag -> */ state::flag, /* 'R' */ character_type::other, /* width -> flag -> */ state::normal, /* 'S' */ character_type::type, /* dot -> flag -> */ state::normal, /* 'T' */ character_type::size, /* precision -> flag -> */ state::normal, /* 'U' */ character_type::other, /* size -> flag -> */ state::normal, /* 'V' */ character_type::other, /* type -> flag -> */ state::normal, /* 'W' */ character_type::other, /* normal -> size -> */ state::normal, /* 'X' */ character_type::type, /* percent -> size -> */ state::size, /* 'Y' */ character_type::other, /* flag -> size -> */ state::size, /* 'Z' */ character_type::type, /* width -> size -> */ state::size, /* '[' */ character_type::other, /* dot -> size -> */ state::size, /* '\' */ character_type::other, /* precision -> size -> */ state::size, /* ']' */ character_type::other, /* size -> size -> */ state::size, /* '^' */ character_type::other, /* type -> size -> */ state::normal, /* '_' */ character_type::other, /* normal -> type -> */ state::normal, /* '`' */ character_type::other, /* percent -> type -> */ state::type, /* 'a' */ character_type::type, /* flag -> type -> */ state::type, /* 'b' */ character_type::other, /* width -> type -> */ state::type, /* 'c' */ character_type::type, /* dot -> type -> */ state::type, /* 'd' */ character_type::type, /* precision -> type -> */ state::type, /* 'e' */ character_type::type, /* size -> type -> */ state::type, /* 'f' */ character_type::type, /* type -> type -> */ state::normal, /* 'g' */ character_type::type, /* unused */ state::normal, /* 'h' */ character_type::size, /* unused */ state::normal, /* 'i' */ character_type::type, /* unused */ state::normal, /* 'j' */ character_type::size, /* unused */ state::normal, /* 'k' */ character_type::other, /* unused */ state::normal, /* 'l' */ character_type::size, /* unused */ state::normal, /* 'm' */ character_type::other, /* unused */ state::normal, /* 'n' */ character_type::type, /* unused */ state::normal, /* 'o' */ character_type::type, /* unused */ state::normal, /* 'p' */ character_type::type, /* unused */ state::normal, /* 'q' */ character_type::other, /* unused */ state::normal, /* 'r' */ character_type::other, /* unused */ state::normal, /* 's' */ character_type::type, /* unused */ state::normal, /* 't' */ character_type::size, /* unused */ state::normal, /* 'u' */ character_type::type, /* unused */ state::normal, /* 'v' */ character_type::other, /* unused */ state::normal, /* 'w' */ character_type::size, /* unused */ state::normal, /* 'x' */ character_type::type, /* unused */ state::normal, /* 'y' */ character_type::other, /* unused */ state::normal, /* 'z' */ character_type::size }; // Note, the state transition and character type are unrelated data - they just occupy the same table. extern __declspec(selectany) printf_state_transition_table const format_validation_lookup_table_spectre { /* prev state -> cur char -> new state character type */ /* normal -> other -> */ state::normal, /* ' ' */ character_type::flag, /* percent -> other -> */ state::invalid, /* '!' */ character_type::other, /* flag -> other -> */ state::invalid, /* '"' */ character_type::other, /* width -> other -> */ state::invalid, /* '#' */ character_type::flag, /* dot -> other -> */ state::invalid, /* '$' */ character_type::other, /* precision -> other -> */ state::invalid, /* '%' */ character_type::percent, /* size -> other -> */ state::invalid, /* '&' */ character_type::other, /* type -> other -> */ state::normal, /* ''' */ character_type::other, /* invalid -> other -> */ state::normal, /* '(' */ character_type::other, /* normal -> percent -> */ state::percent, /* ')' */ character_type::other, /* percent -> percent -> */ state::normal, /* '*' */ character_type::star, /* flag -> percent -> */ state::invalid, /* '+' */ character_type::flag, /* width -> percent -> */ state::invalid, /* ',' */ character_type::other, /* dot -> percent -> */ state::invalid, /* '-' */ character_type::flag, /* precision -> percent -> */ state::invalid, /* '.' */ character_type::dot, /* size -> percent -> */ state::invalid, /* '/' */ character_type::other, /* type -> percent -> */ state::percent, /* '0' */ character_type::zero, /* invalid -> percent -> */ state::normal, /* '1' */ character_type::digit, /* normal -> dot -> */ state::normal, /* '2' */ character_type::digit, /* percent -> dot -> */ state::dot, /* '3' */ character_type::digit, /* flag -> dot -> */ state::dot, /* '4' */ character_type::digit, /* width -> dot -> */ state::dot, /* '5' */ character_type::digit, /* dot -> dot -> */ state::invalid, /* '6' */ character_type::digit, /* precision -> dot -> */ state::invalid, /* '7' */ character_type::digit, /* size -> dot -> */ state::invalid, /* '8' */ character_type::digit, /* type -> dot -> */ state::normal, /* '9' */ character_type::digit, /* invalid -> dot -> */ state::normal, /* ':' */ character_type::other, /* normal -> star -> */ state::normal, /* ';' */ character_type::other, /* percent -> star -> */ state::width, /* '<' */ character_type::other, /* flag -> star -> */ state::width, /* '=' */ character_type::other, /* width -> star -> */ state::invalid, /* '>' */ character_type::other, /* dot -> star -> */ state::precision, /* '?' */ character_type::other, /* precision -> star -> */ state::invalid, /* '@' */ character_type::other, /* size -> star -> */ state::invalid, /* 'A' */ character_type::type, /* type -> star -> */ state::normal, /* 'B' */ character_type::other, /* invalid -> star -> */ state::normal, /* 'C' */ character_type::type, /* normal -> zero -> */ state::normal, /* 'D' */ character_type::other, /* percent -> zero -> */ state::flag, /* 'E' */ character_type::type, /* flag -> zero -> */ state::flag, /* 'F' */ character_type::size, /* width -> zero -> */ state::width, /* 'G' */ character_type::type, /* dot -> zero -> */ state::precision, /* 'H' */ character_type::other, /* precision -> zero -> */ state::precision, /* 'I' */ character_type::size, /* size -> zero -> */ state::invalid, /* 'J' */ character_type::other, /* type -> zero -> */ state::normal, /* 'K' */ character_type::other, /* invalid -> zero -> */ state::normal, /* 'L' */ character_type::size, /* normal -> digit -> */ state::normal, /* 'M' */ character_type::other, /* percent -> digit -> */ state::width, /* 'N' */ character_type::size, /* flag -> digit -> */ state::width, /* 'O' */ character_type::other, /* width -> digit -> */ state::width, /* 'P' */ character_type::other, /* dot -> digit -> */ state::precision, /* 'Q' */ character_type::other, /* precision -> digit -> */ state::precision, /* 'R' */ character_type::other, /* size -> digit -> */ state::invalid, /* 'S' */ character_type::type, /* type -> digit -> */ state::normal, /* 'T' */ character_type::size, /* invalid -> digit -> */ state::normal, /* 'U' */ character_type::other, /* normal -> flag -> */ state::normal, /* 'V' */ character_type::other, /* percent -> flag -> */ state::flag, /* 'W' */ character_type::other, /* flag -> flag -> */ state::flag, /* 'X' */ character_type::type, /* width -> flag -> */ state::invalid, /* 'Y' */ character_type::other, /* dot -> flag -> */ state::invalid, /* 'Z' */ character_type::type, /* precision -> flag -> */ state::invalid, /* '[' */ character_type::other, /* size -> flag -> */ state::invalid, /* '\' */ character_type::other, /* type -> flag -> */ state::normal, /* ']' */ character_type::other, /* invalid -> flag -> */ state::normal, /* '^' */ character_type::other, /* normal -> size -> */ state::normal, /* '_' */ character_type::other, /* percent -> size -> */ state::size, /* '`' */ character_type::other, /* flag -> size -> */ state::size, /* 'a' */ character_type::type, /* width -> size -> */ state::size, /* 'b' */ character_type::other, /* dot -> size -> */ state::size, /* 'c' */ character_type::type, /* precision -> size -> */ state::size, /* 'd' */ character_type::type, /* size -> size -> */ state::size, /* 'e' */ character_type::type, /* type -> size -> */ state::normal, /* 'f' */ character_type::type, /* invalid -> size -> */ state::normal, /* 'g' */ character_type::type, /* normal -> type -> */ state::normal, /* 'h' */ character_type::size, /* percent -> type -> */ state::type, /* 'i' */ character_type::type, /* flag -> type -> */ state::type, /* 'j' */ character_type::size, /* width -> type -> */ state::type, /* 'k' */ character_type::other, /* dot -> type -> */ state::type, /* 'l' */ character_type::size, /* precision -> type -> */ state::type, /* 'm' */ character_type::other, /* size -> type -> */ state::type, /* 'n' */ character_type::other, /* type -> type -> */ state::normal, /* 'o' */ character_type::type, /* invalid -> type -> */ state::normal, /* 'p' */ character_type::type, /* unused */ state::normal, /* 'q' */ character_type::other, /* unused */ state::normal, /* 'r' */ character_type::other, /* unused */ state::normal, /* 's' */ character_type::type, /* unused */ state::normal, /* 't' */ character_type::size, /* unused */ state::normal, /* 'u' */ character_type::type, /* unused */ state::normal, /* 'v' */ character_type::other, /* unused */ state::normal, /* 'w' */ character_type::size, /* unused */ state::normal, /* 'x' */ character_type::type, /* unused */ state::normal, /* 'y' */ character_type::other, /* unused */ state::normal, /* 'z' */ character_type::size }; //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Flags // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ enum FLAG : unsigned { FL_SIGN = 0x01, // Put plus or minus in front FL_SIGNSP = 0x02, // Put space or minus in front FL_LEFT = 0x04, // Left justify FL_LEADZERO = 0x08, // Pad with leading zeros FL_SIGNED = 0x10, // Signed data given FL_ALTERNATE = 0x20, // Alternate form requested FL_NEGATIVE = 0x40, // Value is negative FL_FORCEOCTAL = 0x80, // Force leading '0' for octals }; enum class length_modifier { none, hh, h, l, ll, j, z, t, L, I, I32, I64, w, T, enumerator_count }; inline size_t __cdecl to_integer_size(length_modifier const length) throw() { switch (length) { case length_modifier::none: return sizeof(int ); case length_modifier::hh: return sizeof(char ); case length_modifier::h: return sizeof(short ); case length_modifier::l: return sizeof(long ); case length_modifier::ll: return sizeof(long long); case length_modifier::j: return sizeof(intmax_t ); case length_modifier::z: return sizeof(size_t ); case length_modifier::t: return sizeof(ptrdiff_t); case length_modifier::I: return sizeof(void* ); case length_modifier::I32: return sizeof(int32_t ); case length_modifier::I64: return sizeof(int64_t ); default: return 0; } } template bool __cdecl is_wide_character_specifier( uint64_t const options, Character const format_type, length_modifier const length ) throw() { UNREFERENCED_PARAMETER(options); // If a length specifier was used, use that width: switch (length) { case length_modifier::l: return true; case length_modifier::w: return true; case length_modifier::h: return false; } if (length == length_modifier::T) { return sizeof(Character) == sizeof(wchar_t); } bool const is_naturally_wide{ sizeof(Character) == sizeof(wchar_t) && (options & _CRT_INTERNAL_PRINTF_LEGACY_WIDE_SPECIFIERS) != 0 }; bool const is_natural_width{ format_type == 'c' || format_type == 's' }; return is_naturally_wide == is_natural_width; } //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Core Base Classes // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // The format processor consists of a set of base classes that form a single // inheritance hierarchy that looks like so: // // * common_data // * output_adapter_data // * standard_base [1] // * format_validation_base [2] // * positional_parameter_base [3] // // There is a single class template, output_processor, which may be instantiated // to derive from any of the three bottom-most classes in the aforementioned // base class hierarchy (these are marked with the numbers 1-3). When it derives // from standard_base, it provides the standard formatted output functionality // as defined in the C Standard Library specification. When it derives from the // format_validation_base, it provides that same functionality, but also checks // the format string for validity, and invokes the invalid parameter handler if // the format string is determined to be invalid. Finally, if it derives from // the positional_parameter_base, it supports positional parameters. // // These three derivable bases allow us to implement the three forms of public // functions using a common implementation: the standard, unsuffixed functions, // the format validating _s-suffixed functions, and the _p-suffixed functions // that permit positional parameter usage. // // There are no virtual functions used here: each of the three functionality // bases "overrides" base class functionality by hiding it. We must therefore // be cautious to ensure that functions are called in such a way that the right // implementation is called. template class common_data { protected: common_data(__crt_cached_ptd_host& ptd) : _options {0 }, _ptd {ptd }, _format_it {nullptr }, _valist_it {nullptr }, _characters_written{0 }, _state {state::normal}, _flags {0 }, _field_width {0 }, _precision {0 }, _suppress_output {false }, _format_char {'\0' }, _string_length {0 }, _string_is_wide {false } { } uint64_t _options; // We cache a reference to the PTD and updated locale information // to avoid having to query thread-local storage for every character // write that we perform. __crt_cached_ptd_host& _ptd; // These two iterators control the formatting operation. The format iterator // iterates over the format string, and the va_list argument pointer iterates // over the varargs arguments. Character const* _format_it; va_list _valist_it; // This stores the number of characters that have been written so far. It is // initialized to zero and is incremented as characters are written. If an // I/O error occurs, it is set to -1 to indicate the I/O failure. int _characters_written; // These represent the state for the current format specifier. The suppress // output flag is set when output should be suppressed for the current format // specifier (note that this is distinct from the global suppression that we // use during the first pass of positional parameter handling). state _state; unsigned _flags; int _field_width; int _precision; length_modifier _length; bool _suppress_output; // This is the character from the format string that was used to compute the // current state. We need to store this separately because we advance the // format string iterator at various points during processing. Character _format_char; // These pointers are used in various places to point to strings that either // [1] are being formatted into, or [2] contain formatted data that is ready // to be printed. At any given time, we may have either a narrow string or // a wide string, but never both. The string length is the length of which- // -ever string is currently present. The wide flag is set if the wide string // is currently in use. union { char* _narrow_string; wchar_t* _wide_string; }; char*& tchar_string(char ) throw() { return _narrow_string; } wchar_t*& tchar_string(wchar_t) throw() { return _wide_string; } Character*& tchar_string() throw() { return tchar_string(Character()); } int _string_length; bool _string_is_wide; // The formatting buffer. This buffer is used to store the result of various // formatting operations--notably, numbers get formatted into strings in this // buffer. formatting_buffer _buffer; }; // This data base is split out from the common data base only so that we can // more easily value-initialize all of the members of the common data base. template class output_adapter_data : protected common_data { protected: output_adapter_data( OutputAdapter const& output_adapter, uint64_t const options, Character const* const format, __crt_cached_ptd_host& ptd, va_list const arglist ) throw() : common_data{ptd}, _output_adapter(output_adapter) { // We initialize several base class data members here, so that we can // value initialize the entire base class before we get to this point. _options = options; _format_it = format; _valist_it = arglist; } OutputAdapter _output_adapter; }; //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // standard_base // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // This is the base class with which the format processor is instantiated to // provide "standard" printf formatting. template class standard_base : protected output_adapter_data { protected: template standard_base(Ts&&... arguments) throw() : output_adapter_data{arguments... }, _current_pass {pass::not_started} { } bool advance_to_next_pass() throw() { _current_pass = static_cast(static_cast(_current_pass) + 1); return _current_pass != pass::finished; } __forceinline bool validate_and_update_state_at_end_of_format_string() const throw() { // No validation is performed in the standard output implementation: return true; } bool should_format() throw() { return true; } template bool extract_argument_from_va_list(ActualParameterType& result) throw() { result = static_cast(read_va_arg(_valist_it)); return true; } bool update_field_width() throw() { _field_width = read_va_arg(_valist_it); return true; } bool update_precision() throw() { _precision = read_va_arg(_valist_it); return true; } bool validate_state_for_type_case_a() const throw() { return true; } bool should_skip_normal_state_processing() throw() { return false; } bool validate_and_update_state_at_beginning_of_format_character() throw() { return true; } bool should_skip_type_state_output() const throw() { return false; } static unsigned state_count() throw() { return static_cast(state::type) + 1; } static printf_state_transition_table const& state_transition_table() throw() { return standard_lookup_table_spectre; } private: // In the standard format string processing, there is only one state, in // which we both evaluate the format specifiers and format the parameters. enum class pass : unsigned { not_started, output, finished }; pass _current_pass; }; //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // format_validation_base // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // This is the base class with which the format processor is instantiated to // provide "standard" printf formatting with additional validation of the format // string. template class format_validation_base : protected standard_base { protected: template format_validation_base(Ts&&... arguments) throw() : standard_base{arguments...} { } __forceinline bool validate_and_update_state_at_end_of_format_string() throw() { // When we reach the end of the format string, we ensure that the format // string is not incomplete. I.e., when we are finished, the lsat thing // that we should have encountered is a regular character to be written // or a type specifier. Otherwise, the format string was incomplete. _UCRT_VALIDATE_RETURN(_ptd, _state == state::normal || _state == state::type, EINVAL, false); return true; } static unsigned state_count() throw() { return static_cast(state::invalid) + 1; } static printf_state_transition_table const& state_transition_table() throw() { return format_validation_lookup_table_spectre; } }; //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // positional_parameter_base // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // This is the base class with which the format processor is instantiated to // provide support for the formatted output functions that permit positional // parameter references in the format string. Note that when this base is used, // it also pulls in the format validation functionality. template class positional_parameter_base : protected format_validation_base { protected: typedef positional_parameter_base self_type; typedef format_validation_base base_type; typedef __crt_char_traits char_traits; template positional_parameter_base(Ts&&... arguments) throw() : format_validation_base{arguments... }, _current_pass {pass::not_started}, _format_mode {mode::unknown }, _format {_format_it }, _type_index {-1 }, _maximum_index {-1 } { // Note that we do not zero-initialize the parameter data table until // the first positional parameter is encountered in the format string. } bool advance_to_next_pass() throw() { _current_pass = static_cast(static_cast(_current_pass) + 1); if (_current_pass == pass::finished) return false; // If we are in the output pass but the format string is a non-positional, // ordinary format string, then we do not need a second pass: if (_current_pass == pass::output && _format_mode == mode::nonpositional) return false; // All characters before the first format specifier are output in the // first pass. We reset the format mode to 'unknown' to ensure that // they are not output again during the second pass. _format_mode = mode::unknown; _maximum_index = -1; _type_index = -1; _field_width = 0; _precision = 0; _format_it = _format; return true; } bool validate_and_update_state_at_end_of_format_string() throw() { if (!base_type::validate_and_update_state_at_end_of_format_string()) return false; if (_format_mode != mode::positional || _current_pass != pass::position_scan) return true; // At the end of the first pass, we have the types filled into the // arg_type member for each positional parameter. We now need to get // the argument pointer for each positional parameter and store it // into the arg_ptr member. parameter_data* const first{_parameters}; parameter_data* const last {_parameters + _maximum_index + 1}; for (parameter_data* it{first}; it != last; ++it) { it->_valist_it = _valist_it; switch (it->_actual_type) { case parameter_type::int32: read_va_arg(_valist_it); break; case parameter_type::int64: read_va_arg<__int64 >(_valist_it); break; case parameter_type::pointer: read_va_arg(_valist_it); break; case parameter_type::real64: read_va_arg<_CRT_DOUBLE>(_valist_it); break; default: // We should never reach this point: _UCRT_VALIDATE_RETURN(_ptd, ("Missing position in the format string", 0), EINVAL, false); break; } } return true; } bool should_format() throw() { return _current_pass != pass::position_scan || _format_mode == mode::nonpositional; } template bool extract_argument_from_va_list(ActualParameterType& result) throw() { if (_format_mode == mode::nonpositional) { return base_type::extract_argument_from_va_list(result); } _UCRT_VALIDATE_RETURN(_ptd, _type_index >= 0 && _type_index < _ARGMAX, EINVAL, false); if (_current_pass == pass::position_scan) { return validate_and_store_parameter_data( _parameters[_type_index], get_parameter_type(RequestedParameterType()), _format_char, _length ); } else { result = static_cast(peek_va_arg(_parameters[_type_index]._valist_it)); return true; } } bool update_field_width() throw() { if (_format_mode == mode::nonpositional) { return base_type::update_field_width(); } Character* end_pointer{nullptr}; int const width_index{_tcstol_internal(_ptd, _format_it, &end_pointer, 10) - 1}; _format_it = end_pointer + 1; if (_current_pass == pass::position_scan) { _UCRT_VALIDATE_RETURN(_ptd, width_index >= 0 && *end_pointer == '$' && width_index < _ARGMAX, EINVAL, false); _maximum_index = width_index > _maximum_index ? width_index : _maximum_index; return validate_and_store_parameter_data( _parameters[width_index], parameter_type::int32, _format_char, _length ); } else { _field_width = peek_va_arg(_parameters[width_index]._valist_it); } return true; } bool update_precision() throw() { if (_format_mode == mode::nonpositional) { return base_type::update_precision(); } Character* end_pointer{nullptr}; int const precision_index{_tcstol_internal(_ptd, _format_it, &end_pointer, 10) - 1}; _format_it = end_pointer + 1; if (_current_pass == pass::position_scan) { _UCRT_VALIDATE_RETURN(_ptd, precision_index >= 0 && *end_pointer == '$' && precision_index < _ARGMAX, EINVAL, false); _maximum_index = precision_index > _maximum_index ? precision_index : _maximum_index; return validate_and_store_parameter_data( _parameters[precision_index], parameter_type::int32, _format_char, _length ); } else { _precision = peek_va_arg(_parameters[precision_index]._valist_it); } return true; } bool validate_state_for_type_case_a() throw() { if (_format_mode == mode::positional && _current_pass == pass::position_scan) { _UCRT_VALIDATE_RETURN(_ptd, _type_index >= 0 && _type_index < _ARGMAX, EINVAL, false); return validate_and_store_parameter_data( _parameters[_type_index], parameter_type::real64, _format_char, _length ); } return true; } bool should_skip_normal_state_processing() throw() { if (_current_pass == pass::position_scan && _format_mode == mode::positional) return true; if (_current_pass == pass::output && _format_mode == mode::unknown) return true; // We do not output during the first pass if we have already come across // a positional format specifier. All characters before the first format // specifier are output in the first pass. We need to check the format // type during the second pass to ensure that they are not output a second // time. return false; } bool validate_and_update_state_at_beginning_of_format_character() throw() { // We're looking for a format specifier, so we'll have just seen a '%' // and the next character is not a '%': if (_state != state::percent || *_format_it == '%') return true; // When we encounter the first format specifier, we determine whether // the format string is a positional format string or a standard format // string. if (_format_mode == mode::unknown) { Character* end_pointer{nullptr}; // Only digits are permitted between the % and the $ in the positional format specifier. if (*_format_it < '0' || *_format_it > '9') { _format_mode = mode::nonpositional; } else if (_tcstol_internal(_ptd, _format_it, &end_pointer, 10) > 0 && *end_pointer == '$') { if (_current_pass == pass::position_scan) { memset(_parameters, 0, sizeof(_parameters)); } _format_mode = mode::positional; } else { _format_mode = mode::nonpositional; } } if (_format_mode != mode::positional) { return true; } Character* end_pointer{nullptr}; _type_index = _tcstol_internal(_ptd, _format_it, &end_pointer, 10) - 1; _format_it = end_pointer + 1; if (_current_pass != pass::position_scan) return true; // We do not re-perform the type validations during the second pass... _UCRT_VALIDATE_RETURN(_ptd, _type_index >= 0 && *end_pointer == '$' && _type_index < _ARGMAX, EINVAL, false); _maximum_index = _type_index > _maximum_index ? _type_index : _maximum_index; return true; } bool should_skip_type_state_output() const throw() { return _format_mode == mode::positional && _current_pass == pass::position_scan; } private: // Positional parameter processing occurs in two passes. In the first pass, // we scan the format string to accumulate type information for the // positional parameters. If the format string is a standard format string // (that does not use positional parameters), then this pass also does the // output. Otherwise, if the format string uses positional parameters, // the actual output operation takes place in the second pass, after we've // aggregated all of the information about the positional parameters. enum class pass : unsigned { not_started, position_scan, output, finished }; // These represent the two modes of formatting. The processor starts off in // the unknown mode and can transition to either nonpositional (indicating // standard format string processing) or positional mode. enum class mode : unsigned { unknown, nonpositional, positional }; // These represent the different types of parameters that may be present in // the argument list (remember that integral types narrower than int and // real types narrower than double are promoted for the varargs call). enum class parameter_type : unsigned { unused, int32, int64, pointer, real64 }; // This represents a parameter in the varargs list, along with information // we have about the parameter from the format string. We store an array // of these structures internally, and the structures are updated in several // steps. They start off uninitialized, with the "unused" actual type. If // the format string uses positional parameters, then the actual type, format // type, and flags are updated during the positional scan of the format // string. At the end of that scan, we iterate over the parameters and the // varargs array to fill in the argptr for each of the positional parameters. struct parameter_data { parameter_type _actual_type; Character _format_type; va_list _valist_it; length_modifier _length; }; // These provide transformations from the source parameter types to their // underlying representation enumerators (from the parameter_type enum). template static parameter_type __cdecl get_parameter_type(T* ) throw() { return parameter_type::pointer; } static parameter_type __cdecl get_parameter_type(short ) throw() { return parameter_type::int32; } static parameter_type __cdecl get_parameter_type(unsigned short ) throw() { return parameter_type::int32; } static parameter_type __cdecl get_parameter_type(wchar_t ) throw() { return parameter_type::int32; } static parameter_type __cdecl get_parameter_type(int ) throw() { return parameter_type::int32; } static parameter_type __cdecl get_parameter_type(unsigned int ) throw() { return parameter_type::int32; } static parameter_type __cdecl get_parameter_type(__int64 ) throw() { return parameter_type::int64; } static parameter_type __cdecl get_parameter_type(unsigned __int64) throw() { return parameter_type::int64; } static parameter_type __cdecl get_parameter_type(_CRT_DOUBLE ) throw() { return parameter_type::real64; } // With positional parameters, a given parameter may be used multiple times // in a format string. All appearances of a given parameter must be // consistent (they must match in type and width). The first time a given // parameter appears, it is accepted as-is. Each subsequent time that it // reappears, this function is called to ensure that the reappearance is // consistent with the previously processed appearance. // // Returns true if the reappearance is consistent (and valid). Returns false // otherwise. This function does not invoke invalid parameter handling. bool is_positional_parameter_reappearance_consistent( parameter_data const& parameter, parameter_type const actual_type, Character const format_type, length_modifier const length ) throw() { // Pointer format specifiers are exclusive; a parameter that previously // appeared as a pointer may not reappear as a non-pointer, and vice- // versa. bool const old_is_pointer{is_pointer_specifier(parameter._format_type)}; bool const new_is_pointer{is_pointer_specifier(format_type)}; if (old_is_pointer || new_is_pointer) { return old_is_pointer == new_is_pointer; } // String format specifiers are exclusive, just like pointer specifiers. // We must also ensure that the two appearances match in string type: // either both must be wide or both must be narrow. bool const old_is_string{is_string_specifier(parameter._format_type)}; bool const new_is_string{is_string_specifier(format_type)}; bool const old_is_character{is_character_specifier(parameter._format_type)}; bool const new_is_character{is_character_specifier(format_type)}; if (old_is_string || new_is_string || old_is_character || new_is_character) { if (old_is_string != new_is_string || old_is_character != new_is_character) return false; bool const old_is_wide{is_wide_character_specifier(_options, parameter._format_type, parameter._length)}; bool const new_is_wide{is_wide_character_specifier(_options, format_type, length)}; if (old_is_wide != new_is_wide) return false; return true; } // Numeric specifiers are exclusive: either both appearances must be // numeric specifiers or neither appearance may be a numeric specifier. // Additionally, if both appearances are numeric specifiers, they must // both have the same width. bool const old_is_integral{is_integral_specifier(parameter._format_type)}; bool const new_is_integral{is_integral_specifier(format_type)}; if (old_is_integral || new_is_integral) { if (old_is_integral != new_is_integral) return false; if ((parameter._length == length_modifier::I) != (length == length_modifier::I)) return false; return to_integer_size(parameter._length) == to_integer_size(length); } return parameter._actual_type == actual_type; } bool validate_and_store_parameter_data( parameter_data & parameter, parameter_type const actual_type, Character const format_type, length_modifier const length ) throw() { if (parameter._actual_type == parameter_type::unused) { parameter._actual_type = actual_type; parameter._format_type = format_type; parameter._length = length; } else { _UCRT_VALIDATE_RETURN(_ptd, is_positional_parameter_reappearance_consistent( parameter, actual_type, format_type, length ), EINVAL, false); } return true; } template static bool __cdecl is_pointer_specifier(Character const specifier) throw() { return specifier == 'p'; } template static bool __cdecl is_string_specifier(Character const specifier) throw() { return specifier == 's' || specifier == 'S'; } template static bool __cdecl is_character_specifier(Character const specifier) throw() { return specifier == 'c' || specifier == 'C'; } template static bool __cdecl is_integral_specifier(Character const specifier) throw() { return specifier == 'd' || specifier == 'i' || specifier == 'o' || specifier == 'u' || specifier == 'x' || specifier == 'X' || specifier == '*'; } pass _current_pass; mode _format_mode; // The original format pointer, which allows us to reset the format iterator // between the two passes used in positional parameter processing. Character const* _format; // The array of positional parameters used in the format string. parameter_data _parameters[_ARGMAX]; // These are indices into the _parameters array. They hold the maximum // positional parameter index that has been seen so far and the index of the // current format specifier. Until a positional parameter is encountered in // the format string, these have the value -1. int _maximum_index; int _type_index; }; //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // output_processor // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // This is the main output processor class, which performs the format string // processing and formats the varargs arguments appropriately. It delegates to // the provided ProcessorBase for certain parts of the functionality. It is // expected that the ProcessorBase argument is one of the base class types // defined above. template class output_processor : private ProcessorBase { public: typedef __acrt_stdio_char_traits char_traits; output_processor( OutputAdapter const& output_adapter, uint64_t const options, Character const* const format, __crt_cached_ptd_host& ptd, va_list const arglist ) throw() : ProcessorBase{output_adapter, options, format, ptd, arglist} { } // After construction, this function is called to evaluate the formatted // output operation. This function must be called exactly once, immediately // after constructing the object. int process() throw() { if (!_output_adapter.validate(_ptd)) { return -1; } _UCRT_VALIDATE_RETURN(_ptd, _format_it != nullptr, EINVAL, -1); while (advance_to_next_pass()) { // At the start of each pass, we have no buffered string and we are // in the normal state: _string_length = 0; _state = state::normal; // Iterate over the format string until we reach the end, encounter // an I/O error, or fail due to some other error: while ((_format_char = *_format_it++) != '\0' && _characters_written >= 0) { _state = find_next_state(_format_char, _state); if (!validate_and_update_state_at_beginning_of_format_character()) { return -1; } if (_state >= state::invalid) { _UCRT_VALIDATE_RETURN(_ptd, ("Incorrect format specifier", 0), EINVAL, -1); } bool result = false; switch (_state) { case state::normal: result = state_case_normal (); break; case state::percent: result = state_case_percent (); break; case state::flag: result = state_case_flag (); break; case state::width: result = state_case_width (); break; case state::dot: result = state_case_dot (); break; case state::precision: result = state_case_precision(); break; case state::size: result = state_case_size (); break; case state::type: result = state_case_type (); break; } // If the state-specific operation failed, return immediately. // The individual state cases are responsible for invoking the // invalid parameter handler if the failure is due to an invalid // parameter. if (!result) return -1; } if (!validate_and_update_state_at_end_of_format_string()) return -1; } return _characters_written; } private: // The normal state is entered when a character that is not part of a format // specifier is encountered in the format string. We simply write the // character. There are four parts to the normal state: the state_case_normal // function is called when the state is entered. It tests whether the actual // output operation should take place. The state_case_normal_common function // performs the actual output operation and is called from elsewhere in this // class. __forceinline bool state_case_normal() throw() { if (should_skip_normal_state_processing()) return true; _UCRT_VALIDATE_RETURN(_ptd, state_case_normal_common(), EINVAL, false); return true; } __forceinline bool state_case_normal_common() throw() { if (!state_case_normal_tchar(Character())) return false; _output_adapter.write_character(_format_char, &_characters_written, _ptd); return true; } __forceinline bool state_case_normal_tchar(char) throw() { _string_is_wide = false; if (__acrt_isleadbyte_l_noupdate(_format_char, _ptd.get_locale())) { _output_adapter.write_character(_format_char, &_characters_written, _ptd); _format_char = *_format_it++; // Ensure that we do not fall off the end of the format string: _UCRT_VALIDATE_RETURN(_ptd, _format_char != '\0', EINVAL, false); } return true; } __forceinline bool state_case_normal_tchar(wchar_t) throw() { _string_is_wide = true; return true; } // We enter the percent state when we read a '%' from the format string. The // percent sign begins a format specifier, so we reset our internal state for // the new format specifier. __forceinline bool state_case_percent() throw() { _field_width = 0; _suppress_output = false; _flags = 0; _precision = -1; _length = length_modifier::none; _string_is_wide = false; return true; } // We enter the flag state when we are reading a format specifier and we // encounter one of the optional flags. We update our state to account for // the flag. __forceinline bool state_case_flag() throw() { // Set the flag based on which flag character: switch (_format_char) { case '-': set_flag(FL_LEFT ); break; // '-' => left justify case '+': set_flag(FL_SIGN ); break; // '+' => force sign indicator case ' ': set_flag(FL_SIGNSP ); break; // ' ' => force sign or space case '#': set_flag(FL_ALTERNATE); break; // '#' => alternate form case '0': set_flag(FL_LEADZERO ); break; // '0' => pad with leading zeros } return true; } // Parses an integer from the format string. It is expected in this function // that it is called _after_ the format string iterator has been advanced to // the next character, so it starts parsing from _format_it - 1 (i.e., from // the character currently being processed, a copy of which is stored in // the _format_char data member). bool parse_int_from_format_string(int* const result) throw() { auto const reset_errno = _ptd.get_errno().create_guard(); Character* end{}; *result = static_cast(_tcstol_internal(_ptd, _format_it - 1, &end, 10)); if (_ptd.get_errno().check(ERANGE)) { return false; } if (end < _format_it) { return false; } _format_it = end; return true; } // We enter the width state when we are reading a format specifier and we // encounter either an asterisk (indicating the width should be read from // the varargs) or a digit (indicating that we are in the process of reading // the width from the format string. __forceinline bool state_case_width() throw() { if (_format_char != '*') { return parse_int_from_format_string(&_field_width); } // If the format character is an asterisk, we read the width from the // varargs. If we read a negative value, we treat it as the '-' flag // followed by a positive width (per the C Standard Library spec). if (!update_field_width()) return false; if (!should_format()) return true; if (_field_width < 0) { set_flag(FL_LEFT); _field_width = -_field_width; } return true; } // We enter the dot state when we read a '.' from the format string. This // '.' introduces the precision part of the format specifier. __forceinline bool state_case_dot() throw() { // Reset the precision to zero. If the dot is not followed by a number, // it means a precision of zero, not the default precision (per the C // Standard Library specification). (Note: We represent the default // precision with -1.) _precision = 0; return true; } // We enter the precision state after we read a ',' from the format string. // At this point, we read the precision, in a manner similar to how we read // the width. __forceinline bool state_case_precision() throw() { if (_format_char != '*') { return parse_int_from_format_string(&_precision); } // If the format character is an asterisk, we read the width from the // varargs. If we read a negative value, we treat it as indicating the // default precision. if (!update_precision()) return false; if (!should_format()) return true; if (_precision < 0) _precision = -1; return true; } // We enter the size state when we have read one of the size characters from // the format string. bool state_case_size() throw() { if (_format_char == 'F') { // We hand the 'F' character as a length modifier because the length // modifier occurs before the type specifier. If we find an 'F' and // we are in the legacy compatibility mode that supports the 'F' length // modifier, we just ignore it (it has no meaning). Otherwise we are // not in compatibility mode so we switch out to the type case to handle // the 'F' as a %F format specifier: if ((_options & _CRT_INTERNAL_PRINTF_LEGACY_MSVCRT_COMPATIBILITY) == 0) { _state = state::type; return state_case_type(); } return true; } if (_format_char == 'N') { // If we find an 'N' and we are in the legacy compatibility mode that // supports the 'N' length modifier, we just ignore it (it has no // meaning). Otherwise, we are not in compatibility mode, so we // invoke the invalid parameter handler and return failure. if ((_options & _CRT_INTERNAL_PRINTF_LEGACY_MSVCRT_COMPATIBILITY) == 0) { _state = state::invalid; #pragma warning(suppress: __WARNING_IGNOREDBYCOMMA) // 6319 comma operator _UCRT_VALIDATE_RETURN(_ptd, ("N length modifier not specifier", false), EINVAL, false); return false; } return true; } _UCRT_VALIDATE_RETURN(_ptd, _length == length_modifier::none, EINVAL, false); // We just read a size specifier; set the flags based on it: switch (_format_char) { case 'h': { if (*_format_it == 'h') { ++_format_it; _length = length_modifier::hh; } else { _length = length_modifier::h; } return true; } case 'I': { // The I32, I64, and I length modifiers are Microsoft extensions. if (*_format_it == '3' && *(_format_it + 1) == '2') { _format_it += 2; _length = length_modifier::I32; } else if (*_format_it == '6' && *(_format_it + 1) == '4') { _format_it += 2; _length = length_modifier::I64; } else if (*_format_it == 'd' || *_format_it == 'i' || *_format_it == 'o' || *_format_it == 'u' || *_format_it == 'x' || *_format_it == 'X') { // If we support positional parameters, then %I without a following // 32 or 64 is platform-dependent: _length = length_modifier::I; } return true; } case 'l': { if (*_format_it == 'l') { ++_format_it; _length = length_modifier::ll; } else { _length = length_modifier::l; } return true; } case 'L': { _length = length_modifier::L; return true; } case 'j': { _length = length_modifier::j; return true; } case 't': { _length = length_modifier::t; return true; } case 'z': { _length = length_modifier::z; return true; } case 'w': { _length = length_modifier::w; return true; } case 'T': { _length = length_modifier::T; return true; } } return true; } // We enter the type case when we read the type part of the format specifier. // At this point, we have read the entire format specifier and we can extract // the value to be formatted from the varargs, format it, then output it. // This state is broken up into subfunctions, one per type category. bool state_case_type() throw() { // Each of the subfunctions is responsible for [1] extracting the next // argument from the varargs, [2] formatting that argument into the // internal buffer, and [3] updating the string state variables with // the correct data for the format operation (either _narrow_string or // _wide_string must be set correctly, the _string_length must be set // correctly, and _string_is_wide must be true if the wide string // should be used. bool result{false}; switch (_format_char) { // Individual character output: case 'C': case 'c': result = type_case_c(); break; // String output: case 'Z': result = type_case_Z(); break; case 'S': case 's': result = type_case_s(); break; // Floating-point output: case 'A': case 'E': case 'F': case 'G': case 'a': case 'e': case 'f': case 'g': result = type_case_a(); break; // Integer and pointer output: case 'd': case 'i': result = type_case_d(); break; case 'u': result = type_case_u(); break; case 'o': result = type_case_o(); break; case 'X': result = type_case_X(); break; case 'x': result = type_case_x(); break; case 'p': result = type_case_p(); break; // State reporting (no output): case 'n': result = type_case_n(); break; } // If the case-specific logic failed, return immediately. The case- // specific function is responsible for invoking the invalid parameter // handler if it needs to do so. if (!result) return false; // Check to see whether the output operation should be skipped (we skip // the output part e.g. during the positional scan pass when positional // formatting is used). if (should_skip_type_state_output()) return true; // At this point, we've completed the bulk of the formatting operation // and the string is ready to be printed. We now justify the string, // pre-pend any required prefix and leading zeroes, then print it. Well, // unless output is suppressed, that is... :-) if (_suppress_output) return true; // Compute the prefix, if one is required... Character prefix[3]{}; size_t prefix_length{0}; if (has_flag(FL_SIGNED)) { if (has_flag(FL_NEGATIVE)) { prefix[prefix_length++] = '-'; } else if (has_flag(FL_SIGN)) { prefix[prefix_length++] = '+'; } else if (has_flag(FL_SIGNSP)) { prefix[prefix_length++] = ' '; } } bool const print_integer_0x{(_format_char == 'x' || _format_char == 'X') && has_flag(FL_ALTERNATE)}; bool const print_floating_point_0x{_format_char == 'a' || _format_char == 'A'}; if (print_integer_0x || print_floating_point_0x) { prefix[prefix_length++] = '0'; prefix[prefix_length++] = adjust_hexit('x' - 'a' + '9' + 1, _format_char == 'X' || _format_char == 'A'); } // Compute the amount of padding required to get to the desired field // width, then output the left padding, prefix, leading zeroes, the // string, and right padding, in that order. int const padding = static_cast(_field_width - _string_length - prefix_length); if (!has_flag(FL_LEFT | FL_LEADZERO)) { // Left-pad with spaces write_multiple_characters(_output_adapter, ' ', padding, &_characters_written, _ptd); } // Write the prefix _output_adapter.write_string(prefix, static_cast(prefix_length), &_characters_written, _ptd); if (has_flag(FL_LEADZERO) && !has_flag(FL_LEFT)) { // Write leading zeroes write_multiple_characters(_output_adapter, '0', padding, &_characters_written, _ptd); } // Write the string write_stored_string_tchar(Character()); if (_characters_written >= 0 && has_flag(FL_LEFT)) { // Right-pad with spaces write_multiple_characters(_output_adapter, ' ', padding, &_characters_written, _ptd); } return true; } // Individual character output: The 'C' and 'c' format specifiers output an // individual wide or narrow character, respectively. They both delegate to // the type_case_c_tchar() overloads, which handle the output appropriately // for the character type of the output adapter. bool type_case_c() throw() { return type_case_c_tchar(Character()); } bool type_case_c_tchar(char) throw() { // If the character is a wide character, we translate it to multibyte // to be output, storing the multibyte string in the internal buffer: if (is_wide_character_specifier(_options, _format_char, _length)) { wchar_t wide_character{}; if (!extract_argument_from_va_list(wide_character)) { return false; } if (!should_format()) { return true; } // Convert to multibyte. If the conversion fails, we suppress the // output operation but we do not fail the entire operation: errno_t const status{_wctomb_internal(&_string_length, _buffer.data(), _buffer.count(), wide_character, _ptd)}; if (status != 0) { _suppress_output = true; } } // If the character is a narrow character, we can just write it directly // to the output, as-is. else { if (!extract_argument_from_va_list(_buffer.data()[0])) { return false; } if (!should_format()) { return true; } _string_length = 1; } _narrow_string = _buffer.data(); return true; } bool type_case_c_tchar(wchar_t) throw() { // If the output adapter accepts wide characters, then we must transform // the character into a wide character to be output. _string_is_wide = true; wchar_t wide_character{}; if (!extract_argument_from_va_list(wide_character)) return false; if (!should_format()) return true; if (!is_wide_character_specifier(_options, _format_char, _length)) { // If the character is actually a multibyte character, then we must // transform it into the equivalent wide character. If the translation // is unsuccessful, we ignore this character but do not fail the entire // output operation. char const local_buffer[2]{ static_cast(wide_character & 0x00ff), '\0' }; int const mbc_length{_mbtowc_internal( _buffer.data(), local_buffer, _ptd.get_locale()->locinfo->_public._locale_mb_cur_max, _ptd )}; if (mbc_length < 0) { _suppress_output = true; } } else { _buffer.data()[0] = wide_character; } _wide_string = _buffer.data(); _string_length = 1; return true; } // String Output: The Z, S, and s format specifiers output a string. The Z // format specifier is an extension, used to print a Windows SDK ANSI_STRING // or UNICODE_STRING counted string. The S and s format specifiers output a // wide or narrow C string, respectively. If a null pointer is passed, we // replace it with a special sentinel string, with the contents "(null)". bool type_case_Z() throw() { // This matches the representation of the Windows SDK types ANSI_STRING // and UNICODE_STRING, which represent a counted string. struct ansi_string { unsigned short _length; unsigned short _maximum_length; char* _buffer; }; ansi_string* string{}; if (!extract_argument_from_va_list(string)) return false; if (!should_format()) return true; if (!string || string->_buffer == nullptr) { _narrow_string = narrow_null_string(); _string_length = static_cast(strlen(_narrow_string)); _string_is_wide = false; } else if (is_wide_character_specifier(_options, _format_char, _length)) { _wide_string = reinterpret_cast(string->_buffer); _string_length = string->_length / static_cast(sizeof(wchar_t)); _string_is_wide = true; } else { _narrow_string = string->_buffer; _string_length = string->_length; _string_is_wide = false; } return true; } bool type_case_s() throw() { // If this format specifier has the default precision, then the entire // string is output. If a precision is given, then we output the minimum // of the length of the C string and the given precision. Note that the // string needs not be null-terminated if a precision is given, so we // cannot call strlen to compute the length of the string. if (!extract_argument_from_va_list(_narrow_string)) return false; if (!should_format()) return true; int const maximum_length{(_precision == -1) ? INT_MAX : _precision}; if (is_wide_character_specifier(_options, _format_char, _length)) { if (!_wide_string) _wide_string = wide_null_string(); _string_is_wide = true; _string_length = static_cast(wcsnlen(_wide_string, maximum_length)); } else { if (!_narrow_string) _narrow_string = narrow_null_string(); _string_length = type_case_s_compute_narrow_string_length(maximum_length, Character()); } return true; } // We have two different implementations of the 's' type case, to handle // narrow and wide strings, since computation of the length is subtly // different depending on whether we are outputting narrow or wide // characters. These functions just update the string state appropriately // for the string that has just been read from the varargs. int type_case_s_compute_narrow_string_length(int const maximum_length, char) throw() { return static_cast(strnlen(_narrow_string, maximum_length)); } int type_case_s_compute_narrow_string_length(int const maximum_length, wchar_t) throw() { _locale_t locale = _ptd.get_locale(); if (locale->locinfo->_public._locale_lc_codepage == CP_UTF8) { int string_length{0}; for (char const* p{_narrow_string}; string_length < maximum_length && *p; ++string_length) { int const len = __crt_mbstring::__mblen_utf8(p); if (len >= 1 && len <= 4) { p += len; } else { ++p; // on illegal sequence, treat as ascii. We will return EILSEQ during write } if (len == 4) { ++string_length; // utf-16 surrogate pair } } return string_length; } int string_length{0}; for (char const* p{_narrow_string}; string_length < maximum_length && *p; ++string_length) { if (__acrt_isleadbyte_l_noupdate(static_cast(*p), locale)) { ++p; } ++p; } return string_length; } // Floating-point output: The A case handles the A, E, F, and G format specifiers. // The a case handles the a, e, f, and g format specifiers. A capital format // specifier causes us to output capital hexits, whereas a lowercase format // specifier causes us to output lowercase hexits. bool type_case_a() throw() { // The double type is signed: set_flag(FL_SIGNED); if (!validate_state_for_type_case_a()) return false; if (!should_format()) return true; // First, we need to compute the actual precision to use, limited by // both the maximum precision and the size of the buffer that we can // allocate. if (_precision < 0) { // The default precision depends on the format specifier used. For // %e, %f, and %g, C specifies that the default precision is 6. For // %a, C specifies that "if the precision is missing and FLT_RADIX // is a power of 2, then the precision is sufficient for an exact // representation of the value" (C11 7.21.6.1/8). // // The 64-bit double has 53 bits of precision. When printing in // hexadecimal form, we print one bit of precision to the left of the // radix point and the remaining 52 bits of precision to the right. // Thus, the default precision is 13 (13 * 4 == 52). if (_format_char == 'a' || _format_char == 'A') { _precision = 13; } else { _precision = 6; } } else if (_precision == 0 && (_format_char == 'g' || _format_char == 'G')) { _precision = 1; // Per C Standard Library specification. } if (!_buffer.ensure_buffer_is_big_enough(_CVTBUFSIZE + _precision, _ptd)) { // If we fail to enlarge the buffer, cap precision so that the // statically-sized buffer may be used for the formatting: _precision = static_cast(_buffer.count() - _CVTBUFSIZE); } _narrow_string = _buffer.data(); // Note that we separately handle the FORMAT_POSSCAN_PASS above. _CRT_DOUBLE tmp{}; if (!extract_argument_from_va_list<_CRT_DOUBLE>(tmp)) { return false; } // Format the number into the buffer: __acrt_fp_format( &tmp.x, _buffer.data(), _buffer.count(), _buffer.scratch_data(), _buffer.scratch_count(), static_cast(_format_char), _precision, _options, __acrt_rounding_mode::standard, _ptd); // If the precision is zero but the '#' flag is part of the specifier, // we force a decimal point: if (has_flag(FL_ALTERNATE) && _precision == 0) { force_decimal_point(_narrow_string, _ptd.get_locale()); } // The 'g' format specifier indicates that zeroes should be cropped // unless the '#' flag is part of the specifier. if ((_format_char == 'g' || _format_char == 'G') && !has_flag(FL_ALTERNATE)) { crop_zeroes(_narrow_string, _ptd.get_locale()); } // If the result was negative, we save the '-' for later and advance past // the negative sign (we handle the '-' separately, in code shared with // the integer formatting, to correctly handle flags). if (*_narrow_string == '-') { set_flag(FL_NEGATIVE); ++_narrow_string; } // If the result was a special infinity or a nan string, suppress output // of the "0x" prefix by treating the special string as just a string: if (*_narrow_string == 'i' || *_narrow_string == 'I' || *_narrow_string == 'n' || *_narrow_string == 'N') { unset_flag(FL_LEADZERO); // padded with spaces, not zeros. _format_char = 's'; } _string_length = static_cast(strlen(_narrow_string)); return true; } // Integer output: These functions handle the formatting of integer values. // There are a number of format specifiers that handle integer formatting; // we handle them separately, but they all end up calling the common function // type_case_integer(), defined last. bool type_case_d() throw() { set_flag(FL_SIGNED); return type_case_integer<10>(); } bool type_case_u() throw() { return type_case_integer<10>(); } bool type_case_o() throw() { // If the alternate flag is set, we force a leading 0: if (has_flag(FL_ALTERNATE)) set_flag(FL_FORCEOCTAL); return type_case_integer<8>(); } bool type_case_X() throw() { return type_case_integer<16>(true); } bool type_case_x() throw() { return type_case_integer<16>(); } // The 'p' format specifier writes a pointer, which is simply treated as a // hexadecimal integer with lowercase hexits. bool type_case_p() throw() { // We force the precision to be 2 * sizeof(void*), which is the number // of hexits required to represent the pointer, so that it is zero- // padded. _precision = 2 * sizeof(void*); // Ensure that we read a 32-bit integer on 32-bit architectures, and // a 64-bit integer on 64-bit platforms: _length = sizeof(void*) == 4 ? length_modifier::I32 : length_modifier::I64; return type_case_integer<16>(true); } // This is the first half of the common integer formatting routine. It // extracts the integer of the specified type from the varargs and does // pre-processing common to all integer processing. template bool type_case_integer(bool const capital_hexits = false) throw() { size_t const integer_size = to_integer_size(_length); // First, extract the argument of the required type from the varargs: __int64 original_number {}; bool extraction_result{}; switch (integer_size) { case sizeof(int8_t): extraction_result = has_flag(FL_SIGNED) ? extract_argument_from_va_list(original_number) : extract_argument_from_va_list(original_number); break; case sizeof(int16_t): extraction_result = has_flag(FL_SIGNED) ? extract_argument_from_va_list(original_number) : extract_argument_from_va_list(original_number); break; case sizeof(int32_t): extraction_result = has_flag(FL_SIGNED) ? extract_argument_from_va_list(original_number) : extract_argument_from_va_list(original_number); break; case sizeof(int64_t): extraction_result = has_flag(FL_SIGNED) ? extract_argument_from_va_list(original_number) : extract_argument_from_va_list(original_number); break; default: _UCRT_VALIDATE_RETURN(_ptd, ("Invalid integer length modifier", 0), EINVAL, false); break; } if (!extraction_result) return false; // If we're not formatting, then we're done; we just needed to read the // argument from the varargs. if (!should_format()) return true; // Check the sign of the number. If it is negative, convert it to // positive for formatting. We'll handle the minus sign later (after // we return from this function). unsigned __int64 number{}; if (has_flag(FL_SIGNED) && original_number < 0) { number = static_cast(-original_number); set_flag(FL_NEGATIVE); } else { number = static_cast(original_number); } // Check the precision to see if the default precision was specified. If // a non-default precision was specified, we turn off the zero flag, per // the C Standard Library specification. if (_precision < 0) { _precision = 1; // Default precision } else { unset_flag(FL_LEADZERO); _buffer.ensure_buffer_is_big_enough(_precision, _ptd); } // If the number is zero, we do not want to print the hex prefix ("0x"), // even if it was requested: if (number == 0) { unset_flag(FL_ALTERNATE); } _string_is_wide = sizeof(Character) == sizeof(wchar_t); if (integer_size == sizeof(int64_t)) { type_case_integer_parse_into_buffer(number, capital_hexits); } else { type_case_integer_parse_into_buffer(static_cast(number), capital_hexits); } // If the FORCEOCTAL flag is set, then we output a leading zero, unless // the formatted string already has a leading zero: if (has_flag(FL_FORCEOCTAL) && (_string_length == 0 || tchar_string()[0] != '0')) { *--tchar_string() = '0'; ++_string_length; } return true; } // This is the second half of the common integer formatting routine. It // handles the actual formatting of the number. This logic has been split // out from the first part so that we only use 64-bit arithmetic when // absolutely required (on x86, 64-bit division is Slow-with-a-capital-S). template void type_case_integer_parse_into_buffer( UnsignedInteger number, bool const capital_hexits ) throw() { // Format the number into the formatting buffer. Note that we format the // buffer at the end of the formatting buffer, which allows us to perform // the formatting from least to greatest magnitude, which maps well to // the math. Character* const last_digit{_buffer.data() + _buffer.count() - 1}; Character*& string_pointer = tchar_string(); string_pointer = last_digit; while (_precision > 0 || number != 0) { --_precision; Character digit{static_cast(number % Radix + '0')}; number /= Radix; // If the digit is greater than 9, we need to convert it to the // corresponding letter hexit in the required case: if (digit > '9') { digit = adjust_hexit(digit, capital_hexits); } *string_pointer-- = static_cast(digit); } _string_length = static_cast(last_digit - string_pointer); ++string_pointer; } // The 'n' type specifier is special: We read a short* or int* from the // varargs and write the number of characters that we've written so far to // the pointed-to integer. bool type_case_n() throw() { void* p{nullptr}; if (!extract_argument_from_va_list(p)) return false; if (!should_format()) return true; if (!_get_printf_count_output()) { _UCRT_VALIDATE_RETURN(_ptd, ("'n' format specifier disabled", 0), EINVAL, false); return false; // Unreachable } switch (to_integer_size(_length)) { case sizeof(int8_t): *static_cast(p) = static_cast(_characters_written); break; case sizeof(int16_t): *static_cast(p) = static_cast(_characters_written); break; case sizeof(int32_t): *static_cast(p) = static_cast(_characters_written); break; case sizeof(int64_t): *static_cast(p) = static_cast(_characters_written); break; default: _UCRT_VALIDATE_RETURN(_ptd, ("Invalid integer length modifier", 0), EINVAL, false); break; } // This format specifier never corresponds to an output operation: _suppress_output = true; return true; } // After we have completed the formatting of the string to be output, we // perform the output operation, which is handled by these two functions. __forceinline bool write_stored_string_tchar(char) throw() { if (!_string_is_wide || _string_length <= 0) { _output_adapter.write_string(_narrow_string, _string_length, &_characters_written, _ptd); } else if (_ptd.get_locale()->locinfo->_public._locale_lc_codepage == CP_UTF8) { // Use resumable character translator to handle UTF-16 surrogate pairs. wchar_t const* p{_wide_string}; mbstate_t state{}; for (int i = 0; i != _string_length; ++i) { char local_buffer[MB_LEN_MAX + 1]; size_t const mbc_length = __crt_mbstring::__c16rtomb_utf8(local_buffer, static_cast(*p++), &state, _ptd); if (mbc_length == __crt_mbstring::INVALID) { _characters_written = -1; return true; } _output_adapter.write_string(local_buffer, static_cast(mbc_length), &_characters_written, _ptd); } } else { wchar_t const* p{_wide_string}; for (int i{0}; i != _string_length; ++i) { char local_buffer[MB_LEN_MAX + 1]; int mbc_length{0}; errno_t const status{_wctomb_internal(&mbc_length, local_buffer, _countof(local_buffer), *p++, _ptd)}; if (status != 0 || mbc_length == 0) { _characters_written = -1; return true; } _output_adapter.write_string(local_buffer, mbc_length, &_characters_written, _ptd); } } return true; } __forceinline bool write_stored_string_tchar(wchar_t) throw() { if (_string_is_wide || _string_length <= 0) { _output_adapter.write_string(_wide_string, _string_length, &_characters_written, _ptd); } else if (_ptd.get_locale()->locinfo->_public._locale_lc_codepage == CP_UTF8) { char const* p{_narrow_string}; mbstate_t state{}; for (int i = 0; i != _string_length; ++i) { wchar_t local_buffer[2]{}; // Two wchar_ts to support UTF-16 surrogate pairs size_t const mbc_length = __crt_mbstring::__mbsrtowcs_utf8(local_buffer, &p, 2, &state, _ptd); // Unlike other per-character functions used nearby, __mbsrtowcs_utf8 will advance the string pointer. if (mbc_length == __crt_mbstring::INVALID) { _characters_written = -1; return true; } _output_adapter.write_character(local_buffer[0], &_characters_written, _ptd); if (mbc_length == 2) { _output_adapter.write_character(local_buffer[1], &_characters_written, _ptd); ++i; } } } else { _locale_t locale_ptr = _ptd.get_locale(); char const* p{_narrow_string}; for (int i{0}; i != _string_length; ++i) { wchar_t wide_character{}; int mbc_length{_mbtowc_internal(&wide_character, p, locale_ptr->locinfo->_public._locale_mb_cur_max, _ptd)}; if (mbc_length <= 0) { _characters_written = -1; return true; } _output_adapter.write_character(wide_character, &_characters_written, _ptd); p += mbc_length; } } return true; } // These functions are utilities for working with the flags, and performing // state transitions. bool has_flag (unsigned const f) const throw() { return (_flags & f) != 0; } void set_flag (unsigned const f) throw() { _flags |= f; } void unset_flag(unsigned const f) throw() { _flags &= ~f; } state find_next_state(Character const c, state const previous_state) const throw() { auto const& lookup_table = state_transition_table(); unsigned const current_class = static_cast((c < ' ' || c > 'z') ? character_type::other : static_cast(lookup_table[c - ' '].current_class)); auto const index = current_class * state_count() + static_cast(previous_state); return static_cast(lookup_table[index].next_state); } // Adjusts an out-of-range hexit character to be a lowercase or capital // letter. This function is called when formatting a number as hexadecimal // and a hexit is above 9. The normal formatting will not correctly handle // this case. Here, we adjust it by the required offset to yield a letter, // in the range [A, F], as either a lowercase or capital letter. static char __cdecl adjust_hexit(int const value, bool const capitalize) throw() { int const base {capitalize ? 'A' : 'a'}; int const offset{base - '9' - 1 }; return static_cast(offset + value); } // When a null pointer is passed, we print this string as a placeholder. static char * __cdecl narrow_null_string() throw() { return "(null)"; } static wchar_t* __cdecl wide_null_string () throw() { return L"(null)"; } }; } // namespace __crt_stdio_output