| // -*- 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 <ostream> // see [ostream.syn] |
| |
| namespace std { |
| template<class charT, class traits, class Allocator> |
| class basic_syncbuf; |
| |
| // [syncstream.syncbuf.special], specialized algorithms |
| template<class charT, class traits, class Allocator> |
| void swap(basic_syncbuf<charT, traits, Allocator>&, |
| basic_syncbuf<charT, traits, Allocator>&); |
| |
| using syncbuf = basic_syncbuf<char>; |
| using wsyncbuf = basic_syncbuf<wchar_t>; |
| |
| template<class charT, class traits, class Allocator> |
| class basic_osyncstream; |
| |
| using osyncstream = basic_osyncstream<char>; |
| using wosyncstream = basic_osyncstream<wchar_t>; |
| |
| template<class charT, class traits, class Allocator> |
| class basic_syncbuf : public basic_streambuf<charT, traits> { |
| 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<charT, traits>; |
| |
| // [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<class charT, class traits, class Allocator> |
| void swap(basic_syncbuf<charT, traits, Allocator>&, |
| basic_syncbuf<charT, traits, Allocator>&); |
| |
| template<class charT, class traits, class Allocator> |
| class basic_osyncstream : public basic_ostream<charT, traits> { |
| 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<charT, traits>; |
| using syncbuf_type = basic_syncbuf<charT, traits, Allocator>; |
| |
| // [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<charT, traits>& os, const Allocator& allocator) |
| : basic_osyncstream(os.rdbuf(), allocator) {} |
| explicit basic_osyncstream(basic_ostream<charT, traits>& 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<syncbuf_type*>(addressof(sb)); } |
| |
| private: |
| syncbuf_type sb; // exposition only |
| }; |
| } |
| |
| */ |
| |
| #include <__config> |
| #include <__utility/move.h> |
| #include <ios> |
| #include <iosfwd> // required for declaration of default arguments |
| #include <streambuf> |
| #include <string> |
| |
| #ifndef _LIBCPP_HAS_NO_THREADS |
| # include <map> |
| # include <mutex> |
| # include <shared_mutex> |
| #endif |
| |
| // standard-mandated includes |
| |
| // [syncstream.syn] |
| #include <ostream> |
| |
| #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<uintptr_t>(__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<mutex> __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<uintptr_t>(__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<uintptr_t, __value> __lut_; |
| |
| [[nodiscard]] _LIBCPP_HIDE_FROM_ABI map<uintptr_t, __value>::iterator __get_it(void* __ptr) noexcept { |
| _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to"); |
| |
| auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__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 _CharT, class _Traits, class _Allocator> |
| 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<char_type*>(__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<mutex> __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<char_type*>(__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 <class _CharT, class _Traits, class _Allocator> |
| _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 _CharT, class _Traits, class _Allocator> |
| 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<char_type, traits_type>; |
| using syncbuf_type = basic_syncbuf<char_type, traits_type, allocator_type>; |
| |
| // [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<char_type, traits_type>& __os, allocator_type const& __alloc) |
| : basic_osyncstream(__os.rdbuf(), __alloc) {} |
| |
| _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(basic_ostream<char_type, traits_type>& __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<char_type, traits_type>::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<syncbuf_type*>(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 |