// -*- C++ -*- //===----------------------------------------------------------------------===// // // 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 _LIBCPP_SYNCSTREAM #define _LIBCPP_SYNCSTREAM /* syncstream synopsis #include // see [ostream.syn] namespace std { template class basic_syncbuf; // [syncstream.syncbuf.special], specialized algorithms template void swap(basic_syncbuf&, basic_syncbuf&); using syncbuf = basic_syncbuf; using wsyncbuf = basic_syncbuf; template class basic_osyncstream; using osyncstream = basic_osyncstream; using wosyncstream = basic_osyncstream; template class basic_syncbuf : public basic_streambuf { public: using char_type = charT; using int_type = typename traits::int_type; using pos_type = typename traits::pos_type; using off_type = typename traits::off_type; using traits_type = traits; using allocator_type = Allocator; using streambuf_type = basic_streambuf; // [syncstream.syncbuf.cons], construction and destruction explicit basic_syncbuf(streambuf_type* obuf = nullptr) : basic_syncbuf(obuf, Allocator()) {} basic_syncbuf(streambuf_type*, const Allocator&); basic_syncbuf(basic_syncbuf&&); ~basic_syncbuf(); // [syncstream.syncbuf.assign], assignment and swap basic_syncbuf& operator=(basic_syncbuf&&); void swap(basic_syncbuf&); // [syncstream.syncbuf.members], member functions bool emit(); streambuf_type* get_wrapped() const noexcept; allocator_type get_allocator() const noexcept; void set_emit_on_sync(bool) noexcept; protected: // [syncstream.syncbuf.virtuals], overridden virtual functions int sync() override; private: streambuf_type* wrapped; // exposition only bool emit_on_sync{}; // exposition only }; // [syncstream.syncbuf.special], specialized algorithms template void swap(basic_syncbuf&, basic_syncbuf&); template class basic_osyncstream : public basic_ostream { public: using char_type = charT; using int_type = typename traits::int_type; using pos_type = typename traits::pos_type; using off_type = typename traits::off_type; using traits_type = traits; using allocator_type = Allocator; using streambuf_type = basic_streambuf; using syncbuf_type = basic_syncbuf; // [syncstream.osyncstream.cons], construction and destruction basic_osyncstream(streambuf_type*, const Allocator&); explicit basic_osyncstream(streambuf_type* obuf) : basic_osyncstream(obuf, Allocator()) {} basic_osyncstream(basic_ostream& os, const Allocator& allocator) : basic_osyncstream(os.rdbuf(), allocator) {} explicit basic_osyncstream(basic_ostream& os) : basic_osyncstream(os, Allocator()) {} basic_osyncstream(basic_osyncstream&&) noexcept; ~basic_osyncstream(); // [syncstream.osyncstream.assign], assignment basic_osyncstream& operator=(basic_osyncstream&&); // [syncstream.osyncstream.members], member functions void emit(); streambuf_type* get_wrapped() const noexcept; syncbuf_type* rdbuf() const noexcept { return const_cast(addressof(sb)); } private: syncbuf_type sb; // exposition only }; } */ #include <__config> #include <__utility/move.h> #include #include // required for declaration of default arguments #include #include #ifndef _LIBCPP_HAS_NO_THREADS # include # include # include #endif // standard-mandated includes // [syncstream.syn] #include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header #endif _LIBCPP_PUSH_MACROS #include <__undef_macros> _LIBCPP_BEGIN_NAMESPACE_STD #if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM) // [syncstream.syncbuf.overview]/1 // Class template basic_syncbuf stores character data written to it, // known as the associated output, into internal buffers allocated // using the object's allocator. The associated output is transferred // to the wrapped stream buffer object *wrapped when emit() is called // or when the basic_syncbuf object is destroyed. Such transfers are // atomic with respect to transfers by other basic_syncbuf objects // with the same wrapped stream buffer object. // // This helper singleton is used to implement the required // synchronisation guarantees. # ifndef _LIBCPP_HAS_NO_THREADS class __wrapped_streambuf_mutex { _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex() = default; public: __wrapped_streambuf_mutex(const __wrapped_streambuf_mutex&) = delete; __wrapped_streambuf_mutex& operator=(const __wrapped_streambuf_mutex&) = delete; _LIBCPP_HIDE_FROM_ABI void __inc_reference([[maybe_unused]] void* __ptr) { _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to"); unique_lock __lock{__mutex_}; ++__lut_[reinterpret_cast(__ptr)].__count; } // pre: __ptr is in __lut_ _LIBCPP_HIDE_FROM_ABI void __dec_reference([[maybe_unused]] void* __ptr) noexcept { unique_lock __lock{__mutex_}; auto __it = __get_it(__ptr); if (__it->second.__count == 1) __lut_.erase(__it); else --__it->second.__count; } // TODO // This function causes emit() aquire two mutexes: // - __mutex_ shared // _ __get_it(__ptr)->second.__mutex exclusive // // Instead store a pointer to __get_it(__ptr)->second.__mutex when // calling __inc_reference. // // pre: __ptr is in __lut_ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI lock_guard __get_lock([[maybe_unused]] void* __ptr) noexcept { shared_lock __lock{__mutex_}; return lock_guard{__get_it(__ptr)->second.__mutex}; } // This function is used for testing. // // It is allowed to call this function with a non-registered pointer. [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t __get_count([[maybe_unused]] void* __ptr) noexcept { _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to"); shared_lock __lock{__mutex_}; auto __it = __lut_.find(reinterpret_cast(__ptr)); return __it != __lut_.end() ? __it->second.__count : 0; } [[nodiscard]] static _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex& __instance() noexcept { static __wrapped_streambuf_mutex __result; return __result; } private: struct __value { mutex __mutex; size_t __count{0}; }; shared_mutex __mutex_; map __lut_; [[nodiscard]] _LIBCPP_HIDE_FROM_ABI map::iterator __get_it(void* __ptr) noexcept { _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to"); auto __it = __lut_.find(reinterpret_cast(__ptr)); _LIBCPP_ASSERT_INTERNAL(__it != __lut_.end(), "using a wrapped streambuf that has not been registered"); _LIBCPP_ASSERT_INTERNAL(__it->second.__count >= 1, "found an inactive streambuf wrapper"); return __it; } }; # endif // _LIBCPP_HAS_NO_THREADS // basic_syncbuf // The class uses a basic_string<_CharT, _Traits, _Allocator> as // internal buffer. Per [syncstream.syncbuf.cons]/4 // Remarks: A copy of allocator is used to allocate memory for // internal buffers holding the associated output. // // Therefore the allocator used in the constructor is passed to the // basic_string. The class does not keep a copy of this allocator. template class _LIBCPP_TEMPLATE_VIS basic_syncbuf : public basic_streambuf<_CharT, _Traits> { public: using char_type = _CharT; using traits_type = _Traits; using int_type = typename traits_type::int_type; using pos_type = typename traits_type::pos_type; using off_type = typename traits_type::off_type; using allocator_type = _Allocator; using streambuf_type = basic_streambuf<_CharT, _Traits>; // [syncstream.syncbuf.cons], construction and destruction _LIBCPP_HIDE_FROM_ABI explicit basic_syncbuf(streambuf_type* __obuf = nullptr) : basic_syncbuf(__obuf, _Allocator()) {} _LIBCPP_HIDE_FROM_ABI basic_syncbuf(streambuf_type* __obuf, _Allocator const& __alloc) : __wrapped_(__obuf), __str_(__alloc) { __inc_reference(); } _LIBCPP_HIDE_FROM_ABI basic_syncbuf(basic_syncbuf&& __other) : __wrapped_(__other.get_wrapped()), __str_(std::move(__other.__str_)), __emit_on_sync_(__other.__emit_on_sync_) { __move_common(__other); } _LIBCPP_HIDE_FROM_ABI ~basic_syncbuf() { # ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { # endif // _LIBCPP_HAS_NO_EXCEPTIONS emit(); # ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } # endif // _LIBCPP_HAS_NO_EXCEPTIONS __dec_reference(); } // [syncstream.syncbuf.assign], assignment and swap _LIBCPP_HIDE_FROM_ABI basic_syncbuf& operator=(basic_syncbuf&& __other) { // The function is specified to call emit. This call should // propagate the exception thrown. emit(); __dec_reference(); __wrapped_ = __other.get_wrapped(); __str_ = std::move(__other.__str_); __emit_on_sync_ = __other.__emit_on_sync_; __move_common(__other); return *this; } _LIBCPP_HIDE_FROM_ABI void swap(basic_syncbuf& __other) { _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR( allocator_traits<_Allocator>::propagate_on_container_swap::value || get_allocator() == __other.get_allocator(), "violates the mandated swap precondition"); basic_syncbuf __tmp(std::move(__other)); __other = std::move(*this); *this = std::move(__tmp); } // [syncstream.syncbuf.members], member functions _LIBCPP_HIDE_FROM_ABI bool emit() { return emit(false); } _LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __wrapped_; } _LIBCPP_HIDE_FROM_ABI allocator_type get_allocator() const noexcept { return __str_.get_allocator(); } _LIBCPP_HIDE_FROM_ABI void set_emit_on_sync(bool __b) noexcept { __emit_on_sync_ = __b; } protected: // [syncstream.syncbuf.virtuals], overridden virtual functions _LIBCPP_HIDE_FROM_ABI_VIRTUAL int sync() override { if (__emit_on_sync_ && !emit(true)) return -1; return 0; } _LIBCPP_HIDE_FROM_ABI_VIRTUAL int_type overflow(int_type __c = traits_type::eof()) override { if (traits_type::eq_int_type(__c, traits_type::eof())) return traits_type::not_eof(__c); if (this->pptr() == this->epptr()) { # ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { # endif size_t __size = __str_.size(); __str_.resize(__str_.capacity() + 1); _LIBCPP_ASSERT_INTERNAL(__str_.size() > __size, "the buffer hasn't grown"); char_type* __p = static_cast(__str_.data()); this->setp(__p, __p + __str_.size()); this->pbump(__size); # ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { return traits_type::eof(); } # endif } return this->sputc(traits_type::to_char_type(__c)); } private: streambuf_type* __wrapped_; // TODO Use a more generic buffer. // That buffer should be light with almost no additional headers. Then // it can be use here, the __retarget_buffer, and place that use // the now deprecated get_temporary_buffer basic_string<_CharT, _Traits, _Allocator> __str_; bool __emit_on_sync_{false}; _LIBCPP_HIDE_FROM_ABI bool emit(bool __flush) { if (!__wrapped_) return false; # ifndef _LIBCPP_HAS_NO_THREADS lock_guard __lock = __wrapped_streambuf_mutex::__instance().__get_lock(__wrapped_); # endif bool __result = true; if (this->pptr() != this->pbase()) { _LIBCPP_ASSERT_INTERNAL(this->pbase() && this->pptr() && this->epptr(), "all put area pointers shold be valid"); // The __str_ does not know how much of its buffer is used. This // information is extracted from the information of the base class. __result &= (__wrapped_->sputn(this->pbase(), this->pptr() - this->pbase()) != -1); // Clears the buffer, but keeps the contents (and) size of the // internal buffer. this->setp(this->pbase(), this->epptr()); } if (__flush) __result &= (__wrapped_->pubsync() != -1); return __result; } _LIBCPP_HIDE_FROM_ABI void __move_common(basic_syncbuf& __other) { // Adjust the put area pointers to our buffer. char_type* __p = static_cast(__str_.data()); this->setp(__p, __p + __str_.size()); this->pbump(__other.pptr() - __other.pbase()); // Clear __other_ so the destructor will act as a NOP. __other.setp(nullptr, nullptr); __other.__wrapped_ = nullptr; } _LIBCPP_HIDE_FROM_ABI void __inc_reference() { # ifndef _LIBCPP_HAS_NO_THREADS if (__wrapped_) __wrapped_streambuf_mutex::__instance().__inc_reference(__wrapped_); # endif } _LIBCPP_HIDE_FROM_ABI void __dec_reference() noexcept { # ifndef _LIBCPP_HAS_NO_THREADS if (__wrapped_) __wrapped_streambuf_mutex::__instance().__dec_reference(__wrapped_); # endif } }; using std::syncbuf; # ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::wsyncbuf; # endif // [syncstream.syncbuf.special], specialized algorithms template _LIBCPP_HIDE_FROM_ABI void swap(basic_syncbuf<_CharT, _Traits, _Allocator>& __lhs, basic_syncbuf<_CharT, _Traits, _Allocator>& __rhs) { __lhs.swap(__rhs); } // basic_osyncstream template class _LIBCPP_TEMPLATE_VIS basic_osyncstream : public basic_ostream<_CharT, _Traits> { public: using char_type = _CharT; using traits_type = _Traits; using int_type = typename traits_type::int_type; using pos_type = typename traits_type::pos_type; using off_type = typename traits_type::off_type; using allocator_type = _Allocator; using streambuf_type = basic_streambuf; using syncbuf_type = basic_syncbuf; // [syncstream.osyncstream.cons], construction and destruction _LIBCPP_HIDE_FROM_ABI basic_osyncstream(streambuf_type* __obuf, allocator_type const& __alloc) : basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(__obuf, __alloc) {} _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(streambuf_type* __obuf) : basic_osyncstream(__obuf, allocator_type()) {} _LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_ostream& __os, allocator_type const& __alloc) : basic_osyncstream(__os.rdbuf(), __alloc) {} _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(basic_ostream& __os) : basic_osyncstream(__os, allocator_type()) {} _LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_osyncstream&& __other) noexcept : basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(std::move(__other.__sb_)) { this->set_rdbuf(std::addressof(__sb_)); } // [syncstream.osyncstream.assign], assignment _LIBCPP_HIDE_FROM_ABI basic_osyncstream& operator=(basic_osyncstream&& __other) = default; // [syncstream.osyncstream.members], member functions _LIBCPP_HIDE_FROM_ABI void emit() { // The basic_ostream::put places the sentry in a try // catch, this does not match the wording of the standard // [ostream.unformatted] // TODO validate other unformatted output functions. typename basic_ostream::sentry __s(*this); if (__s) { # ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { # endif if (__sb_.emit() == false) this->setstate(ios::badbit); # ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { this->__set_badbit_and_consider_rethrow(); } # endif } } _LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __sb_.get_wrapped(); } _LIBCPP_HIDE_FROM_ABI syncbuf_type* rdbuf() const noexcept { return const_cast(std::addressof(__sb_)); } private: syncbuf_type __sb_; }; using std::osyncstream; # ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::wosyncstream; # endif #endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM) _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS #endif // _LIBCPP_SYNCSTREAM