diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 4bc93c646c..6fff5b44da 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -9,9 +9,11 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_all_public_headers.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_chrono.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_cxx_stdatomic.hpp + ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_filebuf.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_format_ucd_tables.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_int128.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_iter_core.hpp + ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_print.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_sanitizer_annotate_container.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_system_error_abi.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_tzdb.hpp @@ -182,6 +184,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/numeric ${CMAKE_CURRENT_LIST_DIR}/inc/optional ${CMAKE_CURRENT_LIST_DIR}/inc/ostream + ${CMAKE_CURRENT_LIST_DIR}/inc/print ${CMAKE_CURRENT_LIST_DIR}/inc/queue ${CMAKE_CURRENT_LIST_DIR}/inc/random ${CMAKE_CURRENT_LIST_DIR}/inc/ranges @@ -285,6 +288,7 @@ set(IMPLIB_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/format.cpp ${CMAKE_CURRENT_LIST_DIR}/src/locale0_implib.cpp ${CMAKE_CURRENT_LIST_DIR}/src/nothrow.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/print.cpp ${CMAKE_CURRENT_LIST_DIR}/src/sharedmutex.cpp ${CMAKE_CURRENT_LIST_DIR}/src/stacktrace.cpp ${CMAKE_CURRENT_LIST_DIR}/src/syserror_import_lib.cpp @@ -529,6 +533,24 @@ function(target_stl_compile_options tgt rel_or_dbg) endif() endfunction() +function(generate_satellite_def SATELLITE_NAME D_SUFFIX) + set(full_satellite_name "msvcp140${D_SUFFIX}_${SATELLITE_NAME}${VCLIBS_SUFFIX}") + string(TOUPPER "${full_satellite_name}" upper_full_satellite_name) + set(satellite_input_src_file_path "${CMAKE_CURRENT_LIST_DIR}/src/msvcp_${SATELLITE_NAME}.src") + set(satellite_output_def_file_path "${CMAKE_BINARY_DIR}/msvcp_${SATELLITE_NAME}${D_SUFFIX}.def") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${satellite_input_src_file_path}") + + # We use the placeholder name "LIBRARYNAME" in the corresponding .src file of a satellite DLL + # (i.e., we write "LIBRARY LIBRARYNAME" as the first non-commented line in the file). + # + # Here, we dynamically replace this placeholder name with the name of the satellite DLL for + # the current build configuration. Then, we write out the new .def file to the binary output + # directory. + file(READ "${satellite_input_src_file_path}" satellite_def_file_contents) + string(REPLACE "LIBRARYNAME" "${upper_full_satellite_name}" satellite_def_file_contents "${satellite_def_file_contents}") + file(GENERATE OUTPUT "${satellite_output_def_file_path}" CONTENT "${satellite_def_file_contents}") +endfunction() + function(add_stl_dlls D_SUFFIX REL_OR_DBG) set(link_options_Release "/LTCG;/opt:ref,icf") set(link_options_Debug "/opt:ref,noicf") @@ -604,20 +626,13 @@ function(add_stl_dlls D_SUFFIX REL_OR_DBG) target_stl_compile_options(msvcp${D_SUFFIX}_atomic_wait_objects ${REL_OR_DBG}) # generate the .def for msvcp140_atomic_wait.dll - set(_ATOMIC_WAIT_OUTPUT_NAME "msvcp140${D_SUFFIX}_atomic_wait${VCLIBS_SUFFIX}") - string(TOUPPER "${_ATOMIC_WAIT_OUTPUT_NAME}" _ATOMIC_WAIT_OUTPUT_NAME_UPPER) - set(_ATOMIC_WAIT_DEF_NAME "${CMAKE_BINARY_DIR}/msvcp_atomic_wait${D_SUFFIX}.def") - set(_ATOMIC_WAIT_DEF_FILE_SRC "${CMAKE_CURRENT_LIST_DIR}/src/msvcp_atomic_wait.src") - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_ATOMIC_WAIT_DEF_FILE_SRC}") - file(READ "${_ATOMIC_WAIT_DEF_FILE_SRC}" _ATOMIC_WAIT_SRC_CONTENTS) - string(REPLACE "LIBRARYNAME" "${_ATOMIC_WAIT_OUTPUT_NAME_UPPER}" _ATOMIC_WAIT_DEF_CONTENTS "${_ATOMIC_WAIT_SRC_CONTENTS}") - file(GENERATE OUTPUT "${_ATOMIC_WAIT_DEF_NAME}" CONTENT "${_ATOMIC_WAIT_DEF_CONTENTS}") - - add_library(msvcp${D_SUFFIX}_atomic_wait SHARED "${_ATOMIC_WAIT_DEF_NAME}") + generate_satellite_def("atomic_wait" "${D_SUFFIX}") + + add_library(msvcp${D_SUFFIX}_atomic_wait SHARED "${CMAKE_BINARY_DIR}/msvcp_atomic_wait${D_SUFFIX}.def") target_link_libraries(msvcp${D_SUFFIX}_atomic_wait PRIVATE msvcp${D_SUFFIX}_atomic_wait_objects msvcp${D_SUFFIX}_satellite_objects msvcp${D_SUFFIX}_implib_objects "msvcp${D_SUFFIX}" "${TOOLSET_LIB}/vcruntime${D_SUFFIX}.lib" "${TOOLSET_LIB}/msvcrt${D_SUFFIX}.lib" "ucrt${D_SUFFIX}.lib" "advapi32.lib") set_target_properties(msvcp${D_SUFFIX}_atomic_wait PROPERTIES ARCHIVE_OUTPUT_NAME "msvcp140_atomic_wait${D_SUFFIX}${VCLIBS_SUFFIX}") set_target_properties(msvcp${D_SUFFIX}_atomic_wait PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") - set_target_properties(msvcp${D_SUFFIX}_atomic_wait PROPERTIES OUTPUT_NAME "${_ATOMIC_WAIT_OUTPUT_NAME}") + set_target_properties(msvcp${D_SUFFIX}_atomic_wait PROPERTIES OUTPUT_NAME "msvcp140${D_SUFFIX}_atomic_wait${VCLIBS_SUFFIX}") target_link_options(msvcp${D_SUFFIX}_atomic_wait PRIVATE ${link_options_${REL_OR_DBG}}) # msvcp140_codecvt_ids.dll diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index 1734fa07b2..7b63085c8b 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -112,6 +112,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/__msvc_filebuf.hpp b/stl/inc/__msvc_filebuf.hpp new file mode 100644 index 0000000000..7e420846cf --- /dev/null +++ b/stl/inc/__msvc_filebuf.hpp @@ -0,0 +1,821 @@ +// __msvc_filebuf.hpp internal header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef __MSVC_FILEBUF_HPP +#define __MSVC_FILEBUF_HPP +#include +#if _STL_COMPILER_PREPROCESSOR + +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +// TRANSITION, ABI: The _Path_ish functions accepting filesystem::path or experimental::filesystem::path are templates +// which always use the same types as a workaround for user code deriving from iostreams types and +// __declspec(dllexport)ing the derived types. Adding member functions to iostreams broke the ABI of such DLLs. +// Deriving and __declspec(dllexport)ing standard library types is not supported, but in this particular case +// the workaround was inexpensive. The workaround will be removed in the next ABI breaking release of the +// Visual C++ Libraries. +_STD_BEGIN +#if _HAS_CXX17 +namespace filesystem { + _EXPORT_STD class path; +} +#endif // _HAS_CXX17 + +#ifndef _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM +#ifdef _M_CEE +#define _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM 0 +#else // _M_CEE +#define _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM 1 +#endif // _M_CEE +#endif // _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM + +#if _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM +namespace experimental { + namespace filesystem { + inline namespace v1 { + class path; + } + } // namespace filesystem +} // namespace experimental +#endif // _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM + +// clang-format off +template +_INLINE_VAR constexpr bool _Is_any_path = _Is_any_of_v<_Ty +#if _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM + , experimental::filesystem::path +#endif // _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM +#if _HAS_CXX17 + , filesystem::path +#endif // _HAS_CXX17 + >; +// clang-format on + +extern "C++" _CRTIMP2_PURE FILE* __CLRCALL_PURE_OR_CDECL _Fiopen(const char*, ios_base::openmode, int); +extern "C++" _CRTIMP2_PURE FILE* __CLRCALL_PURE_OR_CDECL _Fiopen(const wchar_t*, ios_base::openmode, int); + +#ifdef _CRTBLD +extern "C++" _CRTIMP2_PURE FILE* __CLRCALL_PURE_OR_CDECL _Fiopen(const unsigned short*, ios_base::openmode, int); +#endif // _CRTBLD + +template +bool _Fgetc(_Elem& _Ch, FILE* _File) { // get an element from a C stream + return _CSTD fread(&_Ch, sizeof(_Elem), 1, _File) == 1; +} + +template <> +inline bool _Fgetc(char& _Byte, FILE* _File) { // get a char element from a C stream + int _Meta; + if ((_Meta = _CSTD fgetc(_File)) == EOF) { + return false; + } else { // got one, convert to char + _Byte = static_cast(_Meta); + return true; + } +} + +template <> +inline bool _Fgetc(wchar_t& _Wchar, FILE* _File) { // get a wchar_t element from a C stream + wint_t _Meta; + if ((_Meta = _CSTD fgetwc(_File)) == WEOF) { + return false; + } else { // got one, convert to wchar_t + _Wchar = static_cast(_Meta); + return true; + } +} + +#ifdef _CRTBLD +template <> +inline bool _Fgetc(unsigned short& _Wchar, FILE* _File) { // get an unsigned short element from a C stream + wint_t _Meta; + if ((_Meta = _CSTD fgetwc(_File)) == WEOF) { + return false; + } else { // got one, convert to unsigned short + _Wchar = static_cast(_Meta); + return true; + } +} +#endif // _CRTBLD + +template +bool _Fputc(_Elem _Ch, FILE* _File) { // put an element to a C stream + return _CSTD fwrite(&_Ch, 1, sizeof(_Elem), _File) == sizeof(_Elem); +} + +template <> +inline bool _Fputc(char _Byte, FILE* _File) { // put a char element to a C stream + return _CSTD fputc(_Byte, _File) != EOF; +} + +template <> +inline bool _Fputc(wchar_t _Wchar, FILE* _File) { // put a wchar_t element to a C stream + return _CSTD fputwc(_Wchar, _File) != WEOF; +} + +#ifdef _CRTBLD +template <> +inline bool _Fputc(unsigned short _Wchar, FILE* _File) { // put an unsigned short element to a C stream + return _CSTD fputwc(_Wchar, _File) != WEOF; +} +#endif // _CRTBLD + +template +bool _Ungetc(const _Elem&, FILE*) { // put back an arbitrary element to a C stream (always fail) + return false; +} + +template <> +inline bool _Ungetc(const char& _Byte, FILE* _File) { // put back a char element to a C stream + return _CSTD ungetc(static_cast(_Byte), _File) != EOF; +} + +template <> +inline bool _Ungetc(const signed char& _Byte, FILE* _File) { // put back a signed char element to a C stream + return _CSTD ungetc(static_cast(_Byte), _File) != EOF; +} + +template <> +inline bool _Ungetc(const unsigned char& _Byte, FILE* _File) { // put back an unsigned char element to a C stream + return _CSTD ungetc(_Byte, _File) != EOF; +} + +template <> +inline bool _Ungetc(const wchar_t& _Wchar, FILE* _File) { // put back a wchar_t element to a C stream + return _CSTD ungetwc(_Wchar, _File) != WEOF; +} + +#ifdef _CRTBLD +template <> +inline bool _Ungetc(const unsigned short& _Wchar, FILE* _File) { // put back an unsigned short element to a C stream + return _CSTD ungetwc(_Wchar, _File) != WEOF; +} +#endif // _CRTBLD + +_EXPORT_STD template +class basic_filebuf : public basic_streambuf<_Elem, _Traits> { // stream buffer associated with a C stream +public: + using _Mysb = basic_streambuf<_Elem, _Traits>; + using _Cvt = codecvt<_Elem, char, typename _Traits::state_type>; + + basic_filebuf() : _Mysb() { + _Init(nullptr, _Newfl); + } + + explicit basic_filebuf(FILE* const _File) : _Mysb() { // extension + _Init(_File, _Newfl); + } + + __CLR_OR_THIS_CALL ~basic_filebuf() noexcept override { + if (_Myfile) { + _Reset_back(); // revert from _Mychar buffer + } + + if (_Closef) { + close(); + } + } + + using int_type = typename _Traits::int_type; + using pos_type = typename _Traits::pos_type; + using off_type = typename _Traits::off_type; + + basic_filebuf(_Uninitialized) noexcept : _Mysb(_Noinit) {} + + basic_filebuf(basic_filebuf&& _Right) { + _Init(_Right._Myfile, _Newfl); // match buffering styles + _Init(static_cast(nullptr), _Closefl); // then make *this look closed + _Assign_rv(_STD move(_Right)); + } + + basic_filebuf& operator=(basic_filebuf&& _Right) { + _Assign_rv(_STD move(_Right)); + return *this; + } + + void _Assign_rv(basic_filebuf&& _Right) { + if (this != _STD addressof(_Right)) { + close(); + this->swap(_Right); + } + } + + void swap(basic_filebuf& _Right) noexcept /* strengthened */ { + if (this != _STD addressof(_Right)) { + FILE* _Myfile_sav = _Myfile; + const _Cvt* _Pcvt_sav = _Pcvt; + typename _Traits::state_type _State_sav = _State; + bool _Wrotesome_sav = _Wrotesome; + bool _Closef_sav = _Closef; + bool _Set_eback_sav = _Mysb::eback() == &_Mychar; + bool _Set_eback_live = _Mysb::gptr() == &_Mychar; + + _Elem* _Pfirst0 = _Mysb::pbase(); + _Elem* _Pnext0 = _Mysb::pptr(); + _Elem* _Pend = _Mysb::epptr(); + _Elem* _Gfirst0 = _Mysb::eback(); + _Elem* _Gnext0 = _Mysb::gptr(); + _Elem* _Gend = _Mysb::egptr(); + + // reinitialize *this + _Init(_Right._Myfile, _Right._Myfile ? _Openfl : _Newfl); + _Mysb::setp(_Right.pbase(), _Right.pptr(), _Right.epptr()); + if (_Right.eback() != &_Right._Mychar) { + _Mysb::setg(_Right.eback(), _Right.gptr(), _Right.egptr()); + } else if (_Right.gptr() != &_Right._Mychar) { + _Mysb::setg(&_Mychar, &_Mychar + 1, &_Mychar + 1); + } else { + _Mysb::setg(&_Mychar, &_Mychar, &_Mychar + 1); + } + + _Pcvt = _Right._Pcvt; + _State = _Right._State; + _Wrotesome = _Right._Wrotesome; + _Closef = _Right._Closef; + + // reinitialize _Right + _Right._Init(_Myfile_sav, _Myfile_sav ? _Openfl : _Newfl); + _Right.setp(_Pfirst0, _Pnext0, _Pend); + if (!_Set_eback_sav) { + _Right.setg(_Gfirst0, _Gnext0, _Gend); + } else if (!_Set_eback_live) { + _Right.setg(&_Right._Mychar, &_Right._Mychar + 1, &_Right._Mychar + 1); + } else { + _Right.setg(&_Right._Mychar, &_Right._Mychar, &_Right._Mychar + 1); + } + + _Right._Pcvt = _Pcvt_sav; + _Right._State = _State_sav; + _Right._Wrotesome = _Wrotesome_sav; + _Right._Closef = _Closef_sav; + + // swap ancillary data + _STD swap(_Set_eback, _Right._Set_eback); + _STD swap(_Set_egptr, _Right._Set_egptr); + + _STD swap(_Mychar, _Right._Mychar); + _STD swap(_Mysb::_Plocale, _Right._Plocale); + } + } + + basic_filebuf(const basic_filebuf&) = delete; + basic_filebuf& operator=(const basic_filebuf&) = delete; + + enum _Initfl { // reasons for a call to _Init + _Newfl, + _Openfl, + _Closefl + }; + + _NODISCARD bool is_open() const noexcept /* strengthened */ { + return static_cast(_Myfile); + } + + basic_filebuf* open(const char* _Filename, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { + // _Prot is an extension + if (_Myfile) { + return nullptr; + } + + const auto _File = _Fiopen(_Filename, _Mode, _Prot); + if (!_File) { + return nullptr; // open failed + } + + _Init(_File, _Openfl); + _Initcvt(_STD use_facet<_Cvt>(_Mysb::getloc())); + return this; // open succeeded + } + + basic_filebuf* open(const string& _Str, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { + // _Prot is an extension + return open(_Str.c_str(), _Mode, _Prot); + } + +#if _HAS_OLD_IOSTREAMS_MEMBERS + basic_filebuf* open(const char* _Filename, ios_base::open_mode _Mode) { + return open(_Filename, static_cast(_Mode)); + } +#endif // _HAS_OLD_IOSTREAMS_MEMBERS + + basic_filebuf* open(const wchar_t* _Filename, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { + // in standard as const std::filesystem::path::value_type *; _Prot is an extension + if (_Myfile) { + return nullptr; + } + + const auto _File = _Fiopen(_Filename, _Mode, _Prot); + if (!_File) { + return nullptr; // open failed + } + + _Init(_File, _Openfl); + _Initcvt(_STD use_facet<_Cvt>(_Mysb::getloc())); + return this; // open succeeded + } + + basic_filebuf* open(const wstring& _Str, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { + // extension + return open(_Str.c_str(), _Mode, _Prot); + } + +#if _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM + template + basic_filebuf* open( + const _Identity_t<_Path_ish>& _Path, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { + // _Prot is an extension + return open(_Path.c_str(), _Mode, _Prot); + } +#endif // _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM + +#if _HAS_CXX17 + template + basic_filebuf* open( + const _Identity_t<_Path_ish>& _Path, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { + // _Prot is an extension + return open(_Path.c_str(), _Mode, _Prot); + } +#endif // _HAS_CXX17 + +#if _HAS_OLD_IOSTREAMS_MEMBERS + basic_filebuf* open(const wchar_t* _Filename, ios_base::open_mode _Mode) { + // in standard as const std::filesystem::path::value_type * + return open(_Filename, static_cast(_Mode)); + } +#endif // _HAS_OLD_IOSTREAMS_MEMBERS + +#ifdef _CRTBLD + basic_filebuf* open( + const unsigned short* _Filename, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { + // in standard as const std::filesystem::path::value_type *; _Prot is an extension + if (_Myfile) { + return nullptr; + } + + const auto _File = _Fiopen(_Filename, _Mode, _Prot); + if (!_File) { + return nullptr; // open failed + } + + _Init(_File, _Openfl); + _Initcvt(_STD use_facet<_Cvt>(_Mysb::getloc())); + return this; // open succeeded + } + +#if _HAS_OLD_IOSTREAMS_MEMBERS + basic_filebuf* open(const unsigned short* _Filename, ios_base::open_mode _Mode) { + // in standard as const std::filesystem::path::value_type * + return open(_Filename, static_cast(_Mode)); + } +#endif // _HAS_OLD_IOSTREAMS_MEMBERS +#endif // _CRTBLD + + basic_filebuf* close() { + basic_filebuf* _Ans; + if (_Myfile) { // put any homing sequence and close file + _Reset_back(); // revert from _Mychar buffer + + _Ans = this; + if (!_Endwrite()) { + _Ans = nullptr; + } + + if (_CSTD fclose(_Myfile) != 0) { + _Ans = nullptr; + } + } else { + _Ans = nullptr; + } + + _Init(nullptr, _Closefl); + return _Ans; + } + + void __CLR_OR_THIS_CALL _Lock() override { // lock file instead of stream buffer + if (_Myfile) { + _CSTD _lock_file(_Myfile); + } + } + + void __CLR_OR_THIS_CALL _Unlock() override { // unlock file instead of stream buffer + if (_Myfile) { + _CSTD _unlock_file(_Myfile); + } + } + +#if defined(__cpp_lib_print) && defined(_CPPRTTI) + template + friend ios_base::iostate _Print_noformat_unicode(ostream&, string_view); +#endif + +protected: + int_type __CLR_OR_THIS_CALL overflow(int_type _Meta = _Traits::eof()) override { // put an element to stream + if (_Traits::eq_int_type(_Traits::eof(), _Meta)) { + return _Traits::not_eof(_Meta); // EOF, return success code + } + + if (_Mysb::pptr() && _Mysb::pptr() < _Mysb::epptr()) { // room in buffer, store it + *_Mysb::_Pninc() = _Traits::to_char_type(_Meta); + return _Meta; + } + + if (!_Myfile) { + return _Traits::eof(); // no open C stream, fail + } + + _Reset_back(); // revert from _Mychar buffer + if (!_Pcvt) { // no codecvt facet, put as is + return _Fputc(_Traits::to_char_type(_Meta), _Myfile) ? _Meta : _Traits::eof(); + } + + // put using codecvt facet + constexpr size_t _Codecvt_temp_buf = 32; + char _Str[_Codecvt_temp_buf]; + const _Elem _Ch = _Traits::to_char_type(_Meta); + const _Elem* _Src; + char* _Dest; + + // test result of converting one element + switch (_Pcvt->out(_State, &_Ch, &_Ch + 1, _Src, _Str, _Str + _Codecvt_temp_buf, _Dest)) { + case codecvt_base::partial: + case codecvt_base::ok: + { // converted something, try to put it out + const auto _Count = static_cast(_Dest - _Str); + if (0 < _Count && _Count != static_cast(_CSTD fwrite(_Str, 1, _Count, _Myfile))) { + return _Traits::eof(); // write failed + } + + _Wrotesome = true; // write succeeded + if (_Src != &_Ch) { + return _Meta; // converted whole element + } + + return _Traits::eof(); // conversion failed + } + + case codecvt_base::noconv: + // no conversion, put as is + return _Fputc(_Ch, _Myfile) ? _Meta : _Traits::eof(); + + default: + return _Traits::eof(); // conversion failed + } + } + + int_type __CLR_OR_THIS_CALL pbackfail(int_type _Meta = _Traits::eof()) override { + // put an element back to stream + if (_Mysb::gptr() && _Mysb::eback() < _Mysb::gptr() + && (_Traits::eq_int_type(_Traits::eof(), _Meta) + || _Traits::eq_int_type(_Traits::to_int_type(_Mysb::gptr()[-1]), + _Meta))) { // just back up position + _Mysb::_Gndec(); + return _Traits::not_eof(_Meta); + } else if (!_Myfile || _Traits::eq_int_type(_Traits::eof(), _Meta)) { + return _Traits::eof(); // no open C stream or EOF, fail + } else if (!_Pcvt && _Ungetc(_Traits::to_char_type(_Meta), _Myfile)) { + return _Meta; // no facet and unget succeeded, return + } else if (_Mysb::gptr() != &_Mychar) { // putback to _Mychar + _Mychar = _Traits::to_char_type(_Meta); + _Set_back(); // switch to _Mychar buffer + return _Meta; + } else { + return _Traits::eof(); // nowhere to put back + } + } + + int_type __CLR_OR_THIS_CALL underflow() override { // get an element from stream, but don't point past it + int_type _Meta; + if (_Mysb::gptr() && _Mysb::gptr() < _Mysb::egptr()) { + return _Traits::to_int_type(*_Mysb::gptr()); // return buffered + } else if (_Traits::eq_int_type(_Traits::eof(), _Meta = uflow())) { + return _Meta; // uflow failed, return EOF + } else { // get a char, don't point past it + pbackfail(_Meta); + return _Meta; + } + } + + int_type __CLR_OR_THIS_CALL uflow() override { // get an element from stream, point past it + if (_Mysb::gptr() && _Mysb::gptr() < _Mysb::egptr()) { + return _Traits::to_int_type(*_Mysb::_Gninc()); // return buffered + } + + if (!_Myfile) { + return _Traits::eof(); // no open C stream, fail + } + + _Reset_back(); // revert from _Mychar buffer + if (!_Pcvt) { // no codecvt facet, just get it + _Elem _Ch; + return _Fgetc(_Ch, _Myfile) ? _Traits::to_int_type(_Ch) : _Traits::eof(); + } + + // build string until codecvt succeeds + string _Str; + + for (;;) { // get using codecvt facet + const char* _Src; + int _Meta = _CSTD fgetc(_Myfile); + + if (_Meta == EOF) { + return _Traits::eof(); // partial char? + } + + _Str.push_back(static_cast(_Meta)); // append byte and convert + + _Elem _Ch; + _Elem* _Dest; + + // test result of converting one element + switch (_Pcvt->in(_State, _Str.data(), _Str.data() + _Str.size(), _Src, &_Ch, &_Ch + 1, _Dest)) { + case codecvt_base::partial: + case codecvt_base::ok: + if (_Dest != &_Ch) { // got an element, put back excess and deliver it + auto _Nleft = _Str.data() + _Str.size() - _Src; + while (0 < _Nleft) { + _CSTD ungetc(_Src[--_Nleft], _Myfile); + } + + return _Traits::to_int_type(_Ch); + } + + _Str.erase(0, static_cast(_Src - _Str.data())); // partial, discard used input + break; + + case codecvt_base::noconv: + // noconv is only possible if _Elem is char, so we can use it directly + return static_cast(_Str.front()); + + default: + return _Traits::eof(); // conversion failed + } + } + } + + streamsize __CLR_OR_THIS_CALL xsgetn(_Elem* _Ptr, streamsize _Count) override { + // get _Count characters from stream + if constexpr (sizeof(_Elem) == 1) { + if (_Count <= 0) { + return 0; + } + + if (_Pcvt) { // if we need a nontrivial codecvt transform, do the default expensive thing + return _Mysb::xsgetn(_Ptr, _Count); + } + + // assuming this is OK because _Ptr + _Count must be valid + auto _Count_s = static_cast(_Count); + const auto _Start_count = _Count; + const auto _Available = static_cast(_Mysb::_Gnavail()); + if (0 < _Available) { // copy from get area + const auto _Read_size = (_STD min)(_Count_s, _Available); + _Traits::copy(_Ptr, _Mysb::gptr(), _Read_size); + _Ptr += _Read_size; + _Count_s -= _Read_size; + _Mysb::gbump(static_cast(_Read_size)); + } + + if (_Myfile) { // open C stream, attempt read + _Reset_back(); // revert from _Mychar buffer + // process in 4k - 1 chunks to avoid tripping over fread's clobber-the-end behavior when + // doing \r\n -> \n translation + constexpr size_t _Read_size = 4095; // _INTERNAL_BUFSIZ - 1 + while (_Read_size < _Count_s) { + const auto _Actual_read = _CSTD fread(_Ptr, sizeof(_Elem), _Read_size, _Myfile); + _Ptr += _Actual_read; + _Count_s -= _Actual_read; + if (_Actual_read != _Read_size) { + return static_cast(_Start_count - _Count_s); + } + } + + if (0 < _Count_s) { + _Count_s -= _CSTD fread(_Ptr, sizeof(_Elem), _Count_s, _Myfile); + } + } + + return static_cast(_Start_count - _Count_s); + } else { // non-chars always get element-by-element processing + return _Mysb::xsgetn(_Ptr, _Count); + } + } + + streamsize __CLR_OR_THIS_CALL xsputn(const _Elem* _Ptr, streamsize _Count) override { + // put _Count characters to stream + if constexpr (sizeof(_Elem) == 1) { + if (_Pcvt) { // if we need a nontrivial codecvt transform, do the default expensive thing + return _Mysb::xsputn(_Ptr, _Count); + } + + const streamsize _Start_count = _Count; + streamsize _Size = _Mysb::_Pnavail(); + if (0 < _Count && 0 < _Size) { // copy to write buffer + if (_Count < _Size) { + _Size = _Count; + } + + _Traits::copy(_Mysb::pptr(), _Ptr, static_cast(_Size)); + _Ptr += _Size; + _Count -= _Size; + _Mysb::pbump(static_cast(_Size)); + } + + if (0 < _Count && _Myfile) { // open C stream, attempt write + _Count -= _CSTD fwrite(_Ptr, sizeof(_Elem), static_cast(_Count), _Myfile); + } + + return _Start_count - _Count; + } else { // non-chars always get element-by-element processing + return _Mysb::xsputn(_Ptr, _Count); + } + } + + pos_type __CLR_OR_THIS_CALL seekoff(off_type _Off, ios_base::seekdir _Way, + ios_base::openmode = ios_base::in | ios_base::out) override { // change position by _Off + fpos_t _Fileposition; + + if (_Mysb::gptr() == &_Mychar // something putback + && _Way == ios_base::cur // a relative seek + && !_Pcvt) { // not converting + _Off -= static_cast(sizeof(_Elem)); // back up over _Elem bytes + } + + if (!_Myfile || !_Endwrite() + || ((_Off != 0 || _Way != ios_base::cur) && _CSTD _fseeki64(_Myfile, _Off, _Way) != 0) + || _CSTD fgetpos(_Myfile, &_Fileposition) != 0) { + return pos_type{off_type{-1}}; // report failure + } + + _Reset_back(); // revert from _Mychar buffer, discarding any putback + return pos_type{_State, _Fileposition}; // return new position + } + + pos_type __CLR_OR_THIS_CALL seekpos(pos_type _Pos, ios_base::openmode = ios_base::in | ios_base::out) override { + // change position to _Pos + off_type _Off = static_cast(_Pos); + + if (!_Myfile || !_Endwrite() || _CSTD fsetpos(_Myfile, &_Off) != 0) { + return pos_type{off_type{-1}}; // report failure + } + + _State = _Pos.state(); + _Reset_back(); // revert from _Mychar buffer, discarding any putback + return pos_type{_State, _Off}; // return new position + } + + _Mysb* __CLR_OR_THIS_CALL setbuf(_Elem* _Buffer, streamsize _Count) override { // offer _Buffer to C stream + int _Mode; + if (!_Buffer && _Count == 0) { + _Mode = _IONBF; + } else { + _Mode = _IOFBF; + } + + const size_t _Size = static_cast(_Count) * sizeof(_Elem); + + if (!_Myfile || _CSTD setvbuf(_Myfile, reinterpret_cast(_Buffer), _Mode, _Size) != 0) { + return nullptr; // failed + } + + // new buffer, reinitialize pointers + _Init(_Myfile, _Openfl); + return this; + } + + int __CLR_OR_THIS_CALL sync() override { // synchronize C stream with external file + if (!_Myfile || _Traits::eq_int_type(_Traits::eof(), overflow()) || 0 <= _CSTD fflush(_Myfile)) { + return 0; + } + + return -1; + } + + void __CLR_OR_THIS_CALL imbue(const locale& _Loc) override { + // set locale to argument (capture nontrivial codecvt facet) + _Initcvt(_STD use_facet<_Cvt>(_Loc)); + } + + void _Init(FILE* _File, _Initfl _Which) noexcept { // initialize to C stream _File after {new, open, close} + using _State_type = typename _Traits::state_type; + + __PURE_APPDOMAIN_GLOBAL static _State_type _Stinit; // initial state + + _Closef = _Which == _Openfl; + _Wrotesome = false; + + _Mysb::_Init(); // initialize stream buffer base object + + if (_File && sizeof(_Elem) == 1) { // point inside C stream with [first, first + count) buffer + _Elem** _Pb = nullptr; + _Elem** _Pn = nullptr; + int* _Nr = nullptr; + + ::_get_stream_buffer_pointers( + _File, reinterpret_cast(&_Pb), reinterpret_cast(&_Pn), &_Nr); + int* _Nw = _Nr; + + _Mysb::_Init(_Pb, _Pn, _Nr, _Pb, _Pn, _Nw); + } + + _Myfile = _File; + _State = _Stinit; + _Pcvt = nullptr; // pointer to codecvt facet + } + + bool _Endwrite() { // put shift to initial conversion state, as needed + if (!_Pcvt || !_Wrotesome) { + return true; + } + + // may have to put + if (_Traits::eq_int_type(_Traits::eof(), overflow())) { + return false; + } + + constexpr size_t _Codecvt_temp_buf = 32; + char _Str[_Codecvt_temp_buf]; + char* _Dest; + switch (_Pcvt->unshift(_State, _Str, _Str + _Codecvt_temp_buf, _Dest)) { // test result of homing conversion + case codecvt_base::ok: + _Wrotesome = false; // homed successfully + _FALLTHROUGH; + + case codecvt_base::partial: + { // put any generated bytes + const auto _Count = static_cast(_Dest - _Str); + if (0 < _Count && _Count != static_cast(_CSTD fwrite(_Str, 1, _Count, _Myfile))) { + return false; // write failed + } + + return !_Wrotesome; + } + + case codecvt_base::noconv: + _Wrotesome = false; // homed successfully + return true; // nothing else to do + + default: + return false; // conversion failed + } + } + + void _Initcvt(const _Cvt& _Newcvt) noexcept { // initialize codecvt pointer + if (_Newcvt.always_noconv()) { + _Pcvt = nullptr; // nothing to do + } else { // set up for nontrivial codecvt facet + _Pcvt = _STD addressof(_Newcvt); + _Mysb::_Init(); // reset any buffering + } + } + +private: + const _Cvt* _Pcvt; // pointer to codecvt facet (may be null) + _Elem _Mychar; // putback character, when _Ungetc fails + bool _Wrotesome; // true if homing sequence may be needed + typename _Traits::state_type _State; // current conversion state + bool _Closef; // true if C stream must be closed + FILE* _Myfile; // pointer to C stream + + void _Reset_back() noexcept { // restore buffer after putback + if (_Mysb::eback() == &_Mychar) { + _Mysb::setg(_Set_eback, _Set_eback, _Set_egptr); + } + } + + void _Set_back() noexcept { // set up putback area + if (_Mysb::eback() != &_Mychar) { // save current get buffer + _Set_eback = _Mysb::eback(); + _Set_egptr = _Mysb::egptr(); + } + _Mysb::setg(&_Mychar, &_Mychar, &_Mychar + 1); + } + + _Elem* _Set_eback; // saves eback() during one-element putback + _Elem* _Set_egptr; // saves egptr() +}; + +_EXPORT_STD template +void swap(basic_filebuf<_Elem, _Traits>& _Left, basic_filebuf<_Elem, _Traits>& _Right) noexcept /* strengthened */ { + _Left.swap(_Right); +} + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) + +#endif // _STL_COMPILER_PREPROCESSOR +#endif // __MSVC_FILEBUF_HPP diff --git a/stl/inc/__msvc_iter_core.hpp b/stl/inc/__msvc_iter_core.hpp index aeacf38ca3..e94bd7183e 100644 --- a/stl/inc/__msvc_iter_core.hpp +++ b/stl/inc/__msvc_iter_core.hpp @@ -410,6 +410,10 @@ concept sized_sentinel_for = sentinel_for<_Se, _It> }; // clang-format on +_EXPORT_STD struct default_sentinel_t {}; + +_EXPORT_STD inline constexpr default_sentinel_t default_sentinel{}; + namespace ranges { _EXPORT_STD enum class subrange_kind : bool { unsized, sized }; diff --git a/stl/inc/__msvc_print.hpp b/stl/inc/__msvc_print.hpp new file mode 100644 index 0000000000..9168dac9b1 --- /dev/null +++ b/stl/inc/__msvc_print.hpp @@ -0,0 +1,75 @@ +// __msvc_print.hpp internal header (core) + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef __MSVC_PRINT_HPP +#define __MSVC_PRINT_HPP +#include +#if _STL_COMPILER_PREPROCESSOR + +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +_EXTERN_C + +enum class __std_unicode_console_handle : intptr_t { _Invalid = -1 }; + +struct __std_unicode_console_retrieval_result { + __std_unicode_console_handle _Console_handle; + + // For this, we have a few potential return values: + // + // - __std_win_error::_Success: The operation completed successfully. This is the only value for which the + // _Console_handle field has a well-defined value. + // + // - __std_win_error::_File_not_found: The FILE* provided is valid, but it is determined to not be associated + // with a unicode console. In this case, printing should fall back to vprint_nonunicode(). + // + // - __std_win_error::_Not_supported: The FILE* provided does not actually have an associated output stream. In + // this case, the entire print can safely be elided, thanks to the "as-if" rule. + // (We haven't observed this happening in practice. Console applications with stdout redirected to NUL + // and Windows applications both appear to activate the __std_win_error::_File_not_found "valid, but + // not a unicode console" codepath.) + // + // - __std_win_error::_Invalid_parameter: The FILE* provided is invalid. A std::system_error exception should be + // thrown if this value is returned within the FILE* overload of vprint_unicode(). + __std_win_error _Error; +}; + +_NODISCARD _Success_(return._Error == __std_win_error::_Success) __std_unicode_console_retrieval_result + __stdcall __std_get_unicode_console_handle_from_file_stream(_In_ FILE* _Stream) noexcept; + +_NODISCARD _Success_(return == __std_win_error::_Success) __std_win_error + __stdcall __std_print_to_unicode_console(_In_ __std_unicode_console_handle _Console_handle, + _In_reads_(_Str_size) const char* _Str, _In_ size_t _Str_size) noexcept; + +_END_EXTERN_C + +_STD_BEGIN + +_NODISCARD consteval bool _Is_ordinary_literal_encoding_utf8() { + // See: https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers +#if defined(_MSVC_EXECUTION_CHARACTER_SET) && _MSVC_EXECUTION_CHARACTER_SET == 65001 // Unicode (UTF-8) == 65001 + return true; +#else + return false; +#endif +} + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) +#endif // _STL_COMPILER_PREPROCESSOR +#endif // __MSVC_PRINT_HPP diff --git a/stl/inc/chrono b/stl/inc/chrono index e6bfa99bb7..c81b284dbe 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -49,21 +49,6 @@ _STL_DISABLE_CLANG_WARNINGS _STD_BEGIN #if _HAS_CXX17 -// We would really love to use the proper way of building error_code by specializing -// is_error_code_enum and make_error_code for __std_win_error, but because: -// 1. We would like to keep the definition of __std_win_error in xfilesystem_abi.h -// 2. and xfilesystem_abi.h cannot include -// 3. and specialization of is_error_code_enum and overload of make_error_code -// need to be kept together with the enum (see limerick in N4810 [temp.expl.spec]/7) -// we resort to using this _Make_ec helper. -_NODISCARD inline error_code _Make_ec(__std_win_error _Errno) noexcept { // make an error_code - return {static_cast(_Errno), _STD system_category()}; -} - -[[noreturn]] inline void _Throw_system_error_from_std_win_error(const __std_win_error _Errno) { - _THROW(system_error{_Make_ec(_Errno)}); -} - _NODISCARD inline int _Check_convert_result(const __std_fs_convert_result _Result) { if (_Result._Err != __std_win_error::_Success) { _Throw_system_error_from_std_win_error(_Result._Err); diff --git a/stl/inc/format b/stl/inc/format index 8c5eec979c..9c0121b7fe 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -3614,6 +3614,54 @@ _NODISCARD size_t formatted_size(const locale& _Loc, const wformat_string<_Types return _Buf._Count(); } +#if _HAS_CXX23 +enum class _Add_newline : bool { _Nope, _Yes }; + +_NODISCARD inline string _Unescape_braces(const _Add_newline _Add_nl, const string_view _Old_str) { + string _Unescaped_str; + + if (_Old_str.empty()) { + if (_Add_nl == _Add_newline::_Yes) { + _Unescaped_str.push_back('\n'); + } + + return _Unescaped_str; + } + + size_t _Unescaped_str_expected_size = _Old_str.size(); + + if (_Add_nl == _Add_newline::_Yes) { + ++_Unescaped_str_expected_size; + } + + _Unescaped_str.resize_and_overwrite(_Unescaped_str_expected_size, [_Add_nl, _Old_str](char* _Dest_ptr, size_t) { + char _Prev_char = _Old_str.front(); + size_t _Num_chars_written = 1; + *_Dest_ptr++ = _Prev_char; + + for (const auto _Curr_char : _Old_str.substr(1)) { + if ((_Curr_char == '{' && _Prev_char == '{') || (_Curr_char == '}' && _Prev_char == '}')) { + _Prev_char = '\0'; + } else { + *_Dest_ptr++ = _Curr_char; + ++_Num_chars_written; + + _Prev_char = _Curr_char; + } + } + + if (_Add_nl == _Add_newline::_Yes) { + *_Dest_ptr = '\n'; + ++_Num_chars_written; + } + + return _Num_chars_written; + }); + + return _Unescaped_str; +} +#endif // _HAS_CXX23 + _STD_END #pragma pop_macro("new") diff --git a/stl/inc/fstream b/stl/inc/fstream index ecf2b7509b..6d19f9800e 100644 --- a/stl/inc/fstream +++ b/stl/inc/fstream @@ -8,6 +8,7 @@ #define _FSTREAM_ #include #if _STL_COMPILER_PREPROCESSOR +#include <__msvc_filebuf.hpp> #include #pragma pack(push, _CRT_PACKING) @@ -17,791 +18,7 @@ _STL_DISABLE_CLANG_WARNINGS #pragma push_macro("new") #undef new -// TRANSITION, ABI: The _Path_ish functions accepting filesystem::path or experimental::filesystem::path are templates -// which always use the same types as a workaround for user code deriving from iostreams types and -// __declspec(dllexport)ing the derived types. Adding member functions to iostreams broke the ABI of such DLLs. -// Deriving and __declspec(dllexport)ing standard library types is not supported, but in this particular case -// the workaround was inexpensive. The workaround will be removed in the next ABI breaking release of the -// Visual C++ Libraries. _STD_BEGIN -#if _HAS_CXX17 -namespace filesystem { - _EXPORT_STD class path; -} -#endif // _HAS_CXX17 - -#ifndef _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM -#ifdef _M_CEE -#define _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM 0 -#else // _M_CEE -#define _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM 1 -#endif // _M_CEE -#endif // _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM - -#if _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM -namespace experimental { - namespace filesystem { - inline namespace v1 { - class path; - } - } // namespace filesystem -} // namespace experimental -#endif // _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM - -// clang-format off -template -_INLINE_VAR constexpr bool _Is_any_path = _Is_any_of_v<_Ty -#if _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM - , experimental::filesystem::path -#endif // _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM -#if _HAS_CXX17 - , filesystem::path -#endif // _HAS_CXX17 - >; -// clang-format on - -extern "C++" _CRTIMP2_PURE FILE* __CLRCALL_PURE_OR_CDECL _Fiopen(const char*, ios_base::openmode, int); -extern "C++" _CRTIMP2_PURE FILE* __CLRCALL_PURE_OR_CDECL _Fiopen(const wchar_t*, ios_base::openmode, int); - -#ifdef _CRTBLD -extern "C++" _CRTIMP2_PURE FILE* __CLRCALL_PURE_OR_CDECL _Fiopen(const unsigned short*, ios_base::openmode, int); -#endif // _CRTBLD - -template -bool _Fgetc(_Elem& _Ch, FILE* _File) { // get an element from a C stream - return _CSTD fread(&_Ch, sizeof(_Elem), 1, _File) == 1; -} - -template <> -inline bool _Fgetc(char& _Byte, FILE* _File) { // get a char element from a C stream - int _Meta; - if ((_Meta = _CSTD fgetc(_File)) == EOF) { - return false; - } else { // got one, convert to char - _Byte = static_cast(_Meta); - return true; - } -} - -template <> -inline bool _Fgetc(wchar_t& _Wchar, FILE* _File) { // get a wchar_t element from a C stream - wint_t _Meta; - if ((_Meta = _CSTD fgetwc(_File)) == WEOF) { - return false; - } else { // got one, convert to wchar_t - _Wchar = static_cast(_Meta); - return true; - } -} - -#ifdef _CRTBLD -template <> -inline bool _Fgetc(unsigned short& _Wchar, FILE* _File) { // get an unsigned short element from a C stream - wint_t _Meta; - if ((_Meta = _CSTD fgetwc(_File)) == WEOF) { - return false; - } else { // got one, convert to unsigned short - _Wchar = static_cast(_Meta); - return true; - } -} -#endif // _CRTBLD - -template -bool _Fputc(_Elem _Ch, FILE* _File) { // put an element to a C stream - return _CSTD fwrite(&_Ch, 1, sizeof(_Elem), _File) == sizeof(_Elem); -} - -template <> -inline bool _Fputc(char _Byte, FILE* _File) { // put a char element to a C stream - return _CSTD fputc(_Byte, _File) != EOF; -} - -template <> -inline bool _Fputc(wchar_t _Wchar, FILE* _File) { // put a wchar_t element to a C stream - return _CSTD fputwc(_Wchar, _File) != WEOF; -} - -#ifdef _CRTBLD -template <> -inline bool _Fputc(unsigned short _Wchar, FILE* _File) { // put an unsigned short element to a C stream - return _CSTD fputwc(_Wchar, _File) != WEOF; -} -#endif // _CRTBLD - -template -bool _Ungetc(const _Elem&, FILE*) { // put back an arbitrary element to a C stream (always fail) - return false; -} - -template <> -inline bool _Ungetc(const char& _Byte, FILE* _File) { // put back a char element to a C stream - return _CSTD ungetc(static_cast(_Byte), _File) != EOF; -} - -template <> -inline bool _Ungetc(const signed char& _Byte, FILE* _File) { // put back a signed char element to a C stream - return _CSTD ungetc(static_cast(_Byte), _File) != EOF; -} - -template <> -inline bool _Ungetc(const unsigned char& _Byte, FILE* _File) { // put back an unsigned char element to a C stream - return _CSTD ungetc(_Byte, _File) != EOF; -} - -template <> -inline bool _Ungetc(const wchar_t& _Wchar, FILE* _File) { // put back a wchar_t element to a C stream - return _CSTD ungetwc(_Wchar, _File) != WEOF; -} - -#ifdef _CRTBLD -template <> -inline bool _Ungetc(const unsigned short& _Wchar, FILE* _File) { // put back an unsigned short element to a C stream - return _CSTD ungetwc(_Wchar, _File) != WEOF; -} -#endif // _CRTBLD - -_EXPORT_STD template -class basic_filebuf : public basic_streambuf<_Elem, _Traits> { // stream buffer associated with a C stream -public: - using _Mysb = basic_streambuf<_Elem, _Traits>; - using _Cvt = codecvt<_Elem, char, typename _Traits::state_type>; - - basic_filebuf() : _Mysb() { - _Init(nullptr, _Newfl); - } - - explicit basic_filebuf(FILE* const _File) : _Mysb() { // extension - _Init(_File, _Newfl); - } - - __CLR_OR_THIS_CALL ~basic_filebuf() noexcept override { - if (_Myfile) { - _Reset_back(); // revert from _Mychar buffer - } - - if (_Closef) { - close(); - } - } - - using int_type = typename _Traits::int_type; - using pos_type = typename _Traits::pos_type; - using off_type = typename _Traits::off_type; - - basic_filebuf(_Uninitialized) noexcept : _Mysb(_Noinit) {} - - basic_filebuf(basic_filebuf&& _Right) { - _Init(_Right._Myfile, _Newfl); // match buffering styles - _Init(static_cast(nullptr), _Closefl); // then make *this look closed - _Assign_rv(_STD move(_Right)); - } - - basic_filebuf& operator=(basic_filebuf&& _Right) { - _Assign_rv(_STD move(_Right)); - return *this; - } - - void _Assign_rv(basic_filebuf&& _Right) { - if (this != _STD addressof(_Right)) { - close(); - this->swap(_Right); - } - } - - void swap(basic_filebuf& _Right) noexcept /* strengthened */ { - if (this != _STD addressof(_Right)) { - FILE* _Myfile_sav = _Myfile; - const _Cvt* _Pcvt_sav = _Pcvt; - typename _Traits::state_type _State_sav = _State; - bool _Wrotesome_sav = _Wrotesome; - bool _Closef_sav = _Closef; - bool _Set_eback_sav = _Mysb::eback() == &_Mychar; - bool _Set_eback_live = _Mysb::gptr() == &_Mychar; - - _Elem* _Pfirst0 = _Mysb::pbase(); - _Elem* _Pnext0 = _Mysb::pptr(); - _Elem* _Pend = _Mysb::epptr(); - _Elem* _Gfirst0 = _Mysb::eback(); - _Elem* _Gnext0 = _Mysb::gptr(); - _Elem* _Gend = _Mysb::egptr(); - - // reinitialize *this - _Init(_Right._Myfile, _Right._Myfile ? _Openfl : _Newfl); - _Mysb::setp(_Right.pbase(), _Right.pptr(), _Right.epptr()); - if (_Right.eback() != &_Right._Mychar) { - _Mysb::setg(_Right.eback(), _Right.gptr(), _Right.egptr()); - } else if (_Right.gptr() != &_Right._Mychar) { - _Mysb::setg(&_Mychar, &_Mychar + 1, &_Mychar + 1); - } else { - _Mysb::setg(&_Mychar, &_Mychar, &_Mychar + 1); - } - - _Pcvt = _Right._Pcvt; - _State = _Right._State; - _Wrotesome = _Right._Wrotesome; - _Closef = _Right._Closef; - - // reinitialize _Right - _Right._Init(_Myfile_sav, _Myfile_sav ? _Openfl : _Newfl); - _Right.setp(_Pfirst0, _Pnext0, _Pend); - if (!_Set_eback_sav) { - _Right.setg(_Gfirst0, _Gnext0, _Gend); - } else if (!_Set_eback_live) { - _Right.setg(&_Right._Mychar, &_Right._Mychar + 1, &_Right._Mychar + 1); - } else { - _Right.setg(&_Right._Mychar, &_Right._Mychar, &_Right._Mychar + 1); - } - - _Right._Pcvt = _Pcvt_sav; - _Right._State = _State_sav; - _Right._Wrotesome = _Wrotesome_sav; - _Right._Closef = _Closef_sav; - - // swap ancillary data - _STD swap(_Set_eback, _Right._Set_eback); - _STD swap(_Set_egptr, _Right._Set_egptr); - - _STD swap(_Mychar, _Right._Mychar); - _STD swap(_Mysb::_Plocale, _Right._Plocale); - } - } - - basic_filebuf(const basic_filebuf&) = delete; - basic_filebuf& operator=(const basic_filebuf&) = delete; - - enum _Initfl { // reasons for a call to _Init - _Newfl, - _Openfl, - _Closefl - }; - - _NODISCARD bool is_open() const noexcept /* strengthened */ { - return static_cast(_Myfile); - } - - basic_filebuf* open(const char* _Filename, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { - // _Prot is an extension - if (_Myfile) { - return nullptr; - } - - const auto _File = _Fiopen(_Filename, _Mode, _Prot); - if (!_File) { - return nullptr; // open failed - } - - _Init(_File, _Openfl); - _Initcvt(_STD use_facet<_Cvt>(_Mysb::getloc())); - return this; // open succeeded - } - - basic_filebuf* open(const string& _Str, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { - // _Prot is an extension - return open(_Str.c_str(), _Mode, _Prot); - } - -#if _HAS_OLD_IOSTREAMS_MEMBERS - basic_filebuf* open(const char* _Filename, ios_base::open_mode _Mode) { - return open(_Filename, static_cast(_Mode)); - } -#endif // _HAS_OLD_IOSTREAMS_MEMBERS - - basic_filebuf* open(const wchar_t* _Filename, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { - // in standard as const std::filesystem::path::value_type *; _Prot is an extension - if (_Myfile) { - return nullptr; - } - - const auto _File = _Fiopen(_Filename, _Mode, _Prot); - if (!_File) { - return nullptr; // open failed - } - - _Init(_File, _Openfl); - _Initcvt(_STD use_facet<_Cvt>(_Mysb::getloc())); - return this; // open succeeded - } - - basic_filebuf* open(const wstring& _Str, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { - // extension - return open(_Str.c_str(), _Mode, _Prot); - } - -#if _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM - template - basic_filebuf* open( - const _Identity_t<_Path_ish>& _Path, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { - // _Prot is an extension - return open(_Path.c_str(), _Mode, _Prot); - } -#endif // _FSTREAM_SUPPORTS_EXPERIMENTAL_FILESYSTEM - -#if _HAS_CXX17 - template - basic_filebuf* open( - const _Identity_t<_Path_ish>& _Path, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { - // _Prot is an extension - return open(_Path.c_str(), _Mode, _Prot); - } -#endif // _HAS_CXX17 - -#if _HAS_OLD_IOSTREAMS_MEMBERS - basic_filebuf* open(const wchar_t* _Filename, ios_base::open_mode _Mode) { - // in standard as const std::filesystem::path::value_type * - return open(_Filename, static_cast(_Mode)); - } -#endif // _HAS_OLD_IOSTREAMS_MEMBERS - -#ifdef _CRTBLD - basic_filebuf* open( - const unsigned short* _Filename, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { - // in standard as const std::filesystem::path::value_type *; _Prot is an extension - if (_Myfile) { - return nullptr; - } - - const auto _File = _Fiopen(_Filename, _Mode, _Prot); - if (!_File) { - return nullptr; // open failed - } - - _Init(_File, _Openfl); - _Initcvt(_STD use_facet<_Cvt>(_Mysb::getloc())); - return this; // open succeeded - } - -#if _HAS_OLD_IOSTREAMS_MEMBERS - basic_filebuf* open(const unsigned short* _Filename, ios_base::open_mode _Mode) { - // in standard as const std::filesystem::path::value_type * - return open(_Filename, static_cast(_Mode)); - } -#endif // _HAS_OLD_IOSTREAMS_MEMBERS -#endif // _CRTBLD - - basic_filebuf* close() { - basic_filebuf* _Ans; - if (_Myfile) { // put any homing sequence and close file - _Reset_back(); // revert from _Mychar buffer - - _Ans = this; - if (!_Endwrite()) { - _Ans = nullptr; - } - - if (_CSTD fclose(_Myfile) != 0) { - _Ans = nullptr; - } - } else { - _Ans = nullptr; - } - - _Init(nullptr, _Closefl); - return _Ans; - } - - void __CLR_OR_THIS_CALL _Lock() override { // lock file instead of stream buffer - if (_Myfile) { - _CSTD _lock_file(_Myfile); - } - } - - void __CLR_OR_THIS_CALL _Unlock() override { // unlock file instead of stream buffer - if (_Myfile) { - _CSTD _unlock_file(_Myfile); - } - } - -protected: - int_type __CLR_OR_THIS_CALL overflow(int_type _Meta = _Traits::eof()) override { // put an element to stream - if (_Traits::eq_int_type(_Traits::eof(), _Meta)) { - return _Traits::not_eof(_Meta); // EOF, return success code - } - - if (_Mysb::pptr() && _Mysb::pptr() < _Mysb::epptr()) { // room in buffer, store it - *_Mysb::_Pninc() = _Traits::to_char_type(_Meta); - return _Meta; - } - - if (!_Myfile) { - return _Traits::eof(); // no open C stream, fail - } - - _Reset_back(); // revert from _Mychar buffer - if (!_Pcvt) { // no codecvt facet, put as is - return _Fputc(_Traits::to_char_type(_Meta), _Myfile) ? _Meta : _Traits::eof(); - } - - // put using codecvt facet - constexpr size_t _Codecvt_temp_buf = 32; - char _Str[_Codecvt_temp_buf]; - const _Elem _Ch = _Traits::to_char_type(_Meta); - const _Elem* _Src; - char* _Dest; - - // test result of converting one element - switch (_Pcvt->out(_State, &_Ch, &_Ch + 1, _Src, _Str, _Str + _Codecvt_temp_buf, _Dest)) { - case codecvt_base::partial: - case codecvt_base::ok: - { // converted something, try to put it out - const auto _Count = static_cast(_Dest - _Str); - if (0 < _Count && _Count != static_cast(_CSTD fwrite(_Str, 1, _Count, _Myfile))) { - return _Traits::eof(); // write failed - } - - _Wrotesome = true; // write succeeded - if (_Src != &_Ch) { - return _Meta; // converted whole element - } - - return _Traits::eof(); // conversion failed - } - - case codecvt_base::noconv: - // no conversion, put as is - return _Fputc(_Ch, _Myfile) ? _Meta : _Traits::eof(); - - default: - return _Traits::eof(); // conversion failed - } - } - - int_type __CLR_OR_THIS_CALL pbackfail(int_type _Meta = _Traits::eof()) override { - // put an element back to stream - if (_Mysb::gptr() && _Mysb::eback() < _Mysb::gptr() - && (_Traits::eq_int_type(_Traits::eof(), _Meta) - || _Traits::eq_int_type(_Traits::to_int_type(_Mysb::gptr()[-1]), - _Meta))) { // just back up position - _Mysb::_Gndec(); - return _Traits::not_eof(_Meta); - } else if (!_Myfile || _Traits::eq_int_type(_Traits::eof(), _Meta)) { - return _Traits::eof(); // no open C stream or EOF, fail - } else if (!_Pcvt && _Ungetc(_Traits::to_char_type(_Meta), _Myfile)) { - return _Meta; // no facet and unget succeeded, return - } else if (_Mysb::gptr() != &_Mychar) { // putback to _Mychar - _Mychar = _Traits::to_char_type(_Meta); - _Set_back(); // switch to _Mychar buffer - return _Meta; - } else { - return _Traits::eof(); // nowhere to put back - } - } - - int_type __CLR_OR_THIS_CALL underflow() override { // get an element from stream, but don't point past it - int_type _Meta; - if (_Mysb::gptr() && _Mysb::gptr() < _Mysb::egptr()) { - return _Traits::to_int_type(*_Mysb::gptr()); // return buffered - } else if (_Traits::eq_int_type(_Traits::eof(), _Meta = uflow())) { - return _Meta; // uflow failed, return EOF - } else { // get a char, don't point past it - pbackfail(_Meta); - return _Meta; - } - } - - int_type __CLR_OR_THIS_CALL uflow() override { // get an element from stream, point past it - if (_Mysb::gptr() && _Mysb::gptr() < _Mysb::egptr()) { - return _Traits::to_int_type(*_Mysb::_Gninc()); // return buffered - } - - if (!_Myfile) { - return _Traits::eof(); // no open C stream, fail - } - - _Reset_back(); // revert from _Mychar buffer - if (!_Pcvt) { // no codecvt facet, just get it - _Elem _Ch; - return _Fgetc(_Ch, _Myfile) ? _Traits::to_int_type(_Ch) : _Traits::eof(); - } - - // build string until codecvt succeeds - string _Str; - - for (;;) { // get using codecvt facet - const char* _Src; - int _Meta = _CSTD fgetc(_Myfile); - - if (_Meta == EOF) { - return _Traits::eof(); // partial char? - } - - _Str.push_back(static_cast(_Meta)); // append byte and convert - - _Elem _Ch; - _Elem* _Dest; - - // test result of converting one element - switch (_Pcvt->in(_State, _Str.data(), _Str.data() + _Str.size(), _Src, &_Ch, &_Ch + 1, _Dest)) { - case codecvt_base::partial: - case codecvt_base::ok: - if (_Dest != &_Ch) { // got an element, put back excess and deliver it - auto _Nleft = _Str.data() + _Str.size() - _Src; - while (0 < _Nleft) { - _CSTD ungetc(_Src[--_Nleft], _Myfile); - } - - return _Traits::to_int_type(_Ch); - } - - _Str.erase(0, static_cast(_Src - _Str.data())); // partial, discard used input - break; - - case codecvt_base::noconv: - // noconv is only possible if _Elem is char, so we can use it directly - return static_cast(_Str.front()); - - default: - return _Traits::eof(); // conversion failed - } - } - } - - streamsize __CLR_OR_THIS_CALL xsgetn(_Elem* _Ptr, streamsize _Count) override { - // get _Count characters from stream - if constexpr (sizeof(_Elem) == 1) { - if (_Count <= 0) { - return 0; - } - - if (_Pcvt) { // if we need a nontrivial codecvt transform, do the default expensive thing - return _Mysb::xsgetn(_Ptr, _Count); - } - - // assuming this is OK because _Ptr + _Count must be valid - auto _Count_s = static_cast(_Count); - const auto _Start_count = _Count; - const auto _Available = static_cast(_Mysb::_Gnavail()); - if (0 < _Available) { // copy from get area - const auto _Read_size = (_STD min)(_Count_s, _Available); - _Traits::copy(_Ptr, _Mysb::gptr(), _Read_size); - _Ptr += _Read_size; - _Count_s -= _Read_size; - _Mysb::gbump(static_cast(_Read_size)); - } - - if (_Myfile) { // open C stream, attempt read - _Reset_back(); // revert from _Mychar buffer - // process in 4k - 1 chunks to avoid tripping over fread's clobber-the-end behavior when - // doing \r\n -> \n translation - constexpr size_t _Read_size = 4095; // _INTERNAL_BUFSIZ - 1 - while (_Read_size < _Count_s) { - const auto _Actual_read = _CSTD fread(_Ptr, sizeof(_Elem), _Read_size, _Myfile); - _Ptr += _Actual_read; - _Count_s -= _Actual_read; - if (_Actual_read != _Read_size) { - return static_cast(_Start_count - _Count_s); - } - } - - if (0 < _Count_s) { - _Count_s -= _CSTD fread(_Ptr, sizeof(_Elem), _Count_s, _Myfile); - } - } - - return static_cast(_Start_count - _Count_s); - } else { // non-chars always get element-by-element processing - return _Mysb::xsgetn(_Ptr, _Count); - } - } - - streamsize __CLR_OR_THIS_CALL xsputn(const _Elem* _Ptr, streamsize _Count) override { - // put _Count characters to stream - if constexpr (sizeof(_Elem) == 1) { - if (_Pcvt) { // if we need a nontrivial codecvt transform, do the default expensive thing - return _Mysb::xsputn(_Ptr, _Count); - } - - const streamsize _Start_count = _Count; - streamsize _Size = _Mysb::_Pnavail(); - if (0 < _Count && 0 < _Size) { // copy to write buffer - if (_Count < _Size) { - _Size = _Count; - } - - _Traits::copy(_Mysb::pptr(), _Ptr, static_cast(_Size)); - _Ptr += _Size; - _Count -= _Size; - _Mysb::pbump(static_cast(_Size)); - } - - if (0 < _Count && _Myfile) { // open C stream, attempt write - _Count -= _CSTD fwrite(_Ptr, sizeof(_Elem), static_cast(_Count), _Myfile); - } - - return _Start_count - _Count; - } else { // non-chars always get element-by-element processing - return _Mysb::xsputn(_Ptr, _Count); - } - } - - pos_type __CLR_OR_THIS_CALL seekoff(off_type _Off, ios_base::seekdir _Way, - ios_base::openmode = ios_base::in | ios_base::out) override { // change position by _Off - fpos_t _Fileposition; - - if (_Mysb::gptr() == &_Mychar // something putback - && _Way == ios_base::cur // a relative seek - && !_Pcvt) { // not converting - _Off -= static_cast(sizeof(_Elem)); // back up over _Elem bytes - } - - if (!_Myfile || !_Endwrite() - || ((_Off != 0 || _Way != ios_base::cur) && _CSTD _fseeki64(_Myfile, _Off, _Way) != 0) - || _CSTD fgetpos(_Myfile, &_Fileposition) != 0) { - return pos_type{off_type{-1}}; // report failure - } - - _Reset_back(); // revert from _Mychar buffer, discarding any putback - return pos_type{_State, _Fileposition}; // return new position - } - - pos_type __CLR_OR_THIS_CALL seekpos(pos_type _Pos, ios_base::openmode = ios_base::in | ios_base::out) override { - // change position to _Pos - off_type _Off = static_cast(_Pos); - - if (!_Myfile || !_Endwrite() || _CSTD fsetpos(_Myfile, &_Off) != 0) { - return pos_type{off_type{-1}}; // report failure - } - - _State = _Pos.state(); - _Reset_back(); // revert from _Mychar buffer, discarding any putback - return pos_type{_State, _Off}; // return new position - } - - _Mysb* __CLR_OR_THIS_CALL setbuf(_Elem* _Buffer, streamsize _Count) override { // offer _Buffer to C stream - int _Mode; - if (!_Buffer && _Count == 0) { - _Mode = _IONBF; - } else { - _Mode = _IOFBF; - } - - const size_t _Size = static_cast(_Count) * sizeof(_Elem); - - if (!_Myfile || _CSTD setvbuf(_Myfile, reinterpret_cast(_Buffer), _Mode, _Size) != 0) { - return nullptr; // failed - } - - // new buffer, reinitialize pointers - _Init(_Myfile, _Openfl); - return this; - } - - int __CLR_OR_THIS_CALL sync() override { // synchronize C stream with external file - if (!_Myfile || _Traits::eq_int_type(_Traits::eof(), overflow()) || 0 <= _CSTD fflush(_Myfile)) { - return 0; - } - - return -1; - } - - void __CLR_OR_THIS_CALL imbue(const locale& _Loc) override { - // set locale to argument (capture nontrivial codecvt facet) - _Initcvt(_STD use_facet<_Cvt>(_Loc)); - } - - void _Init(FILE* _File, _Initfl _Which) noexcept { // initialize to C stream _File after {new, open, close} - using _State_type = typename _Traits::state_type; - - __PURE_APPDOMAIN_GLOBAL static _State_type _Stinit; // initial state - - _Closef = _Which == _Openfl; - _Wrotesome = false; - - _Mysb::_Init(); // initialize stream buffer base object - - if (_File && sizeof(_Elem) == 1) { // point inside C stream with [first, first + count) buffer - _Elem** _Pb = nullptr; - _Elem** _Pn = nullptr; - int* _Nr = nullptr; - - ::_get_stream_buffer_pointers( - _File, reinterpret_cast(&_Pb), reinterpret_cast(&_Pn), &_Nr); - int* _Nw = _Nr; - - _Mysb::_Init(_Pb, _Pn, _Nr, _Pb, _Pn, _Nw); - } - - _Myfile = _File; - _State = _Stinit; - _Pcvt = nullptr; // pointer to codecvt facet - } - - bool _Endwrite() { // put shift to initial conversion state, as needed - if (!_Pcvt || !_Wrotesome) { - return true; - } - - // may have to put - if (_Traits::eq_int_type(_Traits::eof(), overflow())) { - return false; - } - - constexpr size_t _Codecvt_temp_buf = 32; - char _Str[_Codecvt_temp_buf]; - char* _Dest; - switch (_Pcvt->unshift(_State, _Str, _Str + _Codecvt_temp_buf, _Dest)) { // test result of homing conversion - case codecvt_base::ok: - _Wrotesome = false; // homed successfully - _FALLTHROUGH; - - case codecvt_base::partial: - { // put any generated bytes - const auto _Count = static_cast(_Dest - _Str); - if (0 < _Count && _Count != static_cast(_CSTD fwrite(_Str, 1, _Count, _Myfile))) { - return false; // write failed - } - - return !_Wrotesome; - } - - case codecvt_base::noconv: - _Wrotesome = false; // homed successfully - return true; // nothing else to do - - default: - return false; // conversion failed - } - } - - void _Initcvt(const _Cvt& _Newcvt) noexcept { // initialize codecvt pointer - if (_Newcvt.always_noconv()) { - _Pcvt = nullptr; // nothing to do - } else { // set up for nontrivial codecvt facet - _Pcvt = _STD addressof(_Newcvt); - _Mysb::_Init(); // reset any buffering - } - } - -private: - const _Cvt* _Pcvt; // pointer to codecvt facet (may be null) - _Elem _Mychar; // putback character, when _Ungetc fails - bool _Wrotesome; // true if homing sequence may be needed - typename _Traits::state_type _State; // current conversion state - bool _Closef; // true if C stream must be closed - FILE* _Myfile; // pointer to C stream - - void _Reset_back() noexcept { // restore buffer after putback - if (_Mysb::eback() == &_Mychar) { - _Mysb::setg(_Set_eback, _Set_eback, _Set_egptr); - } - } - - void _Set_back() noexcept { // set up putback area - if (_Mysb::eback() != &_Mychar) { // save current get buffer - _Set_eback = _Mysb::eback(); - _Set_egptr = _Mysb::egptr(); - } - _Mysb::setg(&_Mychar, &_Mychar, &_Mychar + 1); - } - - _Elem* _Set_eback; // saves eback() during one-element putback - _Elem* _Set_egptr; // saves egptr() -}; - -_EXPORT_STD template -void swap(basic_filebuf<_Elem, _Traits>& _Left, basic_filebuf<_Elem, _Traits>& _Right) noexcept /* strengthened */ { - _Left.swap(_Right); -} _EXPORT_STD template class basic_ifstream : public basic_istream<_Elem, _Traits> { // input stream associated with a C stream diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index 612fae9502..0bc5e0c963 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -7,9 +7,11 @@ // "__msvc_all_public_headers.hpp", // for testing, not production "__msvc_chrono.hpp", "__msvc_cxx_stdatomic.hpp", + "__msvc_filebuf.hpp", "__msvc_format_ucd_tables.hpp", "__msvc_int128.hpp", "__msvc_iter_core.hpp", + "__msvc_print.hpp", "__msvc_sanitizer_annotate_container.hpp", "__msvc_system_error_abi.hpp", "__msvc_tzdb.hpp", @@ -88,6 +90,7 @@ "numeric", "optional", "ostream", + "print", "queue", "random", "ranges", diff --git a/stl/inc/ostream b/stl/inc/ostream index cc112046fe..7ac90ecf30 100644 --- a/stl/inc/ostream +++ b/stl/inc/ostream @@ -10,6 +10,12 @@ #if _STL_COMPILER_PREPROCESSOR #include +#if _HAS_CXX23 +#include <__msvc_filebuf.hpp> +#include <__msvc_print.hpp> +#include +#endif // _HAS_CXX23 + #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) #pragma warning(disable : _STL_DISABLED_WARNINGS) @@ -1076,6 +1082,212 @@ basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& _Ostr, // display error code return _Ostr << _Errcode.category().name() << ':' << _Errcode.value(); } + +#ifdef __cpp_lib_print +#ifdef _CPPRTTI +template +ios_base::iostate _Print_noformat_nonunicode(ostream& _Ostr, const string_view _Str) { + // *LOCKED* + // + // This function is called from a context in which an ostream::sentry has been + // constructed. + ios_base::iostate _State = ios_base::goodbit; + + _TRY_IO_BEGIN + const auto _Characters_written = _Ostr.rdbuf()->sputn(_Str.data(), static_cast(_Str.size())); + const bool _Was_insertion_successful = static_cast(_Characters_written) == _Str.size(); + if (!_Was_insertion_successful) [[unlikely]] { + _State |= ios_base::badbit; + } + _CATCH_IO_(ios_base, _Ostr) + + return _State; +} + +template +void _Vprint_nonunicode_impl( + const _Add_newline _Add_nl, ostream& _Ostr, const string_view _Fmt_str, const format_args _Fmt_args) { + const ostream::sentry _Ok(_Ostr); + ios_base::iostate _State = ios_base::goodbit; + + if (!_Ok) [[unlikely]] { + _State |= ios_base::badbit; + } else [[likely]] { + // This is intentionally kept outside of the try/catch block in _Print_noformat_nonunicode() + // (see N4928 [ostream.formatted.print]/3.2). + string _Output_str = _STD vformat(_Ostr.getloc(), _Fmt_str, _Fmt_args); + if (_Add_nl == _Add_newline::_Yes) { + _Output_str.push_back('\n'); + } + + _State |= _STD _Print_noformat_nonunicode(_Ostr, _Output_str); + } + + _Ostr.setstate(_State); +} + +template +ios_base::iostate _Print_noformat_unicode(ostream& _Ostr, const string_view _Str) { + // *LOCKED* + // + // This function is called from a context in which an ostream::sentry has been + // constructed. + ios_base::iostate _State = ios_base::goodbit; + + // The std::ostream& overload of vprint_unicode() expects you to determine whether the stream refers + // to a unicode console or not based solely on the std::ostream& provided. That class simply doesn't + // provide enough information to know this in every case. The best we can do (without breaking ABI) + // is check if the underlying std::streambuf object of the std::ostream& actually refers to a std::filebuf + // object. This requires the use of a dynamic_cast. (This is also why the std::ostream& overloads of + // std::print() et al. are deleted when RTTI is disabled.) + streambuf* const _Streambuf = _Ostr.rdbuf(); + const auto _Filebuf = dynamic_cast<_Filebuf_type*>(_Streambuf); + + // If we got nullptr, then it probably isn't being used for a unicode console... + if (_Filebuf == nullptr) { + _State |= _STD _Print_noformat_nonunicode(_Ostr, _Str); + return _State; + } + + FILE* const _File_stream = _Filebuf->_Myfile; + const __std_unicode_console_retrieval_result _Unicode_console_retrieval_result{ + __std_get_unicode_console_handle_from_file_stream(_File_stream)}; + + // See the documentation for __std_unicode_console_retrieval_result to understand why we do this. + bool _Is_unicode_console; + +#pragma warning(push) +#pragma warning(disable : 4061) // enumerator not explicitly handled by switch label + switch (_Unicode_console_retrieval_result._Error) { + case __std_win_error::_Success: + _Is_unicode_console = true; + break; + + case __std_win_error::_File_not_found: + _Is_unicode_console = false; + break; + + case __std_win_error::_Not_supported: + [[unlikely]] return _State; + + default: + [[unlikely]] return ios_base::failbit; + } +#pragma warning(pop) + + if (_Is_unicode_console) { + _TRY_IO_BEGIN + const bool _Was_flush_successful = _Ostr.rdbuf()->pubsync() != -1; + if (!_Was_flush_successful) [[unlikely]] { + _State |= ios_base::badbit; + return _State; + } + + const __std_win_error _Unicode_console_print_result = + __std_print_to_unicode_console(_Unicode_console_retrieval_result._Console_handle, _Str.data(), _Str.size()); + if (_Unicode_console_print_result != __std_win_error::_Success) [[unlikely]] { + _State |= ios_base::badbit; + } + _CATCH_IO_(ios_base, _Ostr) + } else { + _State |= _STD _Print_noformat_nonunicode(_Ostr, _Str); + } + + return _State; +} + +template +void _Vprint_unicode_impl( + const _Add_newline _Add_nl, ostream& _Ostr, const string_view _Fmt_str, const format_args _Fmt_args) { + const ostream::sentry _Ok(_Ostr); + ios_base::iostate _State = ios_base::goodbit; + + if (!_Ok) [[unlikely]] { + _State |= ios_base::badbit; + } else [[likely]] { + // This is intentionally kept outside of the try/catch block in _Print_noformat_unicode() + // (see N4928 [ostream.formatted.print]/3.2). + string _Output_str = _STD vformat(_Ostr.getloc(), _Fmt_str, _Fmt_args); + if (_Add_nl == _Add_newline::_Yes) { + _Output_str.push_back('\n'); + } + + _State |= _STD _Print_noformat_unicode(_Ostr, _Output_str); + } + + _Ostr.setstate(_State); +} + +template +void _Print_noformat(ostream& _Ostr, const string_view _Str) { + const ostream::sentry _Ok(_Ostr); + ios_base::iostate _State = ios_base::goodbit; + + if (!_Ok) [[unlikely]] { + _State |= ios_base::badbit; + } else [[likely]] { + if constexpr (_STD _Is_ordinary_literal_encoding_utf8()) { + _State |= _STD _Print_noformat_unicode(_Ostr, _Str); + } else { + _State |= _STD _Print_noformat_nonunicode(_Ostr, _Str); + } + } + + _Ostr.setstate(_State); +} + +template +void _Print_impl(const _Add_newline _Add_nl, ostream& _Ostr, const format_string<_Types...> _Fmt, _Types&&... _Args) { + constexpr bool _Has_format_args = sizeof...(_Types) > 0; + + // This is intentionally kept outside of the try/catch block in _Print_noformat_*() + // (see N4928 [ostream.formatted.print]/3.2). + + if constexpr (_Has_format_args) { + if constexpr (_STD _Is_ordinary_literal_encoding_utf8()) { + _STD _Vprint_unicode_impl( + _Add_nl, _Ostr, _Fmt.get(), _STD make_format_args(_STD forward<_Types>(_Args)...)); + } else { + _STD _Vprint_nonunicode_impl( + _Add_nl, _Ostr, _Fmt.get(), _STD make_format_args(_STD forward<_Types>(_Args)...)); + } + } else { + const string _Unescaped_str{_Unescape_braces(_Add_nl, _Fmt.get())}; + _STD _Print_noformat(_Ostr, _Unescaped_str); + } +} + +_EXPORT_STD template +void print(ostream& _Ostr, const format_string<_Types...> _Fmt, _Types&&... _Args) { + _STD _Print_impl(_Add_newline::_Nope, _Ostr, _Fmt, _STD forward<_Types>(_Args)...); +} + +_EXPORT_STD template +void println(ostream& _Ostr, const format_string<_Types...> _Fmt, _Types&&... _Args) { + _STD _Print_impl(_Add_newline::_Yes, _Ostr, _Fmt, _STD forward<_Types>(_Args)...); +} + +_EXPORT_STD template // improves throughput, see GH-2329 +void vprint_unicode(ostream& _Ostr, const string_view _Fmt_str, const format_args _Fmt_args) { + _STD _Vprint_unicode_impl(_Add_newline::_Nope, _Ostr, _Fmt_str, _Fmt_args); +} + +_EXPORT_STD template // improves throughput, see GH-2329 +void vprint_nonunicode(ostream& _Ostr, const string_view _Fmt_str, const format_args _Fmt_args) { + _STD _Vprint_nonunicode_impl(_Add_newline::_Nope, _Ostr, _Fmt_str, _Fmt_args); +} +#else // ^^^ _CPPRTTI / !_CPPRTTI vvv +_EXPORT_STD template +void print(ostream&, format_string<_Types...>, _Types&&...) = delete; // requires /GR option + +_EXPORT_STD template +void println(ostream&, format_string<_Types...>, _Types&&...) = delete; // requires /GR option + +_EXPORT_STD void vprint_unicode(ostream&, string_view, format_args) = delete; // requires /GR option +_EXPORT_STD void vprint_nonunicode(ostream&, string_view, format_args) = delete; // requires /GR option +#endif // !_CPPRTTI +#endif // __cpp_lib_print + _STD_END #pragma pop_macro("new") diff --git a/stl/inc/print b/stl/inc/print new file mode 100644 index 0000000000..184392ccb3 --- /dev/null +++ b/stl/inc/print @@ -0,0 +1,171 @@ +// print standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _PRINT_ +#define _PRINT_ +#include +#if _STL_COMPILER_PREPROCESSOR +#ifndef __cpp_lib_print +_EMIT_STL_WARNING(STL4038, "The contents of are available only with C++23 or later."); +#else // ^^^ !defined(__cpp_lib_print) / defined(__cpp_lib_print) vvv + +#include <__msvc_print.hpp> +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +_STD_BEGIN + +inline void _Print_noformat_nonunicode(FILE* const _Stream, const string_view _Str) { + const bool _Was_write_successful = _CSTD fwrite(_Str.data(), 1, _Str.size(), _Stream) == _Str.size(); + + if (!_Was_write_successful) [[unlikely]] { + _Throw_system_error(static_cast(errno)); + } +} + +inline void _Vprint_nonunicode_impl( + const _Add_newline _Add_nl, FILE* const _Stream, const string_view _Fmt_str, const format_args _Fmt_args) { + string _Output_str = _STD vformat(_Fmt_str, _Fmt_args); + if (_Add_nl == _Add_newline::_Yes) { + _Output_str.push_back('\n'); + } + + _STD _Print_noformat_nonunicode(_Stream, _Output_str); +} + +inline void _Print_noformat_unicode(FILE* const _Stream, const string_view _Str) { + const __std_unicode_console_retrieval_result _Unicode_console_retrieval_result{ + __std_get_unicode_console_handle_from_file_stream(_Stream)}; + + // See the documentation for __std_unicode_console_retrieval_result to understand why we do this. + bool _Is_unicode_console; + +#pragma warning(push) +#pragma warning(disable : 4061) // enumerator not explicitly handled by switch label + switch (_Unicode_console_retrieval_result._Error) { + case __std_win_error::_Success: + _Is_unicode_console = true; + break; + + case __std_win_error::_File_not_found: + _Is_unicode_console = false; + break; + + case __std_win_error::_Not_supported: + [[unlikely]] return; + + default: + [[unlikely]] _STD _Throw_system_error_from_std_win_error(_Unicode_console_retrieval_result._Error); + } +#pragma warning(pop) + + if (_Is_unicode_console) { + const bool _Was_flush_successful = _CSTD fflush(_Stream) == 0; + if (!_Was_flush_successful) [[unlikely]] { + _Throw_system_error(static_cast(errno)); + } + + const __std_win_error _Console_print_result = + __std_print_to_unicode_console(_Unicode_console_retrieval_result._Console_handle, _Str.data(), _Str.size()); + if (_Console_print_result != __std_win_error::_Success) [[unlikely]] { + _STD _Throw_system_error_from_std_win_error(_Console_print_result); + } + } else { + _STD _Print_noformat_nonunicode(_Stream, _Str); + } +} + +inline void _Vprint_unicode_impl( + const _Add_newline _Add_nl, FILE* const _Stream, const string_view _Fmt_str, const format_args _Fmt_args) { + string _Output_str = _STD vformat(_Fmt_str, _Fmt_args); + if (_Add_nl == _Add_newline::_Yes) { + _Output_str.push_back('\n'); + } + + _STD _Print_noformat_unicode(_Stream, _Output_str); +} + +template +void _Print_impl( + const _Add_newline _Add_nl, FILE* const _Stream, const format_string<_Types...> _Fmt, _Types&&... _Args) { + constexpr bool _Has_format_args = sizeof...(_Types) > 0; + + if constexpr (_Has_format_args) { + if constexpr (_STD _Is_ordinary_literal_encoding_utf8()) { + _STD _Vprint_unicode_impl( + _Add_nl, _Stream, _Fmt.get(), _STD make_format_args(_STD forward<_Types>(_Args)...)); + } else { + _STD _Vprint_nonunicode_impl( + _Add_nl, _Stream, _Fmt.get(), _STD make_format_args(_STD forward<_Types>(_Args)...)); + } + } else { + const string _Unescaped_str{_Unescape_braces(_Add_nl, _Fmt.get())}; + + if constexpr (_STD _Is_ordinary_literal_encoding_utf8()) { + _STD _Print_noformat_unicode(_Stream, _Unescaped_str); + } else { + _STD _Print_noformat_nonunicode(_Stream, _Unescaped_str); + } + } +} + +_EXPORT_STD template +void print(FILE* const _Stream, const format_string<_Types...> _Fmt, _Types&&... _Args) { + _STD _Print_impl(_Add_newline::_Nope, _Stream, _Fmt, _STD forward<_Types>(_Args)...); +} + +_EXPORT_STD template +void print(const format_string<_Types...> _Fmt, _Types&&... _Args) { + _STD print(stdout, _Fmt, _STD forward<_Types>(_Args)...); +} + +_EXPORT_STD template +void println(FILE* const _Stream, const format_string<_Types...> _Fmt, _Types&&... _Args) { + _STD _Print_impl(_Add_newline::_Yes, _Stream, _Fmt, _STD forward<_Types>(_Args)...); +} + +_EXPORT_STD template +void println(const format_string<_Types...> _Fmt, _Types&&... _Args) { + _STD println(stdout, _Fmt, _STD forward<_Types>(_Args)...); +} + +_EXPORT_STD template // improves throughput, see GH-2329 +void vprint_unicode(FILE* const _Stream, const string_view _Fmt_str, const format_args _Fmt_args) { + _STD _Vprint_unicode_impl(_Add_newline::_Nope, _Stream, _Fmt_str, _Fmt_args); +} + +_EXPORT_STD template // improves throughput, see GH-2329 +void vprint_unicode(const string_view _Fmt_str, const format_args _Fmt_args) { + _STD vprint_unicode(stdout, _Fmt_str, _Fmt_args); +} + +_EXPORT_STD template // improves throughput, see GH-2329 +void vprint_nonunicode(FILE* const _Stream, const string_view _Fmt_str, const format_args _Fmt_args) { + _STD _Vprint_nonunicode_impl(_Add_newline::_Nope, _Stream, _Fmt_str, _Fmt_args); +} + +_EXPORT_STD template // improves throughput, see GH-2329 +void vprint_nonunicode(const string_view _Fmt_str, const format_args _Fmt_args) { + _STD vprint_nonunicode(stdout, _Fmt_str, _Fmt_args); +} + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) + +#endif // __cpp_lib_print +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _PRINT_ diff --git a/stl/inc/system_error b/stl/inc/system_error index 60ccf897eb..04e227fc53 100644 --- a/stl/inc/system_error +++ b/stl/inc/system_error @@ -702,6 +702,30 @@ _EXPORT_STD _NODISCARD inline const error_category& system_category() noexcept { return _Immortalize_memcpy_image<_System_error_category>(); } _STD_END + +#if _HAS_CXX17 +_EXTERN_C +enum class __std_win_error : unsigned long; +_END_EXTERN_C + +_STD_BEGIN +// We would really love to use the proper way of building error_code by specializing +// is_error_code_enum and make_error_code for __std_win_error, but because: +// 1. We would like to keep the definition of __std_win_error in xfilesystem_abi.h +// 2. and xfilesystem_abi.h cannot include +// 3. and specialization of is_error_code_enum and overload of make_error_code +// need to be kept together with the enum (see limerick in N4810 [temp.expl.spec]/7) +// we resort to using this _Make_ec helper. +_NODISCARD inline error_code _Make_ec(__std_win_error _Errno) noexcept { // make an error_code + return {static_cast(_Errno), _STD system_category()}; +} + +[[noreturn]] inline void _Throw_system_error_from_std_win_error(const __std_win_error _Errno) { + _THROW(system_error{_Make_ec(_Errno)}); +} +_STD_END +#endif // _HAS_CXX17 + #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS #pragma warning(pop) diff --git a/stl/inc/xutility b/stl/inc/xutility index ee954bc9c6..8b9046287c 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -4292,10 +4292,6 @@ template requires (!sized_sentinel_for<_Iter1, _Iter2>) inline constexpr bool disable_sized_sentinel_for, move_iterator<_Iter2>> = true; -_EXPORT_STD struct default_sentinel_t {}; - -_EXPORT_STD inline constexpr default_sentinel_t default_sentinel{}; - _EXPORT_STD struct unreachable_sentinel_t; namespace _Unreachable_sentinel_detail { struct _Base { diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index da3870dedf..b0c34181d7 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -326,6 +326,7 @@ // P1951R1 Default Template Arguments For pair's Forwarding Constructor // P1989R2 Range Constructor For string_view // P2077R3 Heterogeneous Erasure Overloads For Associative Containers +// P2093R14 : Formatted Output // P2136R3 invoke_r() // P2164R9 views::enumerate // P2165R4 Compatibility Between tuple, pair, And tuple-like Objects @@ -353,6 +354,7 @@ // P2494R2 Relaxing Range Adaptors To Allow Move-Only Types // P2499R0 string_view Range Constructor Should Be explicit // P2505R5 Monadic Functions For expected +// P2539R4 Synchronizing print() With The Underlying Stream // P2549R1 unexpected::error() // P2652R2 Disallowing User Specialization Of allocator_traits @@ -1724,6 +1726,7 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect #ifdef __cpp_lib_concepts #define __cpp_lib_out_ptr 202106L +#define __cpp_lib_print 202207L #define __cpp_lib_ranges_as_const 202207L #define __cpp_lib_ranges_as_rvalue 202207L #define __cpp_lib_ranges_chunk 202202L diff --git a/stl/modules/std.ixx b/stl/modules/std.ixx index dc43b5722e..5dd3f99d1a 100644 --- a/stl/modules/std.ixx +++ b/stl/modules/std.ixx @@ -84,6 +84,7 @@ export module std; #include #include #include +#include #include #include #include diff --git a/stl/msbuild/stl_base/stl.files.settings.targets b/stl/msbuild/stl_base/stl.files.settings.targets index 977feaf0ea..d9453cc46d 100644 --- a/stl/msbuild/stl_base/stl.files.settings.targets +++ b/stl/msbuild/stl_base/stl.files.settings.targets @@ -166,6 +166,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception $(CrtRoot)\github\stl\src\format.cpp; $(CrtRoot)\github\stl\src\locale0_implib.cpp; $(CrtRoot)\github\stl\src\nothrow.cpp; + $(CrtRoot)\github\stl\src\print.cpp; $(CrtRoot)\github\stl\src\sharedmutex.cpp; $(CrtRoot)\github\stl\src\stacktrace.cpp; $(CrtRoot)\github\stl\src\syserror_import_lib.cpp; diff --git a/stl/src/msvcp_atomic_wait.src b/stl/src/msvcp_atomic_wait.src index 0ea7194ccc..d156a3c4ce 100644 --- a/stl/src/msvcp_atomic_wait.src +++ b/stl/src/msvcp_atomic_wait.src @@ -3,6 +3,10 @@ ; atomic wait satellite DLL definition +; NOTE: "LIBRARYNAME" is just a placeholder. It gets dynamically replaced with the generated +; DLL name during the CMake build. (See the generate_satellite_def() function in +; stl\CMakeLists.txt.) Doing this instead of using, e.g., CMake variable substitution allows +; for file preprocessing via "cl /DLIBRARYNAME=..." LIBRARY LIBRARYNAME EXPORTS diff --git a/stl/src/print.cpp b/stl/src/print.cpp new file mode 100644 index 0000000000..02ae5d6fa4 --- /dev/null +++ b/stl/src/print.cpp @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// print.cpp -- C++23 implementation + +// This must be as small as possible, because its contents are +// injected into the msvcprt.lib and msvcprtd.lib import libraries. +// Do not include or define anything else here. +// In particular, basic_string must not be included here. + +#include <__msvc_print.hpp> +#include +#include +#include +#include +#include + +#include + +_EXTERN_C + +[[nodiscard]] _Success_(return._Error == __std_win_error::_Success) __std_unicode_console_retrieval_result + __stdcall __std_get_unicode_console_handle_from_file_stream(_In_ FILE* const _Stream) noexcept { + if (_Stream == nullptr) [[unlikely]] { + return __std_unicode_console_retrieval_result{._Error = __std_win_error::_Invalid_parameter}; + } + + const int _Fd = _fileno(_Stream); + + if (_Fd == -2) [[unlikely]] { + // According to https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fileno?view=msvc-170 , + // _fileno() returns -2 if _Stream refers to either stdout or stderr and there is no associated output stream. + // In that case, there is also no associated console HANDLE. (We haven't observed this happening in practice.) + return __std_unicode_console_retrieval_result{._Error = __std_win_error::_Not_supported}; + } else if (_Fd == -1) [[unlikely]] { + return __std_unicode_console_retrieval_result{._Error = __std_win_error::_Invalid_parameter}; + } + + const HANDLE _Console_handle = reinterpret_cast(_get_osfhandle(_Fd)); + + if (_Console_handle == INVALID_HANDLE_VALUE) [[unlikely]] { + return __std_unicode_console_retrieval_result{._Error = __std_win_error::_Invalid_parameter}; + } + + // We can check if _Console_handle actually refers to a console or not by checking the + // return value of GetConsoleMode(). + DWORD _Console_mode; + const bool _Is_unicode_console = GetConsoleMode(_Console_handle, &_Console_mode) != 0; + + if (!_Is_unicode_console) { + return __std_unicode_console_retrieval_result{._Error = __std_win_error::_File_not_found}; + } + + return __std_unicode_console_retrieval_result{ + ._Console_handle = static_cast<__std_unicode_console_handle>( + reinterpret_cast<_STD underlying_type_t<__std_unicode_console_handle>>(_Console_handle)), + ._Error = __std_win_error::_Success}; +} + +_END_EXTERN_C + +namespace { + class _Allocated_string { + public: + _Allocated_string() = default; + + explicit _Allocated_string(__crt_unique_heap_ptr&& _Other_str, const size_t _Other_capacity) noexcept + : _Str(_STD move(_Other_str)), _Str_capacity(_Other_capacity) {} + + [[nodiscard]] wchar_t* _Data() const noexcept { + return _Str.get(); + } + + [[nodiscard]] size_t _Capacity() const noexcept { + return _Str_capacity; + } + + void _Reset() noexcept { + _Str.release(); + _Str_capacity = 0; + } + + private: + __crt_unique_heap_ptr _Str; + size_t _Str_capacity = 0; + }; + + template + class _Really_basic_string_view { + public: + _Really_basic_string_view() = default; + + explicit _Really_basic_string_view(const _Char_type* const _Other_str, const size_t _Other_size) noexcept + : _Str(_Other_str), _Str_size(_Other_size) {} + + [[nodiscard]] const _Char_type* _Data() const noexcept { + return _Str; + } + + [[nodiscard]] size_t _Size() const noexcept { + return _Str_size; + } + + [[nodiscard]] bool _Empty() const noexcept { + return _Str_size == 0; + } + + private: + const _Char_type* _Str = nullptr; + size_t _Str_size = 0; + }; + + using _Minimal_string_view = _Really_basic_string_view; + using _Minimal_wstring_view = _Really_basic_string_view; + + [[nodiscard]] _Minimal_string_view _Get_next_utf8_string_segment( + const char* const _Str, const size_t _Str_size) noexcept { + constexpr size_t _Max_str_segment_size = 8192; + + if (_Str_size <= _Max_str_segment_size) [[likely]] { + return _Minimal_string_view{_Str, _Str_size}; + } + + // Let's refer to _Max_str_segment_size as M. + // Now we know _Str_size > M, so we can read _Str[M]. + // We might need to shrink this segment down to M - 3 bytes, in this worst case scenario: + + // Values: [byte1][byte2][byte3] | [byte4] + // Indices: [M - 4][M - 3][M - 2][M - 1] | [ M ] + // Sizes: [M - 3][M - 2][M - 1][ M ] | [M + 1] + // Maximum segment boundary ^ + + for (size_t _Shrink = 0; _Shrink < 3; ++_Shrink) { + const size_t _Kx = _Max_str_segment_size - _Shrink; // consider a segment of _Kx bytes + + // The first byte after the segment is at index _Kx, which we can read (see above). + // If _Str[_Kx] is a non-trailing byte, then it's the beginning of a code point. + const bool _Trailing_byte = (static_cast(_Str[_Kx]) >> 6) == 0b10; + + if (!_Trailing_byte) { + return _Minimal_string_view{_Str, _Kx}; // found a boundary between code points + } + } + + return _Minimal_string_view{_Str, _Max_str_segment_size - 3}; // worst case scenario + } + + class _Transcode_result { + public: + _Transcode_result() noexcept : _Transcoded_str(), _Successful(true) {} + + _Transcode_result(_Minimal_wstring_view _Result_str) noexcept + : _Transcoded_str(_Result_str), _Successful(true) {} + + _Transcode_result(__std_win_error _Result_error) noexcept : _Win_error(_Result_error), _Successful(false) {} + + [[nodiscard]] bool _Has_value() const noexcept { + return _Successful; + } + + [[nodiscard]] _Minimal_wstring_view _Value() const noexcept { + return _Transcoded_str; + } + + [[nodiscard]] __std_win_error _Error() const noexcept { + return _Win_error; + } + + private: + union { + _Minimal_wstring_view _Transcoded_str; + __std_win_error _Win_error; + }; + + bool _Successful; + }; + + [[nodiscard]] _Transcode_result _Transcode_utf8_string( + _Allocated_string& _Dst_str, const _Minimal_string_view _Src_str) noexcept { + // MultiByteToWideChar() fails if strLength == 0. + if (_Src_str._Empty()) [[unlikely]] { + return {}; + } + + // For vprint_unicode(), N4928 [ostream.formatted.print]/4 suggests replacing invalid code units with U+FFFD. + // This is done automatically by MultiByteToWideChar(), so long as we do not use the MB_ERR_INVALID_CHARS flag. + // We transcode up to 8,192 bytes per segment, which easily fits in an int. + const int32_t _Num_chars_required = + MultiByteToWideChar(CP_UTF8, 0, _Src_str._Data(), static_cast(_Src_str._Size()), nullptr, 0); + + if (_Num_chars_required == 0) [[unlikely]] { + return static_cast<__std_win_error>(GetLastError()); + } + + if (static_cast(_Num_chars_required) > _Dst_str._Capacity()) { + _Dst_str._Reset(); + + __crt_unique_heap_ptr _Wide_str{_malloc_crt_t(wchar_t, _Num_chars_required)}; + if (!_Wide_str) [[unlikely]] { + return __std_win_error::_Not_enough_memory; + } + + _Dst_str = _Allocated_string{_STD move(_Wide_str), static_cast(_Num_chars_required)}; + } + + const int32_t _Conversion_result = MultiByteToWideChar(CP_UTF8, 0, _Src_str._Data(), + static_cast(_Src_str._Size()), _Dst_str._Data(), static_cast(_Dst_str._Capacity())); + + if (_Conversion_result == 0) [[unlikely]] { + // This shouldn't happen... + _CSTD abort(); + } + + return _Minimal_wstring_view{_Dst_str._Data(), static_cast(_Conversion_result)}; + } + + [[nodiscard]] __std_win_error _Write_console( + const HANDLE _Console_handle, const _Minimal_wstring_view _Wide_str) noexcept { + const BOOL _Write_result = + WriteConsoleW(_Console_handle, _Wide_str._Data(), static_cast(_Wide_str._Size()), nullptr, nullptr); + + if (!_Write_result) [[unlikely]] { + return static_cast<__std_win_error>(GetLastError()); + } + + return __std_win_error::_Success; + } +} // unnamed namespace + +_EXTERN_C + +[[nodiscard]] _Success_(return == __std_win_error::_Success) __std_win_error + __stdcall __std_print_to_unicode_console(_In_ const __std_unicode_console_handle _Console_handle, + _In_reads_(_Str_size) const char* const _Str, _In_ const size_t _Str_size) noexcept { + if (_Console_handle == __std_unicode_console_handle::_Invalid || _Str == nullptr) [[unlikely]] { + return __std_win_error::_Invalid_parameter; + } + + const HANDLE _Actual_console_handle = reinterpret_cast(_Console_handle); + + // We transcode in fairly large segments of 8,192 bytes per segment, + // so one iteration should handle the vast majority of strings. + const char* _Remaining_str = _Str; + size_t _Remaining_str_size = _Str_size; + + _Minimal_string_view _Curr_str_segment{}; + _Allocated_string _Allocated_str{}; + _Transcode_result _Transcoded_str{}; + + while (true) { + _Curr_str_segment = _Get_next_utf8_string_segment(_Remaining_str, _Remaining_str_size); + _Transcoded_str = _Transcode_utf8_string(_Allocated_str, _Curr_str_segment); + + if (!_Transcoded_str._Has_value()) [[unlikely]] { + return _Transcoded_str._Error(); + } + + const __std_win_error _Write_result = _Write_console(_Actual_console_handle, _Transcoded_str._Value()); + + if (_Write_result != __std_win_error::_Success) [[unlikely]] { + return _Write_result; + } + + _Remaining_str_size -= _Curr_str_segment._Size(); + + if (_Remaining_str_size == 0) { + return __std_win_error::_Success; + } + + _Remaining_str += _Curr_str_segment._Size(); + } +} + +_END_EXTERN_C diff --git a/tests/std/include/temp_file_name.hpp b/tests/std/include/temp_file_name.hpp new file mode 100644 index 0000000000..79991f56f2 --- /dev/null +++ b/tests/std/include/temp_file_name.hpp @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include +#include + +[[nodiscard]] inline std::string temp_file_name() { + std::string ret{"temp_file_"}; + std::uniform_int_distribution dist{0, 15}; + std::random_device rd; + + for (int i = 0; i < 64; ++i) { // 64 hexits = 256 bits of entropy + ret.push_back("0123456789ABCDEF"[dist(rd)]); + } + + ret += ".tmp"; + + return ret; +} diff --git a/tests/std/include/test_header_units_and_modules.hpp b/tests/std/include/test_header_units_and_modules.hpp index 9b73391c18..51e07d54d2 100644 --- a/tests/std/include/test_header_units_and_modules.hpp +++ b/tests/std/include/test_header_units_and_modules.hpp @@ -492,6 +492,16 @@ void test_ostream() { assert(os.rdbuf() == nullptr); } +void test_print() { + using namespace std; + puts("Testing ."); + println("Hello, world!"); + +#ifdef _CPPRTTI + println(cout, "The answer to life, the universe, and everything: {}", 42); +#endif // _CPPRTTI +} + void test_queue() { using namespace std; puts("Testing ."); @@ -1093,6 +1103,7 @@ void all_cpp_header_tests() { test_numeric(); test_optional(); test_ostream(); + test_print(); test_queue(); test_random(); test_ranges(); diff --git a/tests/std/test.lst b/tests/std/test.lst index f15f54508b..1a916d8d64 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -549,6 +549,7 @@ tests\P1682R3_to_underlying tests\P1899R3_views_stride tests\P1899R3_views_stride_death tests\P1951R1_default_arguments_pair_forward_ctor +tests\P2093R14_formatted_output tests\P2136R3_invoke_r tests\P2162R2_std_visit_for_derived_classes_from_variant tests\P2164R9_views_enumerate diff --git a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc index ad8b6c8ab9..585535d3ca 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc +++ b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc @@ -48,6 +48,7 @@ "numeric", "optional", "ostream", + "print", "queue", "random", "ranges", diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index e9ec1a36cf..c82a77835a 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -54,6 +54,7 @@ import ; import ; import ; import ; +import ; import ; import ; import ; diff --git a/tests/std/tests/P2093R14_formatted_output/env.lst b/tests/std/tests/P2093R14_formatted_output/env.lst new file mode 100644 index 0000000000..0f108cca75 --- /dev/null +++ b/tests/std/tests/P2093R14_formatted_output/env.lst @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_latest_matrix.lst +RUNALL_CROSSLIST +PM_CL="" +PM_CL="/utf-8" diff --git a/tests/std/tests/P2093R14_formatted_output/test.cpp b/tests/std/tests/P2093R14_formatted_output/test.cpp new file mode 100644 index 0000000000..92eb088494 --- /dev/null +++ b/tests/std/tests/P2093R14_formatted_output/test.cpp @@ -0,0 +1,703 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "temp_file_name.hpp" + +using namespace std; + +namespace test { + class win_console { + public: + win_console() { + // There's a trick you can do to create a FILE stream for a Win32 console: + // + // 1. Call _open_osfhandle() to create a file descriptor for the console screen + // buffer handle. + // + // 2. Call _fdopen() to get a FILE* for the file descriptor. + constexpr DWORD screen_buffer_access = GENERIC_READ | GENERIC_WRITE; + console_handle = + CreateConsoleScreenBuffer(screen_buffer_access, 0, nullptr, CONSOLE_TEXTMODE_BUFFER, nullptr); + + if (console_handle == INVALID_HANDLE_VALUE) [[unlikely]] { + return; + } + + const int console_fd = _open_osfhandle(reinterpret_cast(console_handle), _O_TEXT); + + if (console_fd == -1) [[unlikely]] { + return; + } + + file_stream_ptr = _fdopen(console_fd, "w"); + } + + ~win_console() { + delete_console(); + } + + win_console(const win_console&) = delete; + win_console& operator=(const win_console&) = delete; + + win_console(win_console&& rhs) noexcept + : console_handle(rhs.console_handle), file_stream_ptr(rhs.file_stream_ptr) { + rhs.console_handle = nullptr; + rhs.file_stream_ptr = nullptr; + } + + win_console& operator=(win_console&& rhs) noexcept { + delete_console(); + + console_handle = rhs.console_handle; + rhs.console_handle = nullptr; + + file_stream_ptr = rhs.file_stream_ptr; + rhs.file_stream_ptr = nullptr; + + return *this; + } + + FILE* get_file_stream() const { + assert(is_console_valid()); + return file_stream_ptr; + } + + wstring get_console_line(const size_t line_number) const { + // We use the ReadConsoleOutputCharacterW() function to read lines of text which were written to + // the console. The neat thing here is that if we write to the console using the FILE* returned by + // win_console::get_file_stream() with std::print(), we can still use ReadConsoleOutputCharacterW() + // to get that text! This allows us to verify the output being written to the console and check for, + // e.g., invalid code point replacement. + const size_t line_char_width = get_line_character_width(); + const COORD read_coords{.X = 0, .Y = static_cast(line_number)}; + + wstring output_str; + output_str.resize_and_overwrite( + line_char_width, [this, read_coords](wchar_t* const dest_ptr, const size_t allocated_size) { + DWORD num_chars_read; + const BOOL read_output_result = ReadConsoleOutputCharacterW( + console_handle, dest_ptr, static_cast(allocated_size), read_coords, &num_chars_read); + + assert(read_output_result && "ERROR: ReadConsoleOutputCharacterW() failed!"); + return num_chars_read; + }); + + // By default, Windows fills console character cells with space characters. We want to remove + // those space characters which appear after the user's text. + const size_t lastValidChar = output_str.find_last_not_of(' '); + + if (lastValidChar == wstring::npos) [[unlikely]] { + output_str.clear(); + } else [[likely]] { + output_str = output_str.substr(0, lastValidChar + 1); + } + + return output_str; + } + + private: + void delete_console() { + if (is_console_valid()) [[likely]] { + // According to the MSDN, we don't call CloseHandle() on handles passed to _open_osfhandle(), + // and we don't call _close() on file descriptors passed to _fdopen(). So, our only clean-up + // task is to call fclose(). + fclose(file_stream_ptr); + + console_handle = nullptr; + file_stream_ptr = nullptr; + } + } + + size_t get_line_character_width() const { + CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info; + + { + const BOOL get_screen_buffer_info_result = + GetConsoleScreenBufferInfo(console_handle, &screen_buffer_info); + assert(get_screen_buffer_info_result && "ERROR: GetConsoleScreenBufferInfo() failed!"); + } + + return static_cast(screen_buffer_info.dwSize.X); + } + + bool is_console_valid() const { + return console_handle != nullptr && console_handle != INVALID_HANDLE_VALUE && file_stream_ptr != nullptr; + } + + private: + HANDLE console_handle; + FILE* file_stream_ptr; + }; + + // We use Windows semaphores to synchronize the parent and child processes. + class win_semaphore { + public: + // Construct a new semaphore in the parent process. + win_semaphore() { + SECURITY_ATTRIBUTES semaphore_attributes{ + .nLength = sizeof(SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE, // The child process will inherit this handle. + }; + m_handle = CreateSemaphoreW(&semaphore_attributes, 0, 1, nullptr); + assert(m_handle != nullptr); + } + + // Construct an inherited semaphore in the child process. + explicit win_semaphore(const uintptr_t val) : m_handle(reinterpret_cast(val)) { + assert(m_handle != nullptr); + } + + win_semaphore(const win_semaphore&) = delete; + win_semaphore& operator=(const win_semaphore&) = delete; + + ~win_semaphore() { + const BOOL close_handle_succeeded = CloseHandle(m_handle); + assert(close_handle_succeeded); + } + + // The parent process has to tell the child process what its inherited handles are. + [[nodiscard]] uintptr_t to_uintptr() const { + return reinterpret_cast(m_handle); + } + + // win_semaphore imitates std::counting_semaphore's interface with release() and acquire(). + void release() { + const BOOL release_semaphore_succeeded = ReleaseSemaphore(m_handle, 1, nullptr); + assert(release_semaphore_succeeded); + } + + void acquire() { + const DWORD wait_result = WaitForSingleObject(m_handle, INFINITE); + assert(wait_result == WAIT_OBJECT_0); + } + + private: + HANDLE m_handle; + }; +} // namespace test + +const locale& get_utf8_locale() { +#pragma warning(push) +#pragma warning(disable : 4640) // construction of local static object is not thread-safe + static const locale utf8_locale{"en-US.UTF-8"}; +#pragma warning(pop) + + return utf8_locale; +} + +wstring string_to_wstring(const string_view str) { + using facet_type = codecvt; + const facet_type& facet{use_facet(get_utf8_locale())}; + + mbstate_t conversion_state{}; + const size_t num_chars_required = static_cast( + facet.length(conversion_state, str.data(), str.data() + str.size(), (numeric_limits::max)())); + + wstring output_str; + output_str.resize_and_overwrite(num_chars_required, [&](wchar_t* const dest_str_ptr, const size_t output_size) { + const char* src_end_ptr; + wchar_t* dest_end_ptr; + const codecvt_base::result conversion_result = facet.in(conversion_state, str.data(), str.data() + str.size(), + src_end_ptr, dest_str_ptr, dest_str_ptr + output_size, dest_end_ptr); + + assert(conversion_result == codecvt_base::ok); + + return dest_end_ptr - dest_str_ptr; + }); + + return output_str; +} + +string wstring_to_string(const wstring_view wide_str) { + using facet_type = codecvt; + const facet_type& facet{use_facet(get_utf8_locale())}; + + mbstate_t conversion_state{}; + const size_t max_chars_required = wide_str.size() * facet.max_length(); + + string output_str; + output_str.resize_and_overwrite(max_chars_required, [&](char* const dest_str_ptr, const size_t output_size) { + const wchar_t* src_end_ptr; + char* dest_end_ptr; + const codecvt_base::result conversion_result = facet.out(conversion_state, wide_str.data(), + wide_str.data() + wide_str.size(), src_end_ptr, dest_str_ptr, dest_str_ptr + output_size, dest_end_ptr); + + assert(conversion_result == codecvt_base::ok); + + return dest_end_ptr - dest_str_ptr; + }); + + return output_str; +} + +void maybe_flush_console_file_stream(const test::win_console& console) { + // std::print() and std::println() should automatically flush the stream if the Unicode + // API is being used, according to N4928 [print.fun]/7. So, as an additional check, + // we'll only call std::fflush() if the ordinary literal encoding is *NOT* UTF-8. This + // should work fine, assuming that our implementation is correct. + if constexpr (!_Is_ordinary_literal_encoding_utf8()) { + fflush(console.get_file_stream()); + } +} + +void test_print_optimizations() { + test::win_console test_console{}; + FILE* const console_file_stream = test_console.get_file_stream(); + + size_t curr_line_number = 0; + + const auto get_last_console_line_closure = [&test_console, &curr_line_number]() { + maybe_flush_console_file_stream(test_console); + + return wstring_to_string(test_console.get_console_line(curr_line_number++)); + }; + + stringstream test_str_stream{}; + + // Even if a string has no formatting arguments, std::format() will still replace escaped + // brace characters (i.e., {{ and }}) with a single brace character. We need to make sure + // that we do the same. + { + constexpr string_view escaped_braces_str{"[{{a{{{{b c}}}}d}}]"}; + constexpr string_view expected_str{"[{a{{b c}}d}][{a{{b c}}d}]"}; + + print(console_file_stream, escaped_braces_str); + println(console_file_stream, escaped_braces_str); + + const string last_console_line = get_last_console_line_closure(); + assert(last_console_line == expected_str); + + print(test_str_stream, escaped_braces_str); + println(test_str_stream, escaped_braces_str); + + string str_stream_line; + getline(test_str_stream, str_stream_line); + + assert(str_stream_line == expected_str); + } + + // When writing out to a Unicode console, we transcode the string in segments of 8,192 bytes + // each. Splitting up the strings into segments requires ending each segment on a valid code + // point (if applicable). We need to make sure that the actual string is getting printed + // appropriately. Manual test: + + /********** + #include + #include + #include + using namespace std; + + int main() { + string str; + for (int i = 10; i < 8190; i += 10) { + str += format("[{:.<8}]", i); + } + str += "[8189...]"; + println("{}\xF0\x9F\x90\x88", str); + } + **********/ +} + +void test_invalid_code_points_console() { + if constexpr (!_Is_ordinary_literal_encoding_utf8()) { + if (GetConsoleOutputCP() != CP_UTF8) { + return; // With neither `/utf-8` nor a UTF-8 console output codepage, we can't run this part of the test. + } + } + + test::win_console test_console{}; + FILE* const console_file_stream = test_console.get_file_stream(); + size_t curr_line_number = 0; + + using printed_string_type = format_string<>; + + const auto test_valid_sequence_closure = [&](const printed_string_type printed_str) { + println(console_file_stream, printed_str); + maybe_flush_console_file_stream(test_console); + + const wstring console_line{test_console.get_console_line(curr_line_number++)}; + assert(wstring_to_string(console_line) == printed_str.get()); + }; + + const auto test_invalid_sequence_closure = [&](const printed_string_type printed_str) { + println(console_file_stream, printed_str); + maybe_flush_console_file_stream(test_console); + + const wstring console_line{test_console.get_console_line(curr_line_number++)}; + const bool contains_replacement_character = console_line.contains(L'\uFFFD'); + + if constexpr (_Is_ordinary_literal_encoding_utf8()) { + // It isn't necessarily well-documented how MultiByteToWideChar() (used internally by + // __std_print_to_unicode_console()) replaces invalid code points, except for the fact + // that if an invalid code point is encountered, then some amount of characters are + // replaced with a single U+FFFD character. So, we instead check that the string written + // to the console has at least one U+FFFD character if it is invalid. (The documentation + // for the function also implies that if the string provided to MultiByteToWideChar() isn't + // empty, then the string created by it also won't be empty.) + assert(printed_str.get().empty() || !console_line.empty()); + assert(contains_replacement_character); + } + + // There seems to be some inconsistent behavior with what happens when std::fputs() (used internally + // by std::print() et al. when writing to files or when the ordinary literal encoding isn't UTF-8) writes + // invalid code sequences to a FILE stream associated with a console. On some systems, it seems that + // these characters are dropped; on others, they seem to be replaced with U+FFFD. So, a check like + // "assert(!contains_replacement_character)" might pass on some systems and fail on others. + }; + + // Example UTF-8 Code Sequences from https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805 + + // Valid ASCII + test_valid_sequence_closure("a"); + + // Valid 2 Octet Sequence + test_valid_sequence_closure("\xC3\xB1"); + + // Invalid 2 Octet Sequence + test_invalid_sequence_closure("\xC3\x28"); + + // Invalid Sequence Identifier + test_invalid_sequence_closure("\xA0\xA1"); + + // Valid 3 Octet Sequence + test_valid_sequence_closure("\xE2\x82\xA1"); + + // Invalid 3 Octet Sequence (in 2nd Octet) + test_invalid_sequence_closure("\xE2\x28\xA1"); + + // Invalid 3 Octet Sequence (in 3rd Octet) + test_invalid_sequence_closure("\xE2\x82\x28"); + + // Skipped: 0xF0 0x90 0x8C 0xBC (MultiByteToWideChar() correctly finds that this is a valid + // UTF-8 code point, but the call to WriteConsoleW() seems to erroneously replace it with U+FFFD.) + + // Invalid 4 Octet Sequence (in 2nd Octet) + test_invalid_sequence_closure("\xF0\x28\x8C\xBC"); + + // Invalid 4 Octet Sequence (in 3rd Octet) + test_invalid_sequence_closure("\xF0\x90\x28\xBC"); + + // Invalid 4 Octet Sequence (in 4th Octet) + test_invalid_sequence_closure("\xF0\x28\x8C\x25"); +} + +void test_invalid_code_points_file() { + // Unlike for the console API when the ordinary literal encoding is UTF-8, invalid code points shouldn't + // be replaced when writing to a file. + const string temp_file_name_str = temp_file_name(); + + FILE* temp_file_stream; + + { + const errno_t fopen_result = fopen_s(&temp_file_stream, temp_file_name_str.c_str(), "w+b"); + assert(fopen_result == 0); + } + + using printed_string_type = format_string<>; + + const auto test_sequence_closure = [&](const printed_string_type printed_str) { + rewind(temp_file_stream); + print(temp_file_stream, printed_str); + rewind(temp_file_stream); + + string file_line_str; + file_line_str.resize_and_overwrite( + printed_str.get().size() + 1, [&](char* const dest_str_ptr, const size_t output_size) { + fgets(dest_str_ptr, static_cast(output_size), temp_file_stream); + return output_size - 1; + }); + + assert(file_line_str == printed_str.get()); + }; + + // Example UTF-8 Code Sequences from https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805 + + // Valid ASCII + test_sequence_closure("a"); + + // Valid 2 Octet Sequence + test_sequence_closure("\xC3\xB1"); + + // Invalid 2 Octet Sequence + test_sequence_closure("\xC3\x28"); + + // Invalid Sequence Identifier + test_sequence_closure("\xA0\xA1"); + + // Valid 3 Octet Sequence + test_sequence_closure("\xE2\x82\xA1"); + + // Invalid 3 Octet Sequence (in 2nd Octet) + test_sequence_closure("\xE2\x28\xA1"); + + // Invalid 3 Octet Sequence (in 3rd Octet) + test_sequence_closure("\xE2\x82\x28"); + + // Valid 4 Octet Sequence + test_sequence_closure("\xF0\x90\x8C\xBC"); + + // Invalid 4 Octet Sequence (in 2nd Octet) + test_sequence_closure("\xF0\x28\x8C\xBC"); + + // Invalid 4 Octet Sequence (in 3rd Octet) + test_sequence_closure("\xF0\x90\x28\xBC"); + + // Invalid 4 Octet Sequence (in 4th Octet) + test_sequence_closure("\xF0\x28\x8C\x25"); + + fclose(temp_file_stream); + + filesystem::remove(temp_file_name_str); +} + +void test_stream_flush_console() { + // If the ordinary literal encoding is UTF-8, then the FILE stream associated with + // a console should always be flushed before writing output during a call to std::print() + // and std::println(). Otherwise, the stream should *NOT* be flushed. + test::win_console temp_console{}; + FILE* const console_file_stream = temp_console.get_file_stream(); + + print(console_file_stream, "Hello,"); + + { + const wstring extractedStr{temp_console.get_console_line(0)}; + + if constexpr (_Is_ordinary_literal_encoding_utf8()) { + assert(extractedStr == L"Hello,"); + } else { + assert(extractedStr.empty()); + } + } + + println(console_file_stream, " world!"); + + { + const wstring extractedStr{temp_console.get_console_line(0)}; + + if constexpr (_Is_ordinary_literal_encoding_utf8()) { + assert(extractedStr == L"Hello, world!"); + } else { + assert(extractedStr.empty()); + } + } + + maybe_flush_console_file_stream(temp_console); + + { + const wstring extractedStr{temp_console.get_console_line(0)}; + assert(extractedStr == L"Hello, world!"); + } +} + +void test_stream_flush_file() { + // Regardless of the ordinary literal encoding, neither std::print() nor std::println() + // should flush file streams which do not refer to consoles. + const string temp_file_name_str = temp_file_name(); + + { + ofstream output_file_stream{temp_file_name_str}; + + print(output_file_stream, "Hello, "); + + { + ifstream input_file_stream{temp_file_name_str}; + + string extracted_line_str; + getline(input_file_stream, extracted_line_str); + + assert(extracted_line_str.empty()); + } + + println(output_file_stream, "world!"); + + { + ifstream input_file_stream{temp_file_name_str}; + + string extracted_line_str; + getline(input_file_stream, extracted_line_str); + + assert(extracted_line_str.empty()); + } + + output_file_stream.flush(); + + { + ifstream input_file_stream{temp_file_name_str}; + + string extracted_line_str; + getline(input_file_stream, extracted_line_str); + + assert(extracted_line_str == "Hello, world!"); + } + } + + filesystem::remove(temp_file_name_str); +} + +void test_empty_strings_and_newlines() { + const string temp_file_name_str = temp_file_name(); + + { + ofstream output_file_stream{temp_file_name_str}; + + print(output_file_stream, "NCC-1701"); + print(output_file_stream, ""); + print(output_file_stream, "-D\n"); + print(output_file_stream, "{{}} for {}!\n", "impact"); + + println(output_file_stream, "I have {} cute {} kittens.", 1729, "fluffy"); + println(output_file_stream, ""); + println(output_file_stream, "What are an orthodontist's favorite characters? '{{' and '}}', of course!"); + println(output_file_stream, "ONE\nTWO\n"); + println(output_file_stream, "THREE"); + } + + { + ifstream input_file_stream{temp_file_name_str}; + + vector lines; + for (string str; getline(input_file_stream, str);) { + lines.push_back(str); + } + + const vector expected_lines{ + "NCC-1701-D", + "{} for impact!", + "I have 1729 cute fluffy kittens.", + "", + "What are an orthodontist's favorite characters? '{' and '}', of course!", + "ONE", + "TWO", + "", + "THREE", + }; + + assert(lines == expected_lines); + } + + filesystem::remove(temp_file_name_str); +} + +void all_tests() { + test_print_optimizations(); + + test_invalid_code_points_console(); + test_invalid_code_points_file(); + + test_stream_flush_console(); + test_stream_flush_file(); + + test_empty_strings_and_newlines(); +} + +int main(int argc, char* argv[]) { + // For clarity, we pass a --child option, instead of just detecting the semaphore values. + const bool is_child{argc == 4 && argv[1] == "--child"sv}; + + if (is_child) { + test::win_semaphore hello_semaphore{static_cast(stoull(argv[2]))}; + test::win_semaphore goodbye_semaphore{static_cast(stoull(argv[3]))}; + + // We use the hello_semaphore to tell the parent process that we're ready for it to attach to our console. + hello_semaphore.release(); + + // Then, we use the goodbye_semaphore to wait for the parent process to finish its work. + goodbye_semaphore.acquire(); + } else { + test::win_semaphore hello_semaphore{}; + test::win_semaphore goodbye_semaphore{}; + + // This will receive the child process ID. + PROCESS_INFORMATION process_information{}; + + { + // Get the absolute path of our executable. + // This assumes that our test infrastructure doesn't use long paths. + wstring module_filename; + module_filename.resize_and_overwrite(MAX_PATH, [](wchar_t* const ptr, const size_t n) { + // resize_and_overwrite() prepares a buffer of size n + 1. + // GetModuleFileNameW() takes that buffer size. + const DWORD len = GetModuleFileNameW(nullptr, ptr, static_cast(n + 1)); + assert(len != 0); + return len; + }); + + // CreateProcessW() requires the command line to be non-const, so we pass command_line.data() below. + // We use quotes to defend against module_filename containing spaces. + wstring command_line = format(LR"("{}" --child {} {})", module_filename, hello_semaphore.to_uintptr(), + goodbye_semaphore.to_uintptr()); + + // The entire purpose of this code is to hide the child process's console window, + // to avoid rapid flickering during test runs. + STARTUPINFOW startup_info{ + .cb = sizeof(STARTUPINFOW), + .dwFlags = STARTF_USESHOWWINDOW, + .wShowWindow = SW_HIDE, + }; + + constexpr BOOL inherit_handles = TRUE; + + // https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags + // "The new process has a new console, instead of inheriting its parent's console (the default)." + constexpr DWORD creation_flags = CREATE_NEW_CONSOLE; + + const BOOL create_process_succeeded = CreateProcessW(module_filename.c_str(), command_line.data(), nullptr, + nullptr, inherit_handles, creation_flags, nullptr, nullptr, &startup_info, &process_information); + assert(create_process_succeeded); + } + + // Wait for the child process to be ready before attaching to its console. + hello_semaphore.acquire(); + + { + // We want to do things like alter the output code page of our console, but we don't want + // to affect other tests which run concurrently. So, we want to detach this process from + // its current console, attach to the child process's console, and modify that one instead. + + const BOOL free_console_succeeded = FreeConsole(); + assert(free_console_succeeded); + + const BOOL attach_console_succeeded = AttachConsole(process_information.dwProcessId); + assert(attach_console_succeeded); + } + + all_tests(); // Run tests with the original console output codepage. + + { + const BOOL set_console_output_cp_succeeded = SetConsoleOutputCP(CP_UTF8); + assert(set_console_output_cp_succeeded); + } + + all_tests(); // Run tests with the console output codepage set to UTF-8. + + // Tell the child process that we're done working with its console. + goodbye_semaphore.release(); + + { + const BOOL close_process_handle_succeeded = CloseHandle(process_information.hProcess); + assert(close_process_handle_succeeded); + + const BOOL close_thread_handle_succeeded = CloseHandle(process_information.hThread); + assert(close_thread_handle_succeeded); + } + } +} diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 0aa3e9b765..83368cba70 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -1522,6 +1522,20 @@ STATIC_ASSERT(__cpp_lib_polymorphic_allocator == 201902L); #endif #endif +#if _HAS_CXX23 && defined(__cpp_lib_concepts) // TRANSITION, GH-395 +#ifndef __cpp_lib_print +#error __cpp_lib_print is not defined +#elif __cpp_lib_print != 202207L +#error __cpp_lib_print is not 202207L +#else +STATIC_ASSERT(__cpp_lib_print == 202207L); +#endif +#else +#ifdef __cpp_lib_print +#error __cpp_lib_print is defined +#endif +#endif + #ifndef __cpp_lib_quoted_string_io #error __cpp_lib_quoted_string_io is not defined #elif __cpp_lib_quoted_string_io != 201304L diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index e9fdcfa9c7..e65d2a6401 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -52,6 +52,7 @@ PM_CL="/DMEOW_HEADER=numbers" PM_CL="/DMEOW_HEADER=numeric" PM_CL="/DMEOW_HEADER=optional" PM_CL="/DMEOW_HEADER=ostream" +PM_CL="/DMEOW_HEADER=print" PM_CL="/DMEOW_HEADER=queue" PM_CL="/DMEOW_HEADER=random" PM_CL="/DMEOW_HEADER=ranges"