diff mbox series

libstdc++: Fix race condition in std::ios_base::Init construction [PR111676]

Message ID 20240922165213.1435833-1-jwakely@redhat.com
State New
Headers show
Series libstdc++: Fix race condition in std::ios_base::Init construction [PR111676] | expand

Commit Message

Jonathan Wakely Sept. 22, 2024, 4:51 p.m. UTC
Tested x86_64-linux.

-- >8 --

The current implementation of std::ios_base::Init::Init() uses an atomic
reference count to ensure that the global iostreams are only initialized
once. However, it doesn't ensure that other threads will wait for that
initialization to finish before trying to use the streams. The reference
count also doesn't ensure that only one thread flushes the streams on
process exit.

libstdc++-v3/ChangeLog:

	PR libstdc++/111676
	* src/c++98/ios_init.cc (init_once): New function.
	(ios_base::Init::Init()): Move initialization logic to init_once
	and use local static initialization to call it.
---
 libstdc++-v3/src/c++98/ios_init.cc | 84 +++++++++++++++++-------------
 1 file changed, 49 insertions(+), 35 deletions(-)
diff mbox series

Patch

diff --git a/libstdc++-v3/src/c++98/ios_init.cc b/libstdc++-v3/src/c++98/ios_init.cc
index 6e2e5014cf0..14f8bb678b0 100644
--- a/libstdc++-v3/src/c++98/ios_init.cc
+++ b/libstdc++-v3/src/c++98/ios_init.cc
@@ -75,49 +75,63 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
   extern wostream wclog;
 #endif
 
-  ios_base::Init::Init()
+namespace
+{
+  bool init_once(bool* synced_with_stdio, _Atomic_word* refcount)
   {
-    if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
-      {
-	// Standard streams default to synced with "C" operations.
-	_S_synced_with_stdio = true;
+    // Standard streams default to synced with "C" operations.
+    *synced_with_stdio = true;
 
-	new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
-	new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
-	new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);
+    new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
+    new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
+    new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);
 
-	// The standard streams are constructed once only and never
-	// destroyed.
-	new (&cout) ostream(&buf_cout_sync);
-	new (&cin) istream(&buf_cin_sync);
-	new (&cerr) ostream(&buf_cerr_sync);
-	new (&clog) ostream(&buf_cerr_sync);
-	cin.tie(&cout);
-	cerr.setf(ios_base::unitbuf);
-	// _GLIBCXX_RESOLVE_LIB_DEFECTS
-	// 455. cerr::tie() and wcerr::tie() are overspecified.
-	cerr.tie(&cout);
+    // The standard streams are constructed once only and never
+    // destroyed.
+    new (&cout) ostream(&buf_cout_sync);
+    new (&cin) istream(&buf_cin_sync);
+    new (&cerr) ostream(&buf_cerr_sync);
+    new (&clog) ostream(&buf_cerr_sync);
+    cin.tie(&cout);
+    cerr.setf(ios_base::unitbuf);
+    // _GLIBCXX_RESOLVE_LIB_DEFECTS
+    // 455. cerr::tie() and wcerr::tie() are overspecified.
+    cerr.tie(&cout);
 
 #ifdef _GLIBCXX_USE_WCHAR_T
-	new (&buf_wcout_sync) stdio_sync_filebuf<wchar_t>(stdout);
-	new (&buf_wcin_sync) stdio_sync_filebuf<wchar_t>(stdin);
-	new (&buf_wcerr_sync) stdio_sync_filebuf<wchar_t>(stderr);
+    new (&buf_wcout_sync) stdio_sync_filebuf<wchar_t>(stdout);
+    new (&buf_wcin_sync) stdio_sync_filebuf<wchar_t>(stdin);
+    new (&buf_wcerr_sync) stdio_sync_filebuf<wchar_t>(stderr);
 
-	new (&wcout) wostream(&buf_wcout_sync);
-	new (&wcin) wistream(&buf_wcin_sync);
-	new (&wcerr) wostream(&buf_wcerr_sync);
-	new (&wclog) wostream(&buf_wcerr_sync);
-	wcin.tie(&wcout);
-	wcerr.setf(ios_base::unitbuf);
-	wcerr.tie(&wcout);
+    new (&wcout) wostream(&buf_wcout_sync);
+    new (&wcin) wistream(&buf_wcin_sync);
+    new (&wcerr) wostream(&buf_wcerr_sync);
+    new (&wclog) wostream(&buf_wcerr_sync);
+    wcin.tie(&wcout);
+    wcerr.setf(ios_base::unitbuf);
+    wcerr.tie(&wcout);
 #endif
 
-	// NB: Have to set refcount above one, so that standard
-	// streams are not re-initialized with uses of ios_base::Init
-	// besides <iostream> static object, ie just using <ios> with
-	// ios_base::Init objects.
-	__gnu_cxx::__atomic_add_dispatch(&_S_refcount, 1);
-      }
+    *refcount = 1;
+
+    return true;
+  }
+} // namespace
+
+  ios_base::Init::Init()
+  {
+#if defined(__GTHREADS) && !defined(__cpp_threadsafe_static_init)
+# error "Initialization of global iostreams requires thread-safe static init"
+#endif
+
+    [[__maybe_unused__]]
+    static const bool init = init_once(&_S_synced_with_stdio, &_S_refcount);
+
+    // NB: Have to set refcount above one, so that standard
+    // streams are not re-initialized with uses of ios_base::Init
+    // besides <iostream> static object, ie just using <ios> with
+    // ios_base::Init objects.
+    __gnu_cxx::__atomic_add_dispatch(&_S_refcount, 1);
   }
 
   ios_base::Init::~Init()