[libcxx-commits] [libcxx] [libc++] Fix checks for terminal and flushes in std::print() (PR #70321)
Dimitrij Mijoski via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Mar 10 08:30:29 PDT 2026
https://github.com/dimztimz updated https://github.com/llvm/llvm-project/pull/70321
>From e7eb80183656f9ea7dbfca54e283d541bfc7a7c8 Mon Sep 17 00:00:00 2001
From: Dimitrij Mijoski <dmjpp at hotmail.com>
Date: Tue, 16 Dec 2025 19:15:47 +0100
Subject: [PATCH 1/3] [libc++] Fix checks for terminal and flushes in
std::print()
The check whether a stream is associated with a terminal or not and the
flushing of the stream in std::print() is needed only on Windows.
Additionally, the correct flush should be used. When std::print is
called with a C stream, std::fflush() should be used. When it is called
with C++ ostream, ostream::flush() should be called.
Because POSIX does not have a separate Unicode API for terminal output,
checking for terminal (isatty) and flushing is not needed at all.
Moreover, isatty has noticeable performance cost.
See also https://wg21.link/LWG4044.
Fixes #70142
---
libcxx/include/__ostream/print.h | 8 +-
libcxx/include/print | 37 +++------
libcxx/src/print.cpp | 5 +-
.../vprint_unicode.pass.cpp | 35 ++++++--
.../print.fun/vprint_unicode_posix.pass.cpp | 79 -------------------
.../print.fun/vprint_unicode_windows.pass.cpp | 36 ++-------
6 files changed, 54 insertions(+), 146 deletions(-)
delete mode 100644 libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp
diff --git a/libcxx/include/__ostream/print.h b/libcxx/include/__ostream/print.h
index c5906c41b95b5..dd0955256550e 100644
--- a/libcxx/include/__ostream/print.h
+++ b/libcxx/include/__ostream/print.h
@@ -87,7 +87,7 @@ _LIBCPP_EXPORTED_FROM_ABI FILE* __get_ostream_file(ostream& __os);
# if _LIBCPP_HAS_UNICODE
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
_LIBCPP_HIDE_FROM_ABI void __vprint_unicode(ostream& __os, string_view __fmt, format_args __args, bool __write_nl) {
-# if _LIBCPP_AVAILABILITY_HAS_PRINT == 0
+# if _LIBCPP_AVAILABILITY_HAS_PRINT == 0 || !defined(_LIBCPP_WIN32API)
return std::__vprint_nonunicode(__os, __fmt, __args, __write_nl);
# else
FILE* __file = std::__get_ostream_file(__os);
@@ -110,10 +110,8 @@ _LIBCPP_HIDE_FROM_ABI void __vprint_unicode(ostream& __os, string_view __fmt, fo
# endif // _LIBCPP_HAS_EXCEPTIONS
ostream::sentry __s(__os);
if (__s) {
-# ifndef _LIBCPP_WIN32API
- __print::__vprint_unicode_posix(__file, __fmt, __args, __write_nl, true);
-# elif _LIBCPP_HAS_WIDE_CHARACTERS
- __print::__vprint_unicode_windows(__file, __fmt, __args, __write_nl, true);
+# if _LIBCPP_HAS_WIDE_CHARACTERS
+ __print::__vprint_unicode_windows(__file, __fmt, __args, __write_nl);
# else
# error "Windows builds with wchar_t disabled are not supported."
# endif
diff --git a/libcxx/include/print b/libcxx/include/print
index 0ff314c22dcd9..253b4af4f7971 100644
--- a/libcxx/include/print
+++ b/libcxx/include/print
@@ -69,8 +69,6 @@ _LIBCPP_EXPORTED_FROM_ABI bool __is_windows_terminal(FILE* __stream);
// 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_WIDE_CHARACTERS
-# elif __has_include(<unistd.h>)
-_LIBCPP_EXPORTED_FROM_ABI bool __is_posix_terminal(FILE* __stream);
# endif // _LIBCPP_WIN32API
# if _LIBCPP_STD_VER >= 23
@@ -202,14 +200,10 @@ _LIBCPP_HIDE_FROM_ABI inline bool __is_terminal([[maybe_unused]] FILE* __stream)
// the behavior in the test. This is not part of the public API.
# ifdef _LIBCPP_TESTING_PRINT_IS_TERMINAL
return _LIBCPP_TESTING_PRINT_IS_TERMINAL(__stream);
-# elif _LIBCPP_AVAILABILITY_HAS_PRINT == 0 || !_LIBCPP_HAS_TERMINAL
- return false;
-# elif defined(_LIBCPP_WIN32API)
+# elif _LIBCPP_AVAILABILITY_HAS_PRINT && _LIBCPP_HAS_TERMINAL && defined(_LIBCPP_WIN32API)
return std::__is_windows_terminal(__stream);
-# elif __has_include(<unistd.h>)
- return std::__is_posix_terminal(__stream);
# else
-# error "Provide a way to determine whether a FILE* is a terminal"
+ return false;
# endif
}
@@ -236,26 +230,11 @@ __vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool
// 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 <class = void> // 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);
-}
# if _LIBCPP_HAS_WIDE_CHARACTERS
template <class = void> // 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);
-
+__vprint_unicode_windows([[maybe_unused]] FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) {
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.
@@ -316,9 +295,15 @@ __vprint_unicode([[maybe_unused]] FILE* __stream,
// Windows there is a different API. This API requires transcoding.
# ifndef _LIBCPP_WIN32API
- __print::__vprint_unicode_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
+ __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
# elif _LIBCPP_HAS_WIDE_CHARACTERS
- __print::__vprint_unicode_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
+ if (__print::__is_terminal(__stream)) {
+ // TODO PRINT Should flush errors throw too?
+ std::fflush(__stream);
+ __print::__vprint_unicode_windows(__stream, __fmt, __args, __write_nl);
+ } else {
+ __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
+ }
# else
# error "Windows builds with wchar_t disabled are not supported."
# endif
diff --git a/libcxx/src/print.cpp b/libcxx/src/print.cpp
index 82cf2afd052e2..a5edcc4632195 100644
--- a/libcxx/src/print.cpp
+++ b/libcxx/src/print.cpp
@@ -64,9 +64,12 @@ __write_to_windows_console([[maybe_unused]] FILE* __stream, [[maybe_unused]] wst
}
# endif // _LIBCPP_HAS_WIDE_CHARACTERS
-#elif defined(HAS_FILENO_AND_ISATTY) // !_LIBCPP_WIN32API
+#elif defined(HAS_FILENO_AND_ISATTY) && _LIBCPP_AVAILABILITY_MINIMUM_HEADER_VERSION < 23 // !_LIBCPP_WIN32API
+_LIBCPP_DIAGNOSTIC_PUSH
+_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wmissing-prototypes")
_LIBCPP_EXPORTED_FROM_ABI bool __is_posix_terminal(FILE* __stream) { return isatty(fileno(__stream)); }
+_LIBCPP_DIAGNOSTIC_POP
#endif
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/libcxx/input.output/iostream.format/output.streams/ostream.formatted/ostream.formatted.print/vprint_unicode.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/output.streams/ostream.formatted/ostream.formatted.print/vprint_unicode.pass.cpp
index 52d8500f7fa3a..27387311b9f16 100644
--- a/libcxx/test/libcxx/input.output/iostream.format/output.streams/ostream.formatted/ostream.formatted.print/vprint_unicode.pass.cpp
+++ b/libcxx/test/libcxx/input.output/iostream.format/output.streams/ostream.formatted/ostream.formatted.print/vprint_unicode.pass.cpp
@@ -13,13 +13,8 @@
// XFAIL: availability-fp_to_chars-missing
-// When std::print is unavailable, we don't rely on an implementation of
-// std::__is_terminal and we always assume a non-unicode and non-terminal
-// output.
-// XFAIL: availability-print-missing
-
// Clang modules do not work with the definiton of _LIBCPP_TESTING_PRINT_IS_TERMINAL
-// XFAIL: clang-modules-build
+// ADDITIONAL_COMPILE_FLAGS: -fno-modules
// <ostream>
// Tests the implementation of
@@ -81,14 +76,22 @@ static void test_is_terminal_file_stream() {
assert(stream.is_open());
assert(stream.good());
std::print(stream, "test");
+#ifdef _WIN32
assert(is_terminal_calls == 1);
+#else
+ assert(is_terminal_calls == 0);
+#endif
}
{
std::ofstream stream(filename);
assert(stream.is_open());
assert(stream.good());
std::print(stream, "test");
+#ifdef _WIN32
assert(is_terminal_calls == 2);
+#else
+ assert(is_terminal_calls == 0);
+#endif
}
}
@@ -105,7 +108,11 @@ static void test_is_terminal_rdbuf_derived_from_filebuf() {
std::ostream stream(&buf);
std::print(stream, "test");
+#ifdef _WIN32
assert(is_terminal_calls == 1);
+#else
+ assert(is_terminal_calls == 0);
+#endif
}
// When the stream is cout, clog, or cerr, its FILE* may be a terminal. Validate
@@ -115,15 +122,27 @@ static void test_is_terminal_std_cout_cerr_clog() {
is_terminal_result = false;
{
std::print(std::cout, "test");
+#ifdef _WIN32
assert(is_terminal_calls == 1);
+#else
+ assert(is_terminal_calls == 0);
+#endif
}
{
std::print(std::cerr, "test");
+#ifdef _WIN32
assert(is_terminal_calls == 2);
+#else
+ assert(is_terminal_calls == 0);
+#endif
}
{
std::print(std::clog, "test");
+#ifdef _WIN32
assert(is_terminal_calls == 3);
+#else
+ assert(is_terminal_calls == 0);
+#endif
}
}
@@ -156,7 +175,11 @@ static void test_is_terminal_is_flushed() {
// A terminal sync is called.
is_terminal_result = true;
std::print(stream, "");
+#ifdef _WIN32
assert(buf.sync_calls == 1); // only called from the destructor of the sentry
+#else
+ assert(buf.sync_calls == 0);
+#endif
}
int main(int, char**) {
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
deleted file mode 100644
index b89d02ba99425..0000000000000
--- a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-
-// fmemopen is available starting in Android M (API 23)
-// XFAIL: target={{.+}}-android{{(eabi)?(21|22)}}
-
-// REQUIRES: has-unix-headers
-
-// <print>
-
-// 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 <algorithm>
-#include <array>
-#include <cassert>
-#include <cstdio>
-#include <print>
-
-#include "test_macros.h"
-
-int main(int, char**) {
- std::array<char, 100> 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
index bcd1d05a3aeeb..162579027831b 100644
--- 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
@@ -13,7 +13,7 @@
// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
// Clang modules do not work with the definiton of _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION
-// XFAIL: clang-modules-build
+// ADDITIONAL_COMPILE_FLAGS: -fno-modules
// XFAIL: availability-fp_to_chars-missing
@@ -21,8 +21,7 @@
// Tests the implementation of
// void __print::__vprint_unicode_windows(FILE* __stream, string_view __fmt,
-// format_args __args, bool __write_nl,
-// bool __is_terminal);
+// format_args __args, bool __write_nl);
//
// 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
@@ -62,40 +61,19 @@ 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);
+ std::__print::__vprint_unicode_windows(file, " world", std::make_format_args(), false);
}
-// When the output is a file the data is written as-is.
-// When the output is a "terminal" invalid UTF-8 input is flagged.
+// 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<long>(input.size()));
- std::fclose(file);
-
- file = std::fopen(filename.c_str(), "rb");
- assert(file);
-
- std::vector<char> 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);
+ std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false);
+ assert(std::ftell(file) == 0);
+ std::fclose(file);
}
static void test() {
>From d5f0e2372039c1470da871aceafd59872e4afbfd Mon Sep 17 00:00:00 2001
From: Dimitrij Mijoski <dmjpp at hotmail.com>
Date: Tue, 10 Mar 2026 16:24:54 +0100
Subject: [PATCH 2/3] fixup! [libc++] Fix checks for terminal and flushes in
std::print()
simplify and add ifdef guards to __is_terminal
---
libcxx/include/print | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/libcxx/include/print b/libcxx/include/print
index 253b4af4f7971..c9651ccc8ae9d 100644
--- a/libcxx/include/print
+++ b/libcxx/include/print
@@ -195,17 +195,17 @@ inline constexpr bool __use_unicode_execution_charset = _MSVC_EXECUTION_CHARACTE
inline constexpr bool __use_unicode_execution_charset = true;
# endif
+#if defined(_LIBCPP_WIN32API) && _LIBCPP_AVAILABILITY_HAS_PRINT
_LIBCPP_HIDE_FROM_ABI inline bool __is_terminal([[maybe_unused]] FILE* __stream) {
// The macro _LIBCPP_TESTING_PRINT_IS_TERMINAL is used to change
// the behavior in the test. This is not part of the public API.
# ifdef _LIBCPP_TESTING_PRINT_IS_TERMINAL
return _LIBCPP_TESTING_PRINT_IS_TERMINAL(__stream);
-# elif _LIBCPP_AVAILABILITY_HAS_PRINT && _LIBCPP_HAS_TERMINAL && defined(_LIBCPP_WIN32API)
- return std::__is_windows_terminal(__stream);
# else
- return false;
+ return std::__is_windows_terminal(__stream);
# endif
}
+#endif
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
_LIBCPP_HIDE_FROM_ABI inline void
>From 32b29d2ae70fe995541a6f0a68e4959aea535881 Mon Sep 17 00:00:00 2001
From: Dimitrij Mijoski <dmjpp at hotmail.com>
Date: Tue, 10 Mar 2026 16:30:12 +0100
Subject: [PATCH 3/3] fixup! fixup! [libc++] Fix checks for terminal and
flushes in std::print()
clang-format
---
libcxx/include/print | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/libcxx/include/print b/libcxx/include/print
index c9651ccc8ae9d..5281536fd5830 100644
--- a/libcxx/include/print
+++ b/libcxx/include/print
@@ -69,7 +69,7 @@ _LIBCPP_EXPORTED_FROM_ABI bool __is_windows_terminal(FILE* __stream);
// 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_WIDE_CHARACTERS
-# endif // _LIBCPP_WIN32API
+# endif // _LIBCPP_WIN32API
# if _LIBCPP_STD_VER >= 23
@@ -195,17 +195,17 @@ inline constexpr bool __use_unicode_execution_charset = _MSVC_EXECUTION_CHARACTE
inline constexpr bool __use_unicode_execution_charset = true;
# endif
-#if defined(_LIBCPP_WIN32API) && _LIBCPP_AVAILABILITY_HAS_PRINT
+# if defined(_LIBCPP_WIN32API) && _LIBCPP_AVAILABILITY_HAS_PRINT
_LIBCPP_HIDE_FROM_ABI inline bool __is_terminal([[maybe_unused]] FILE* __stream) {
// The macro _LIBCPP_TESTING_PRINT_IS_TERMINAL is used to change
// the behavior in the test. This is not part of the public API.
-# ifdef _LIBCPP_TESTING_PRINT_IS_TERMINAL
+# ifdef _LIBCPP_TESTING_PRINT_IS_TERMINAL
return _LIBCPP_TESTING_PRINT_IS_TERMINAL(__stream);
-# else
+# else
return std::__is_windows_terminal(__stream);
-# endif
+# endif
}
-#endif
+# endif
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
_LIBCPP_HIDE_FROM_ABI inline void
More information about the libcxx-commits
mailing list