diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -340,6 +340,8 @@ --------------------------------------------------- ----------------- ``__cpp_lib_out_ptr`` *unimplemented* --------------------------------------------------- ----------------- + ``__cpp_lib_print`` *unimplemented* + --------------------------------------------------- ----------------- ``__cpp_lib_ranges_as_rvalue`` ``202207L`` --------------------------------------------------- ----------------- ``__cpp_lib_ranges_chunk`` *unimplemented* diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -59,7 +59,7 @@ "`P1467R9 `__","LWG","Extended ``floating-point`` types and standard names","July 2022","","" "`P1642R11 `__","LWG","Freestanding ``[utilities]``, ``[ranges]``, and ``[iterators]``","July 2022","","" "`P1899R3 `__","LWG","``stride_view``","July 2022","","","|ranges|" -"`P2093R14 `__","LWG","Formatted output","July 2022","","" +"`P2093R14 `__","LWG","Formatted output","July 2022","","|In Progress|" "`P2165R4 `__","LWG","Compatibility between ``tuple``, ``pair`` and ``tuple-like`` objects","July 2022","","" "`P2278R4 `__","LWG","``cbegin`` should always return a constant iterator","July 2022","","","|ranges|" "`P2286R8 `__","LWG","Formatting Ranges","July 2022","|Complete|","16.0","|format| |ranges|" @@ -101,7 +101,7 @@ "`P2167R3 `__","LWG", "Improved Proposed Wording for LWG 2114", "November 2022","","","" "`P2396R1 `__","LWG", "Concurrency TS 2 fixes ", "November 2022","","","|concurrency TS|" "`P2505R5 `__","LWG", "Monadic Functions for ``std::expected``", "November 2022","|Complete|","17.0","" -"`P2539R4 `__","LWG", "Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?", "November 2022","","","|format|" +"`P2539R4 `__","LWG", "Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?", "November 2022","|In Progress|","","|format|" "`P2602R2 `__","LWG", "Poison Pills are Too Toxic", "November 2022","","","|ranges|" "`P2708R1 `__","LWG", "No Further Fundamentals TSes", "November 2022","|Nothing to do|","","" "","","","","","","" diff --git a/libcxx/docs/Status/FormatIssues.csv b/libcxx/docs/Status/FormatIssues.csv --- a/libcxx/docs/Status/FormatIssues.csv +++ b/libcxx/docs/Status/FormatIssues.csv @@ -5,16 +5,16 @@ `P1868 `_,"width: clarifying units of width and precision in std::format (Implements the unicode support.)","C++20",Mark de Wever,|Complete|,14.0 `P2216 `_,"std::format improvements","C++20",Mark de Wever,|Complete|,15.0 `P2418 `__,"Add support for ``std::generator``-like types to ``std::format``","C++20",Mark de Wever,|Complete|,15.0 -"`P2093R14 `__","Formatted output","C++23",Mark de Wever,|In Progress|, +"`P2093R14 `__","Formatted output","C++23",Mark de Wever,|In Progress| "`P2286R8 `__","Formatting Ranges","C++23","Mark de Wever","|Complete|",16.0 "`P2508R1 `__","Exposing ``std::basic-format-string``","C++23","Mark de Wever","|Complete|",15.0 "`P2585R0 `__","Improving default container formatting","C++23","Mark de Wever","|Complete|",17.0 -"`P2539R4 `__","Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?","C++23","Mark de Wever" +"`P2539R4 `__","Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?","C++23","Mark de Wever","|In Progress|" "`P2713R1 `__","Escaping improvements in ``std::format``","C++23","Mark de Wever","" "`P2675R1 `__","``format``'s width estimation is too approximate and not forward compatible","C++23","Mark de Wever","|Complete|",17.0 "`P2572R1 `__","``std::format`` fill character allowances","C++23","Mark de Wever","|Complete|",17.0 -"`P2693R1 `__","Formatting ``thread::id`` and ``stacktrace``","C++23","Mark de Wever","|In progress|" -"`P2510R3 `__","Formatting pointers","C++26","Mark de Wever","|Complete|", 17.0 +"`P2693R1 `__","Formatting ``thread::id`` and ``stacktrace``","C++23","Mark de Wever","|In Progress|" +"`P2510R3 `__","Formatting pointers","C++26","Mark de Wever","|Complete|",17.0 "`P2757R3 `__","Type-checking format args","C++26","","", "`P2637R3 `__","Member ``visit``","C++26","","", `P1361 `_,"Integration of chrono with text formatting","C++20",Mark de Wever,|In Progress|, diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv --- a/libcxx/docs/Status/FormatPaper.csv +++ b/libcxx/docs/Status/FormatPaper.csv @@ -48,5 +48,5 @@ "`P2093R14 `__","Formatted output" `[print.fun] `__,"Output to ``stdout``",,Mark de Wever,|In Progress|, -`[print.fun] `__,"Output to ``FILE*``",,Mark de Wever,, +`[print.fun] `__,"Output to ``FILE*``",,Mark de Wever,|Complete|, 17.0 `[ostream.formatted.print] `__,"Output to ``ostream``",,Mark de Wever diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h --- a/libcxx/include/__format/buffer.h +++ b/libcxx/include/__format/buffer.h @@ -529,6 +529,7 @@ struct __iterator { using difference_type = ptrdiff_t; + using value_type = _CharT; _LIBCPP_HIDE_FROM_ABI constexpr explicit __iterator(__retarget_buffer& __buffer) : __buffer_(std::addressof(__buffer)) {} @@ -551,7 +552,14 @@ __retarget_buffer& operator=(const __retarget_buffer&) = delete; _LIBCPP_HIDE_FROM_ABI explicit __retarget_buffer(size_t __size_hint) { - auto __result = std::__allocate_at_least(__alloc_, __size_hint ? __size_hint : 256 / sizeof(_CharT)); + // When the initial size is very small a lot of resizes happen + // when elements added. So use a hard-coded minimum size. + // + // Note a size < 2 will not work + // - 0 there is no buffer, while push_back requires 1 empty element. + // - 1 multiplied by the grow factor is 1 and thus the buffer never + // grows. + auto __result = std::__allocate_at_least(__alloc_, std::max(__size_hint, 256 / sizeof(_CharT))); __ptr_ = __result.ptr; __capacity_ = __result.count; } diff --git a/libcxx/include/print b/libcxx/include/print --- a/libcxx/include/print +++ b/libcxx/include/print @@ -10,19 +10,67 @@ #ifndef _LIBCPP_PRINT #define _LIBCPP_PRINT +/* +namespace std { + // [print.fun], print functions + template + void print(FILE* stream, format_string fmt, Args&&... args); + + template + void println(FILE* stream, format_string fmt, Args&&... args); + + void vprint_unicode(FILE* stream, string_view fmt, format_args args); + + void vprint_nonunicode(FILE* stream, string_view fmt, format_args args); +} +*/ + #include <__assert> // all public C++ headers provide the assertion handler #include <__concepts/same_as.h> #include <__config> +#include <__format/buffer.h> +#include <__format/format_arg_store.h> +#include <__format/format_args.h> +#include <__format/format_context.h> +#include <__format/format_error.h> +#include <__format/format_functions.h> #include <__format/unicode.h> +#include <__system_error/system_error.h> +#include <__utility/forward.h> +#include +#include +#include #include #include +#if __has_include() +# include +#endif + #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header #endif _LIBCPP_BEGIN_NAMESPACE_STD +#ifdef _WIN32 +_LIBCPP_EXPORTED_FROM_ABI bool __is_windows_terminal(FILE* __stream); + +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +// A wrapper for WriteConsoleW which is used to write to the Windows +// console. This function is in the dylib to avoid pulling in windows.h +// in the library headers. The function itself uses some private parts +// of the dylib too. +// +// The function does not depend on the language standard used. Guarding +// it with C++23 would fail since the dylib is currently built using C++20. +// +// Note the function is only implemented on the Windows platform. +_LIBCPP_EXPORTED_FROM_ABI void __write_to_windows_console(FILE* __stream, wstring_view __view); +# endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +#endif // _WIN32 + #if _LIBCPP_STD_VER >= 23 # ifndef _LIBCPP_HAS_NO_UNICODE @@ -68,7 +116,7 @@ template requires __utf16_code_unit> _LIBCPP_HIDE_FROM_ABI constexpr void __encode(_OutIt& __out_it, char32_t __value) { - _LIBCPP_ASSERT(__is_scalar_value(__value), "an invalid unicode scalar value results in invalid UTF-16"); + _LIBCPP_ASSERT_UNCATEGORIZED(__is_scalar_value(__value), "an invalid unicode scalar value results in invalid UTF-16"); if (__value < 0x10000) { *__out_it++ = __value; @@ -83,7 +131,7 @@ template requires __utf32_code_unit> _LIBCPP_HIDE_FROM_ABI constexpr void __encode(_OutIt& __out_it, char32_t __value) { - _LIBCPP_ASSERT(__is_scalar_value(__value), "an invalid unicode scalar value results in invalid UTF-32"); + _LIBCPP_ASSERT_UNCATEGORIZED(__is_scalar_value(__value), "an invalid unicode scalar value results in invalid UTF-32"); *__out_it++ = __value; } @@ -101,6 +149,9 @@ // Note if P2728 is accepted types like int may become valid. In that case // the __code_point_view should use a span. Libc++ will remove support for // char_traits. + + // TODO PRINT Validate with clang-tidy + // NOLINTNEXTLINE(bugprone-dangling-handle) basic_string_view> __data{__first, __last}; __code_point_view> __view{__data.begin(), __data.end()}; while (!__view.__at_end()) @@ -112,6 +163,198 @@ # endif // _LIBCPP_HAS_NO_UNICODE +namespace __print { + +// [print.fun]/2 +// Effects: If the ordinary literal encoding ([lex.charset]) is UTF-8, equivalent to: +// vprint_unicode(stream, fmt.str, make_format_args(args...)); +// Otherwise, equivalent to: +// vprint_nonunicode(stream, fmt.str, make_format_args(args...)); +// +// Based on the compiler and its compilation flags this value is or is +// not true. As mentioned in P2093R14 this only affects Windows. The +// test below could also be done for +// - GCC using __GNUC_EXECUTION_CHARSET_NAME +// https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html +// - Clang using __clang_literal_encoding__ +// https://clang.llvm.org/docs/LanguageExtensions.html#builtin-macros +// (note at the time of writing Clang is hard-coded to UTF-8.) +// + +# ifdef _LIBCPP_HAS_NO_UNICODE +inline constexpr bool __use_unicode = false; +# elif defined(_MSVC_EXECUTION_CHARACTER_SET) +// This is the same test MSVC STL uses in their implementation of +// See: https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers +inline constexpr bool __use_unicode = _MSVC_EXECUTION_CHARACTER_SET == 65001; +# else +inline constexpr bool __use_unicode = true; +# endif + +_LIBCPP_HIDE_FROM_ABI inline bool __is_terminal(FILE* __stream) { +# ifdef _WIN32 + return std::__is_windows_terminal(__stream); +# elif __has_include() + return isatty(fileno(__stream)); +# else +# error "Provide a way to determine whether a FILE* is a terminal" +# endif +} + +template // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) { + _LIBCPP_ASSERT_UNCATEGORIZED(__stream, "__stream is a valid pointer to an output C stream"); + string __str = std::vformat(__fmt, __args); + if (__write_nl) + __str.push_back('\n'); + + size_t __size = fwrite(__str.data(), 1, __str.size(), __stream); + if (__size < __str.size()) { + if (std::feof(__stream)) + std::__throw_system_error(EIO, "EOF while writing the formatted output"); + std::__throw_system_error(std::ferror(__stream), "failed to write formatted output"); + } +} + +# ifndef _LIBCPP_HAS_NO_UNICODE + +// Note these helper functions are mainly used to aid testing. +// On POSIX systems and Windows the output is no longer considered a +// terminal when the output is redirected. Typically during testing the +// output is redirected to be able to capture it. This makes it hard to +// test this code path. +template // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { + // TODO PRINT Should flush errors throw too? + if (__is_terminal) + std::fflush(__stream); + + __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); +} + +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +template // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { + if (!__is_terminal) + return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); + + // TODO PRINT Should flush errors throw too? + std::fflush(__stream); + + string __str = std::vformat(__fmt, __args); + // UTF-16 uses the same number or less code units than UTF-8. + // However the size of the code unit is 16 bits instead of 8 bits. + // + // The buffer uses the worst-case estimate and should never resize. + // However when the string is large this could lead to OOM. Using a + // smaller size might work, but since the buffer uses a grow factor + // the final size might be larger when the estimate is wrong. + // + // TODO PRINT profile and improve the speed of this code. + __format::__retarget_buffer __buffer{__str.size()}; + __unicode::__transcode(__str.begin(), __str.end(), __buffer.__make_output_iterator()); + if (__write_nl) + __buffer.push_back(L'\n'); + + [[maybe_unused]] wstring_view __view = __buffer.__view(); + + // The macro _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION is used to change + // the behavior in the test. This is not part of the public API. +# ifdef _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION + _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION(__stream, __view); +# elif defined(_WIN32) + std::__write_to_windows_console(__stream, __view); +# else + std::__throw_runtime_error("No defintion of _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION and " + "__write_to_windows_console is not available."); +# endif +} +# endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +template // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_unicode([[maybe_unused]] FILE* __stream, + [[maybe_unused]] string_view __fmt, + [[maybe_unused]] format_args __args, + [[maybe_unused]] bool __write_nl) { + _LIBCPP_ASSERT_UNCATEGORIZED(__stream, "__stream is a valid pointer to an output C stream"); + + // [print.fun] + // 7 - Effects: If stream refers to a terminal capable of displaying + // Unicode, writes out to the terminal using the native Unicode + // API; if out contains invalid code units, the behavior is + // undefined and implementations are encouraged to diagnose it. + // Otherwise writes out to stream unchanged. If the native + // Unicode API is used, the function flushes stream before + // writing out. + // 8 - Throws: Any exception thrown by the call to vformat + // ([format.err.report]). system_error if writing to the terminal + // or stream fails. May throw bad_alloc. + // 9 - Recommended practice: If invoking the native Unicode API + // requires transcoding, implementations should substitute + // invalid code units with U+FFFD replacement character per the + // Unicode Standard, Chapter 3.9 U+FFFD Substitution in + // Conversion. + + // On non-Windows platforms the Unicode API is the normal file I/O API + // so there the call can be forwarded to the non_unicode API. On + // Windows there is a different API. This API requires transcoding. + +# ifndef _WIN32 + __print::__vprint_unicode_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); +# elif !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS) + __print::__vprint_unicode_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); +# else +# error "Windows builds with wchar_t disabled are not supported." +# endif +} + +# endif // _LIBCPP_HAS_NO_UNICODE + +} // namespace __print + +template +_LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { +# ifndef _LIBCPP_HAS_NO_UNICODE + if constexpr (__print::__use_unicode) + __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false); + else + __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false); +# else // _LIBCPP_HAS_NO_UNICODE + __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false); +# endif // _LIBCPP_HAS_NO_UNICODE +} + +template +_LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { +# ifndef _LIBCPP_HAS_NO_UNICODE + // Note the wording in the Standard is inefficient. The output of + // std::format is a std::string which is then copied. This solution + // just appends a newline at the end of the output. + if constexpr (__print::__use_unicode) + __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true); + else + __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true); +# else // _LIBCPP_HAS_NO_UNICODE + __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true); +# endif // _LIBCPP_HAS_NO_UNICODE +} + +# ifndef _LIBCPP_HAS_NO_UNICODE +template // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(FILE* __stream, string_view __fmt, format_args __args) { + __print::__vprint_unicode(__stream, __fmt, __args, false); +} +# endif // _LIBCPP_HAS_NO_UNICODE + +template // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args) { + __print::__vprint_nonunicode(__stream, __fmt, __args, false); +} + #endif // _LIBCPP_STD_VER >= 23 _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/version b/libcxx/include/version --- a/libcxx/include/version +++ b/libcxx/include/version @@ -149,6 +149,7 @@ __cpp_lib_out_ptr 202106L __cpp_lib_parallel_algorithm 201603L __cpp_lib_polymorphic_allocator 201902L +__cpp_lib_print 202207L __cpp_lib_quoted_string_io 201304L __cpp_lib_ranges 202207L @@ -429,6 +430,7 @@ # undef __cpp_lib_optional # define __cpp_lib_optional 202110L // # define __cpp_lib_out_ptr 202106L +// # define __cpp_lib_print 202207L # define __cpp_lib_ranges_as_rvalue 202207L // # define __cpp_lib_ranges_chunk 202202L // # define __cpp_lib_ranges_chunk_by 202202L diff --git a/libcxx/modules/std/print.cppm b/libcxx/modules/std/print.cppm --- a/libcxx/modules/std/print.cppm +++ b/libcxx/modules/std/print.cppm @@ -12,12 +12,10 @@ export module std:print; export namespace std { -#if 0 // [print.fun], print functions using std::print; using std::println; using std::vprint_nonunicode; using std::vprint_unicode; -#endif } // namespace std diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -41,6 +41,7 @@ new_handler.cpp new_helpers.cpp optional.cpp + print.cpp random_shuffle.cpp ryu/d2fixed.cpp ryu/d2s.cpp diff --git a/libcxx/src/print.cpp b/libcxx/src/print.cpp new file mode 100644 --- /dev/null +++ b/libcxx/src/print.cpp @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> +#include +#include + +#if defined(_LIBCPP_WIN32API) +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include +# include + +# include <__system_error/system_error.h> + +# include "filesystem/error.h" +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#ifdef _WIN32 +_LIBCPP_EXPORTED_FROM_ABI bool __is_windows_terminal(FILE* __stream) { + // Note the Standard does this in one call, but it's unclear whether + // an invalid handle is allowed when calling GetConsoleMode. + // + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=msvc-170 + // https://learn.microsoft.com/en-us/windows/console/getconsolemode + intptr_t __handle = _get_osfhandle(fileno(__stream)); + if (__handle == -1) + return false; + + unsigned long __mode; + return GetConsoleMode(reinterpret_cast(__handle), &__mode); +} + +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +_LIBCPP_EXPORTED_FROM_ABI void +__write_to_windows_console([[maybe_unused]] FILE* __stream, [[maybe_unused]] wstring_view __view) { + // https://learn.microsoft.com/en-us/windows/console/writeconsole + if (WriteConsoleW(reinterpret_cast(_get_osfhandle(fileno(__stream))), // clang-format aid + __view.data(), + __view.size(), + nullptr, + nullptr) == 0) { + std::__throw_system_error{ + filesystem::detail::make_windows_error(GetLastError()), "failed to write formatted output"}; + } +} +# endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +#endif // _WIN32 + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// XFAIL: availability-fp_to_chars-missing + +// REQUIRES: has-unix-headers + +// + +// Tests the implementation of +// void __print::__vprint_unicode_posix(FILE* __stream, string_view __fmt, +// format_args __args, bool __write_nl, +// bool __is_terminal); +// +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. By testing this function we can "force" emulate a +// terminal. +// Note __write_nl is tested by the public API. + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +int main(int, char**) { + std::array buffer; + std::ranges::fill(buffer, '*'); + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + // Test the file is buffered. + std::fprintf(file, "Hello"); + assert(std::ftell(file) == 5); +#if defined(TEST_HAS_GLIBC) && \ + !(__has_feature(address_sanitizer) || __has_feature(thread_sanitizer) || __has_feature(memory_sanitizer)) + assert(std::ranges::all_of(buffer, [](char c) { return c == '*'; })); +#endif + + // Test writing to a "non-terminal" stream does not flush. + std::__print::__vprint_unicode_posix(file, " world", std::make_format_args(), false, false); + assert(std::ftell(file) == 11); +#if defined(TEST_HAS_GLIBC) && \ + !(__has_feature(address_sanitizer) || __has_feature(thread_sanitizer) || __has_feature(memory_sanitizer)) + assert(std::ranges::all_of(buffer, [](char c) { return c == '*'; })); +#endif + + // Test writing to a "terminal" stream flushes before writing. + std::__print::__vprint_unicode_posix(file, "!", std::make_format_args(), false, true); + assert(std::ftell(file) == 12); + assert(std::string_view(buffer.data(), buffer.data() + 11) == "Hello world"); +#if defined(TEST_HAS_GLIBC) + // glibc does not flush after a write. + assert(buffer[11] != '!'); +#endif + + // Test everything is written when closing the stream. + std::fclose(file); + assert(std::string_view(buffer.data(), buffer.data() + 12) == "Hello world!"); + + return 0; +} diff --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp @@ -0,0 +1,133 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: no-filesystem +// UNSUPPORTED: no-wide-characters +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// Clang modules do not work with the definiton of _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION +// XFAIL: modules-build + +// XFAIL: availability-fp_to_chars-missing + +// + +// Tests the implementation of +// void __print::__vprint_unicode_windows(FILE* __stream, string_view __fmt, +// format_args __args, bool __write_nl, +// bool __is_terminal); +// +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. By testing this function we can "force" emulate a +// terminal. +// Note __write_nl is tested by the public API. + +#include +#include +#include +#include + +void write_to_console(FILE*, std::wstring_view data); +#define _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION ::write_to_console +#include + +#include "test_macros.h" +#include "filesystem_test_helper.h" +#include "make_string.h" + +TEST_GCC_DIAGNOSTIC_IGNORED("-Wuse-after-free") + +#define SV(S) MAKE_STRING_VIEW(wchar_t, S) + +bool calling = false; +std::wstring_view expected = L" world"; + +void write_to_console(FILE*, std::wstring_view data) { + assert(calling); + assert(data == expected); +} + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +static void test_basics() { + FILE* file = std::fopen(filename.c_str(), "wb"); + assert(file); + + // Test writing to a "non-terminal" stream does not call WriteConsoleW. + std::__print::__vprint_unicode_windows(file, "Hello", std::make_format_args(), false, false); + assert(std::ftell(file) == 5); + + // It's not possible to reliably test whether writing to a "terminal" stream + // flushes before writing. Testing flushing a closed stream worked on some + // platforms, but was unreliable. + calling = true; + std::__print::__vprint_unicode_windows(file, " world", std::make_format_args(), false, true); +} + +// When the output is a file the data is written as-is. +// When the output is a "terminal" invalid UTF-8 input is flagged. +static void test(std::wstring_view output, std::string_view input) { + // *** File *** + FILE* file = std::fopen(filename.c_str(), "wb"); + assert(file); + + std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, false); + assert(std::ftell(file) == static_cast(input.size())); + std::fclose(file); + + file = std::fopen(filename.c_str(), "rb"); + assert(file); + + std::vector buffer(input.size()); + size_t read = fread(buffer.data(), 1, buffer.size(), file); + assert(read == input.size()); + assert(input == std::string_view(buffer.begin(), buffer.end())); + std::fclose(file); + + // *** Terminal *** + expected = output; + std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, true); +} + +static void test() { + // *** Test valid UTF-8 *** +#define TEST(S) test(SV(S), S) + TEST("hello world"); + + // copied from benchmarks/std_format_spec_string_unicode.bench.cpp + TEST("Lorem ipsum dolor sit amet, ne sensibus evertitur aliquando his. Iuvaret fabulas qui ex."); + TEST("Lōrem ipsūm dolor sīt æmeÞ, ea vel nostrud feuġǣit, muciūs tēmporiȝusrefērrēnÞur no mel."); + TEST("Лорем ипсум долор сит амет, еу диам тамяуам принципес вис, еяуидем цонцептам диспутандо"); + TEST("入ト年媛ろ舗学ラロ準募ケカ社金スノ屋検れう策他セヲシ引口ぎ集7独ぱクふ出車ぽでぱ円輪ルノ受打わ。"); + TEST("\U0001f636\u200d\U0001f32b\ufe0f"); +#undef TEST + + // *** Test invalid utf-8 *** + test(SV("\ufffd"), "\xc3"); + test(SV("\ufffd("), "\xc3\x28"); + + // surrogate range + test(SV("\ufffd"), "\xed\xa0\x80"); // U+D800 + test(SV("\ufffd"), "\xed\xaf\xbf"); // U+DBFF + test(SV("\ufffd"), "\xed\xbf\x80"); // U+DC00 + test(SV("\ufffd"), "\xed\xbf\xbf"); // U+DFFF + + // beyond valid values + test(SV("\ufffd"), "\xf4\x90\x80\x80"); // U+110000 + test(SV("\ufffd"), "\xf4\xbf\xbf\xbf"); // U+11FFFF + + // Validates http://unicode.org/review/pr-121.html option 3. + test(SV("\u0061\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0062"), "\x61\xf1\x80\x80\xe1\x80\xc2\x62"); +} + +int main(int, char**) { + test_basics(); + test(); +} diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -635,11 +635,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -636,11 +636,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -638,11 +638,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -638,11 +638,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -644,11 +644,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -453,11 +453,22 @@ ostream string ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print new +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue cstddef diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -453,11 +453,22 @@ ostream string ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print new +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue cstddef diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/no_file_description.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/no_file_description.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/no_file_description.pass.cpp @@ -0,0 +1,90 @@ +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// XFAIL: msvc, target={{.+}}-windows-gnu +// XFAIL: availability-fp_to_chars-missing + +// + +// The FILE returned by fmemopen does not have file descriptor. +// This means the test could fail when the implementation uses a +// function that requires a file descriptor, for example write. +// +// This tests all print functions which takes a FILE* as argument. + +// template +// void print(FILE* stream, format_string fmt, Args&&... args); +// template +// void println(FILE* stream, format_string fmt, Args&&... args); +// void vprint_unicode(FILE* stream, string_view fmt, format_args args); +// void vprint_nonunicode(FILE* stream, string_view fmt, format_args args); + +#include +#include +#include +#include + +static void test_print() { + std::array buffer{0}; + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + std::print(file, "hello world{}", '!'); + long pos = std::ftell(file); + std::fclose(file); + + assert(pos > 0); + assert(std::string_view(buffer.data(), pos) == "hello world!"); +} + +static void test_println() { + std::array buffer{0}; + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + std::println(file, "hello world{}", '!'); + long pos = std::ftell(file); + std::fclose(file); + + assert(pos > 0); + assert(std::string_view(buffer.data(), pos) == "hello world!\n"); +} + +static void test_vprint_unicode() { + std::array buffer{0}; + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + std::vprint_unicode(file, "hello world{}", std::make_format_args('!')); + long pos = std::ftell(file); + std::fclose(file); + + assert(pos > 0); + assert(std::string_view(buffer.data(), pos) == "hello world!"); +} + +static void test_vprint_nonunicode() { + std::array buffer{0}; + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + std::vprint_nonunicode(file, "hello world{}", std::make_format_args('!')); + long pos = std::ftell(file); + std::fclose(file); + + assert(pos > 0); + assert(std::string_view(buffer.data(), pos) == "hello world!"); +} + +int main(int, char**) { + test_print(); + test_println(); + test_vprint_unicode(); + test_vprint_nonunicode(); + + return 0; +} diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: no-filesystem +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// XFAIL: availability-fp_to_chars-missing + +// + +// template +// void print(FILE* stream, format_string fmt, Args&&... args); + +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. There are tests in +// libcxx/test/libcxx/input.output/iostream.format/print.fun/ +// to validate that behaviour + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_format_string.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = [](std::string_view expected, test_format_string fmt, Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::print(file, fmt, std::forward(args)...); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt.get(), "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = [](std::string_view, std::string_view, Args&&...) { + // After P2216 most exceptions thrown by std::format become ill-formed. + // Therefore this tests does nothing. + // A basic ill-formed test is done in format.verify.cpp + // The exceptions are tested by other functions that don't use the basic-format-string as fmt argument. +}; + +// Glibc fails writing to a wide stream. +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::print(file, "hello")); +} +#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { +#ifdef _AIX + [[maybe_unused]] std::string_view what{"failed to write formatted output: Broken pipe"}; +#else + [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"}; +#endif + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::print(file, "hello")); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::print(file, "\n"); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::print(file, "\n"); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + test_wide_stream(); +#endif + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h b/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H +#define TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H + +template +void print_tests(TestFunction check, ExceptionTest check_exception) { + // *** Test escaping *** + + check("{", "{{"); + check("{:^}", "{{:^}}"); + check("{: ^}", "{{:{}^}}", ' '); + check("{:{}^}", "{{:{{}}^}}"); + check("{:{ }^}", "{{:{{{}}}^}}", ' '); + + // *** Test argument ID *** + check("hello false true", "hello {0:} {1:}", false, true); + check("hello true false", "hello {1:} {0:}", false, true); + + // *** Test many arguments *** + check( + "1234567890\t1234567890", + "{}{}{}{}{}{}{}{}{}{}\t{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0); + + // *** Test embedded NUL character *** + using namespace std::literals; + check("hello\0world"sv, "hello{}{}", '\0', "world"); + check("hello\0world"sv, "hello\0{}"sv, "world"); + check("hello\0world"sv, "hello{}", "\0world"sv); + + // *** Test Unicode *** + // 2-byte code points + check("\u00a1"sv, "{}"sv, "\u00a1"); // INVERTED EXCLAMATION MARK + check("\u07ff"sv, "{:}"sv, "\u07ff"); // NKO TAMAN SIGN + + // 3-byte code points + check("\u0800"sv, "{}"sv, "\u0800"); // SAMARITAN LETTER ALAF + check("\ufffd"sv, "{}"sv, "\ufffd"); // REPLACEMENT CHARACTER + + // 4-byte code points + check("\U00010000"sv, "{}"sv, "\U00010000"); // LINEAR B SYLLABLE B008 A + check("\U0010FFFF"sv, "{}"sv, "\U0010FFFF"); // Undefined Character + + // *** Test invalid format strings *** + check_exception("The format string terminates at a '{'", "{"); + check_exception("The replacement field misses a terminating '}'", "{:", 42); + + check_exception("The format string contains an invalid escape sequence", "}"); + check_exception("The format string contains an invalid escape sequence", "{:}-}", 42); + + check_exception("The format string contains an invalid escape sequence", "} "); + check_exception("The arg-id of the format-spec starts with an invalid character", "{-", 42); + check_exception("Argument index out of bounds", "hello {}"); + check_exception("Argument index out of bounds", "hello {0}"); + check_exception("Argument index out of bounds", "hello {1}", 42); +} + +#endif // TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp @@ -0,0 +1,142 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: no-filesystem +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// XFAIL: availability-fp_to_chars-missing + +// + +// template +// void println(FILE* stream, format_string fmt, Args&&... args); + +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. There are tests in +// libcxx/test/libcxx/input.output/iostream.format/print.fun/ +// to validate that behaviour + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_format_string.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = [](std::string_view e, test_format_string fmt, Args&&... args) { + std::string expected = std::string{e} + '\n'; + + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::println(file, fmt, std::forward(args)...); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt.get(), "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = [](std::string_view, std::string_view, Args&&...) { + // After P2216 most exceptions thrown by std::format become ill-formed. + // Therefore this tests does nothing. + // A basic ill-formed test is done in format.verify.cpp + // The exceptions are tested by other functions that don't use the basic-format-string as fmt argument. +}; + +// Glibc fails writing to a wide stream. +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::println(file, "hello")); +} +#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { +#ifdef _AIX + [[maybe_unused]] std::string_view what{"failed to write formatted output: Broken pipe"}; +#else + [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"}; +#endif + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::println(file, "hello")); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::println(file, ""); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::println(file, ""); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + test_wide_stream(); +#endif + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp @@ -0,0 +1,143 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: no-filesystem +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// XFAIL: availability-fp_to_chars-missing + +// + +// void vprint_nonunicode(FILE* stream, string_view fmt, format_args args); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = [](std::string_view expected, std::string_view fmt, Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_nonunicode(file, fmt, std::make_format_args(args...)); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = []([[maybe_unused]] std::string_view what, + [[maybe_unused]] std::string_view fmt, + [[maybe_unused]] Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::format_error, + [&]([[maybe_unused]] const std::format_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode(file, fmt, std::make_format_args(args...))); + + fclose(file); +}; + +// Glibc fails writing to a wide stream. +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode(file, "hello", std::make_format_args())); +} +#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { +#ifdef _AIX + [[maybe_unused]] std::string_view what{"failed to write formatted output: Broken pipe"}; +#else + [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"}; +#endif + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode(file, "hello", std::make_format_args())); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::vprint_nonunicode(file, "\n", std::make_format_args()); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_nonunicode(file, "\n", std::make_format_args()); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + test_wide_stream(); +#endif + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp @@ -0,0 +1,150 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: no-filesystem +// UNSUPPORTED: libcpp-has-no-unicode +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// XFAIL: availability-fp_to_chars-missing + +// + +// void vprint_unicode(FILE* stream, string_view fmt, format_args args); + +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. There are tests in +// libcxx/test/libcxx/input.output/iostream.format/print.fun/ +// to validate that behaviour + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = [](std::string_view expected, std::string_view fmt, Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_unicode(file, fmt, std::make_format_args(args...)); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = []([[maybe_unused]] std::string_view what, + [[maybe_unused]] std::string_view fmt, + [[maybe_unused]] Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::format_error, + [&]([[maybe_unused]] const std::format_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode(file, fmt, std::make_format_args(args...))); + + fclose(file); +}; + +// Glibc fails writing to a wide stream. +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode(file, "hello", std::make_format_args())); +} +#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { +#ifdef _AIX + [[maybe_unused]] std::string_view what{"failed to write formatted output: Broken pipe"}; +#else + [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"}; +#endif + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode(file, "hello", std::make_format_args())); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::vprint_unicode(file, "\n", std::make_format_args()); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_unicode(file, "\n", std::make_format_args()); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + test_wide_stream(); +#endif + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp --- a/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp @@ -19,6 +19,7 @@ /* Constant Value __cpp_lib_char8_t 201907L [C++20] + __cpp_lib_print 202207L [C++23] */ #include @@ -30,18 +31,30 @@ # error "__cpp_lib_char8_t should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + #elif TEST_STD_VER == 14 # ifdef __cpp_lib_char8_t # error "__cpp_lib_char8_t should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + #elif TEST_STD_VER == 17 # ifdef __cpp_lib_char8_t # error "__cpp_lib_char8_t should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + #elif TEST_STD_VER == 20 # if defined(__cpp_char8_t) @@ -57,6 +70,10 @@ # endif # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + #elif TEST_STD_VER == 23 # if defined(__cpp_char8_t) @@ -72,6 +89,19 @@ # endif # endif +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++23" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++23" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + #elif TEST_STD_VER > 23 # if defined(__cpp_char8_t) @@ -87,5 +117,18 @@ # endif # endif +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++26" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++26" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + #endif // TEST_STD_VER > 23 diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// WARNING: This test was generated by generate_feature_test_macro_components.py +// and should not be edited manually. +// +// clang-format off + +// + +// Test the feature test macros defined by + +/* Constant Value + __cpp_lib_print 202207L [C++23] +*/ + +#include +#include "test_macros.h" + +#if TEST_STD_VER < 14 + +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 14 + +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 17 + +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 20 + +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++23" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++23" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#elif TEST_STD_VER > 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++26" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++26" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#endif // TEST_STD_VER > 23 + diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp --- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp @@ -138,6 +138,7 @@ __cpp_lib_out_ptr 202106L [C++23] __cpp_lib_parallel_algorithm 201603L [C++17] __cpp_lib_polymorphic_allocator 201902L [C++20] + __cpp_lib_print 202207L [C++23] __cpp_lib_quoted_string_io 201304L [C++14] __cpp_lib_ranges 202207L [C++20] __cpp_lib_ranges_as_rvalue 202207L [C++23] @@ -674,6 +675,10 @@ # error "__cpp_lib_polymorphic_allocator should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + # ifdef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should not be defined before c++14" # endif @@ -1421,6 +1426,10 @@ # error "__cpp_lib_polymorphic_allocator should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++14" # endif @@ -2342,6 +2351,10 @@ # error "__cpp_lib_polymorphic_allocator should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++17" # endif @@ -3530,6 +3543,10 @@ # endif # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++23" +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++20" # endif @@ -4889,6 +4906,19 @@ # endif # endif +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++23" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++23" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++23" # endif @@ -6428,6 +6458,19 @@ # endif # endif +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++26" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++26" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++26" # endif diff --git a/libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp b/libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp --- a/libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp @@ -6,7 +6,6 @@ //===----------------------------------------------------------------------===// // UNSUPPORTED: c++03, c++11, c++14, c++17 -// UNSUPPORTED: libcpp-has-no-incomplete-format // UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME // This version runs the test when the platform has Unicode support. diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot --- a/libcxx/utils/ci/run-buildbot +++ b/libcxx/utils/ci/run-buildbot @@ -248,6 +248,7 @@ --exclude 'ostream.pass.cpp' \ --exclude 'std_format_spec_string_unicode.bench.cpp' \ --exclude 'transcoding.pass.cpp' \ + --exclude 'vprint_unicode_windows.pass.cpp' \ --exclude 'underflow.pass.cpp' \ || false diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -771,6 +771,12 @@ "test_suite_guard": "!defined(_LIBCPP_AVAILABILITY_HAS_NO_PMR)", "libcxx_guard": "!defined(_LIBCPP_AVAILABILITY_HAS_NO_PMR)", }, + { + "name": "__cpp_lib_print", + "values": {"c++23": 202207}, + "headers": ["ostream", "print"], + "unimplemented": True, + }, { "name": "__cpp_lib_quoted_string_io", "values": {"c++14": 201304}, diff --git a/libcxx/utils/libcxx/test/header_information.py b/libcxx/utils/libcxx/test/header_information.py --- a/libcxx/utils/libcxx/test/header_information.py +++ b/libcxx/utils/libcxx/test/header_information.py @@ -41,10 +41,11 @@ "iostream": "// UNSUPPORTED: no-localization", "istream": "// UNSUPPORTED: no-localization", "latch": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17", - "locale.h": "// UNSUPPORTED: no-localization", "locale": "// UNSUPPORTED: no-localization", + "locale.h": "// UNSUPPORTED: no-localization", "mutex": "// UNSUPPORTED: no-threads, c++03", "ostream": "// UNSUPPORTED: no-localization", + "print": "// UNSUPPORTED: availability-fp_to_chars-missing", # TODO PRINT investigate "regex": "// UNSUPPORTED: no-localization", "semaphore": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17", "shared_mutex": "// UNSUPPORTED: no-threads, c++03, c++11",