diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 35e7b52..4bac576 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,8 +1,86 @@ -no tparam for params. -comments are concise. -@ref public symbols. -tparam, param, return, throws come last, in order. -newline after brief. -indent subsequent multiline tparam,param,return,throws. -@li for list items. -don't doc void return. +# CLAUDE.md v2 + +## Code Style + +- C++11 unless otherwise specified +- Boost C++ Libraries naming conventions (snake_case) +- 4-space indentation, no tabs +- Braces on their own line for classes/functions +- "detail" namespace symbols are never public +- public headers in "include/" +- library cpp and private header files in "src/" +- test files in "test/" +- Prefer RAII rollback guards over try-catch for cleanup + +## Javadoc Documentation + +Follow Boost C++ Libraries Javadoc style: + +- Brief descriptions on first line after `/**` +- Functions returning values: brief starts with "Return" +- Use `@param` for function parameters +- Use `@tparam` for template parameters, except: + - Variadic args (`Args...`) — omit + - Types deduced from function parameters — omit (self-evident from `@param`) +- Use `@return` for return value details +- Use `@pre` for preconditions +- Use `@post` for postconditions +- Use `@throws` for exceptions +- Use `@note` for important notes +- Use `@see` for cross-references +- Use `@code` / `@endcode` for examples + +### Examples + +```cpp +/** Return the size of the buffer sequence. + + @param buffers The buffer sequence to measure. + + @return The total byte count. +*/ +template +std::size_t +buffer_size(BufferSequence const& buffers); +``` + +No `@tparam` needed—`BufferSequence` is evident from `@param buffers`. + +```cpp +/** Return the default value. + + @tparam T The value type. +*/ +template +T default_value(); +``` + +`@tparam` needed—`T` has no corresponding function parameter. + +## Visibility and Linkage + +### Platform behavior +- MSVC class-level dllexport: exports vtable, typeinfo, ALL members (including private) +- GCC/Clang visibility("default") on class: exports vtable and typeinfo only +- MinGW: GCC frontend (key function rule) + Windows ABI (needs dllexport) + +### vtable requirements +- Construction/destruction requires vtable (vptr initialization) +- dynamic_cast/typeid require typeinfo +- Polymorphic class crossing DLL boundary: MUST export vtable + +### Strategy by class type +- Non-polymorphic: per-member export (minimizes symbols) +- Polymorphic, DLL boundary: class-level export (MSVC has no alternative) +- Polymorphic, header-only: BOOST_SYMBOL_VISIBLE (typeinfo consistency, not export) + +### Pitfalls +- Class dllexport + private member with incomplete type = link error +- Missing key function export on GCC = "undefined reference to vtable" +- Implicit special members not exported unless class-level export used + +## Preferences + +- Concise, dry answers +- Full files, not diffs +- Accurate, compiling C++ code diff --git a/.github/workflows/ci-failure-auto-fix.yml b/.github/workflows/ci-failure-auto-fix.yml deleted file mode 100644 index ff08a7a..0000000 --- a/.github/workflows/ci-failure-auto-fix.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Auto Fix CI Failures - -on: - workflow_run: - workflows: ["CI"] - types: - - completed - -permissions: - contents: write - pull-requests: write - actions: read - issues: write - id-token: write # Required for OIDC token exchange - -jobs: - auto-fix: - if: | - github.event.workflow_run.conclusion == 'failure' && - github.event.workflow_run.pull_requests[0] && - !startsWith(github.event.workflow_run.head_branch, 'claude-auto-fix-ci-') - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - ref: ${{ github.event.workflow_run.head_branch }} - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup git identity - run: | - git config --global user.email "claude[bot]@users.noreply.github.com" - git config --global user.name "claude[bot]" - - - name: Create fix branch - id: branch - run: | - BRANCH_NAME="claude-auto-fix-ci-${{ github.event.workflow_run.head_branch }}-${{ github.run_id }}" - git checkout -b "$BRANCH_NAME" - echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT - - - name: Get CI failure details - id: failure_details - uses: actions/github-script@v7 - with: - script: | - const run = await github.rest.actions.getWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{ github.event.workflow_run.id }} - }); - - const jobs = await github.rest.actions.listJobsForWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{ github.event.workflow_run.id }} - }); - - const failedJobs = jobs.data.jobs.filter(job => job.conclusion === 'failure'); - - let errorLogs = []; - for (const job of failedJobs) { - const logs = await github.rest.actions.downloadJobLogsForWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - job_id: job.id - }); - errorLogs.push({ - jobName: job.name, - logs: logs.data - }); - } - - return { - runUrl: run.data.html_url, - failedJobs: failedJobs.map(j => j.name), - errorLogs: errorLogs - }; - - - name: Fix CI failures with Claude - id: claude - uses: anthropics/claude-code-action@v1 - with: - prompt: | - /fix-ci - Failed CI Run: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }} - Failed Jobs: ${{ join(fromJSON(steps.failure_details.outputs.result).failedJobs, ', ') }} - PR Number: ${{ github.event.workflow_run.pull_requests[0].number }} - Branch Name: ${{ steps.branch.outputs.branch_name }} - Base Branch: ${{ github.event.workflow_run.head_branch }} - Repository: ${{ github.repository }} - - Error logs: - ${{ toJSON(fromJSON(steps.failure_details.outputs.result).errorLogs) }} - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(gh:*)'" \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 912365a..4bac576 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ -# CLAUDE.md +# CLAUDE.md v2 ## Code Style @@ -6,9 +6,11 @@ - Boost C++ Libraries naming conventions (snake_case) - 4-space indentation, no tabs - Braces on their own line for classes/functions -- Symbols in "detail" namespaces are never public +- "detail" namespace symbols are never public - public headers in "include/" -- library cpp files in "src/" +- library cpp and private header files in "src/" +- test files in "test/" +- Prefer RAII rollback guards over try-catch for cleanup ## Javadoc Documentation @@ -28,7 +30,7 @@ Follow Boost C++ Libraries Javadoc style: - Use `@see` for cross-references - Use `@code` / `@endcode` for examples -## Examples +### Examples ```cpp /** Return the size of the buffer sequence. @@ -55,6 +57,28 @@ T default_value(); `@tparam` needed—`T` has no corresponding function parameter. +## Visibility and Linkage + +### Platform behavior +- MSVC class-level dllexport: exports vtable, typeinfo, ALL members (including private) +- GCC/Clang visibility("default") on class: exports vtable and typeinfo only +- MinGW: GCC frontend (key function rule) + Windows ABI (needs dllexport) + +### vtable requirements +- Construction/destruction requires vtable (vptr initialization) +- dynamic_cast/typeid require typeinfo +- Polymorphic class crossing DLL boundary: MUST export vtable + +### Strategy by class type +- Non-polymorphic: per-member export (minimizes symbols) +- Polymorphic, DLL boundary: class-level export (MSVC has no alternative) +- Polymorphic, header-only: BOOST_SYMBOL_VISIBLE (typeinfo consistency, not export) + +### Pitfalls +- Class dllexport + private member with incomplete type = link error +- Missing key function export on GCC = "undefined reference to vtable" +- Implicit special members not exported unless class-level export used + ## Preferences - Concise, dry answers diff --git a/include/boost/buffers.hpp b/include/boost/buffers.hpp index a55a935..9c6a388 100644 --- a/include/boost/buffers.hpp +++ b/include/boost/buffers.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include diff --git a/include/boost/buffers/any_buffers.hpp b/include/boost/buffers/any_buffers.hpp index 49c3c6b..3bff3aa 100644 --- a/include/boost/buffers/any_buffers.hpp +++ b/include/boost/buffers/any_buffers.hpp @@ -12,10 +12,11 @@ #include #include +#include #include #include -#include #include +#include #include #include @@ -60,14 +61,12 @@ class any_buffers /** Destructor. */ - ~any_buffers() - { - p_->destroy(); - } + ~any_buffers() = default; /** Constructor. Default-constructed objects are empty with zero length. */ + BOOST_BUFFERS_DECL any_buffers() noexcept; /** Constructor. @@ -75,7 +74,7 @@ class any_buffers any_buffers( any_buffers const& other) noexcept { - other.p_->copy(*this); + other.sp_->copy(*this, other.sp_); } /** Assignment. @@ -86,8 +85,7 @@ class any_buffers { if(this == &other) return *this; - p_->destroy(); - other.p_->copy(*this); + other.sp_->copy(*this, other.sp_); return *this; } @@ -107,6 +105,9 @@ class any_buffers any_buffers( BufferSequence&& buffers) { + BOOST_CORE_STATIC_ASSERT( + is_const_buffer_sequence::value && (IsConst || + is_mutable_buffer_sequence::value)); using T = typename std::decay::type; construct(std::forward(buffers), std::integral_constant const&) const = 0; virtual void it_copy(void*, void const*) const = 0; virtual void it_destroy(void*) const = 0; virtual void inc(void*) const = 0; @@ -160,30 +161,31 @@ class any_buffers void construct(T&& t, std::true_type) { using U = typename std::decay::type; - p_ = ::new(&storage_) impl( - std::forward(t)); + sp_ = { + ::new(&storage_) impl(std::forward(t)), + null_deleter{} }; } template void construct(T&& t, std::false_type) { using U = typename std::decay::type; - p_ = new impl(std::forward(t)); + sp_ = std::make_shared>(std::forward(t)); } bool is_small_buffers() const noexcept { - return p_->is_small_buffers(); + return sp_->is_small_buffers(); } bool is_small_iter() const noexcept { - return p_->is_small_iter(); + return sp_->is_small_iter(); } alignas(std::max_align_t) unsigned char mutable storage_[sbo_size] = {}; - any_impl const* p_; + std::shared_ptr sp_; }; //----------------------------------------------- @@ -231,41 +233,29 @@ struct any_buffers:: return true; } - void destroy() const override - { - destroy(std::integral_constant{}); - } - - void destroy(std::true_type) const // small buffers - { - this->~impl(); - } - - void destroy(std::false_type) const - { - if(refs_.fetch_sub( 1, std::memory_order_acq_rel ) == 1) - { - std::atomic_thread_fence(std::memory_order_acquire); - delete this; - } - } - - void copy(any_buffers& dest) const override + void copy(any_buffers& dest, std::shared_ptr< + any_buffers::any_impl const> const& sp) const override { - copy(dest, std::integral_constant{}); } - void copy(any_buffers& dest, std::true_type) const // small buffers + void copy( + any_buffers& dest, std::shared_ptr< + any_buffers::any_impl const> const&, + std::true_type) const // small buffers { - dest.p_ = ::new(&dest.storage_) impl(t_); + dest.sp_ = std::shared_ptr>( + ::new(&dest.storage_) impl(t_), + null_deleter{} ); } - void copy(any_buffers& dest, std::false_type) const + void copy( + any_buffers& dest, std::shared_ptr< + any_buffers::any_impl const> const&, + std::false_type) const { - refs_.fetch_add( 1, std::memory_order_acq_rel ); - dest.p_ = this; + dest.sp_ = std::make_shared>(t_); } void it_copy(void* dest, void const* src) const override @@ -311,7 +301,6 @@ struct any_buffers:: private: T t_; - std::atomic mutable refs_{1}; }; template @@ -333,8 +322,7 @@ struct any_buffers:: bool is_small_buffers() const noexcept override { - return sizeof(*this) <= - any_buffers::sbo_size; + return sizeof(*this) <= any_buffers::sbo_size; } bool is_small_iter() const noexcept override @@ -342,40 +330,30 @@ struct any_buffers:: return false; } - void destroy() const override - { - destroy(std::integral_constant::sbo_size>{}); - } - - void destroy(std::true_type) const // small buffers - { - this->~impl(); - } - - void destroy(std::false_type) const - { - if(--refs_ == 0) - delete this; - } - - void copy(any_buffers& dest) const override + void copy( + any_buffers& dest, std::shared_ptr< + any_buffers::any_impl const> const& sp) const override { - copy(dest, std::integral_constant::sbo_size>{}); } - void copy(any_buffers& dest, + void copy( + any_buffers& dest, std::shared_ptr< + any_buffers::any_impl const> const&, std::true_type) const // small buffers { - dest.p_ = ::new(&dest.storage_) impl(t_); + dest.sp_ = std::shared_ptr>( + ::new(&dest.storage_) impl(t_), + null_deleter{}); } - void copy(any_buffers& dest, + void copy( + any_buffers& dest, std::shared_ptr< + any_buffers::any_impl const> const&, std::false_type) const { - ++refs_; - dest.p_ = this; + dest.sp_ = std::make_shared>(t_); } void it_copy(void* dest, void const* src) const override @@ -426,7 +404,6 @@ struct any_buffers:: private: T t_; - std::atomic mutable refs_{1}; std::size_t len_; }; @@ -446,7 +423,8 @@ class any_buffers:: public: /** The buffer type returned by dereferencing. */ - using value_type = typename any_buffers::value_type; + using value_type = typename + any_buffers::value_type; /** The type returned by `operator*`. @@ -479,7 +457,7 @@ class any_buffers:: */ ~const_iterator() { - p_->it_destroy(&storage_); + sp_->it_destroy(&storage_); } /** Default constructor. @@ -495,9 +473,9 @@ class any_buffers:: */ const_iterator( const_iterator const& other) noexcept - : p_(other.p_) + : sp_(other.sp_) { - p_->it_copy(&storage_, &other.storage_); + sp_->it_copy(&storage_, &other.storage_); } /** Copy assignment. @@ -510,9 +488,9 @@ class any_buffers:: { if(this == &other) return *this; - p_->it_destroy(&storage_); - p_ = other.p_; - p_->it_copy(&storage_, &other.storage_); + sp_->it_destroy(&storage_); + sp_ = other.sp_; + sp_->it_copy(&storage_, &other.storage_); return *this; } @@ -526,9 +504,9 @@ class any_buffers:: operator==( const_iterator const& other) const noexcept { - if(p_ != other.p_) + if(sp_ != other.sp_) return false; - return p_->equal(&storage_, &other.storage_); + return sp_->equal(&storage_, &other.storage_); } /** Test for inequality. @@ -554,7 +532,7 @@ class any_buffers:: reference operator*() const noexcept { - return p_->deref(&storage_); + return sp_->deref(&storage_); } /** Pre-increment. @@ -568,7 +546,7 @@ class any_buffers:: const_iterator& operator++() noexcept { - p_->inc(&storage_); + sp_->inc(&storage_); return *this; } @@ -599,7 +577,7 @@ class any_buffers:: const_iterator& operator--() noexcept { - p_->dec(&storage_); + sp_->dec(&storage_); return *this; } @@ -625,23 +603,23 @@ class any_buffers:: struct begin_tag {}; struct end_tag {}; - const_iterator(begin_tag, - any_impl const* p) noexcept - : p_(p) + const_iterator(begin_tag, std::shared_ptr< + any_impl const> const& sp) noexcept + : sp_(sp) { - p_->begin(&storage_); + sp_->begin(&storage_); } - const_iterator(end_tag, - any_impl const* p) noexcept - : p_(p) + const_iterator(end_tag, std::shared_ptr< + any_impl const> const& sp) noexcept + : sp_(sp) { - p_->end(&storage_); + sp_->end(&storage_); } alignas(std::max_align_t) unsigned char mutable storage_[iter_sbo_size] = {}; - any_buffers::any_impl const* p_; + std::shared_ptr::any_impl const> sp_; }; //----------------------------------------------- @@ -677,7 +655,7 @@ begin() const noexcept -> const_iterator { return const_iterator(typename - const_iterator::begin_tag{}, p_); + const_iterator::begin_tag{}, sp_); } template @@ -687,7 +665,7 @@ end() const noexcept -> const_iterator { return const_iterator(typename - const_iterator::end_tag{}, p_); + const_iterator::end_tag{}, sp_); } } // buffers diff --git a/include/boost/buffers/any_source.hpp b/include/boost/buffers/any_source.hpp new file mode 100644 index 0000000..d316121 --- /dev/null +++ b/include/boost/buffers/any_source.hpp @@ -0,0 +1,373 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/beast2 +// + +#ifndef BOOST_BUFFERS_ANY_SOURCE_HPP +#define BOOST_BUFFERS_ANY_SOURCE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace buffers { + +/** A type erased source. + + An object of this type represents shared ownership of a type-erased read\ + source or data source. + It provides a uniform interface for reading the source data regardless of + how the source is implemented. Accessing the bytes is achieved by calling + @ref read which reads data into a caller-provided buffer. Alternatively, + when @ref has_buffers returns `true` the source consists of buffers in memory, + and they can be accessed directly by calling @ref get_buffers. + + Example sources include: + - in-memory buffers + - streaming file data + - generated data + + @note @ref any_source is copyable, and the copies share ownership of the + underlying source. Changes to the state of one copy, such as reading + or rewinding, will be visible in all copies. + + Type-erased sources can always be rewound to the beginning by + calling @ref rewind. Therefore, a source can be read multiple times. + + @par Thread Safety + Unsafe. +*/ +class any_source +{ +public: + /** Constructor + + Default-constructed sources are empty. + */ + BOOST_BUFFERS_DECL + any_source() noexcept; + + any_source(any_source const&) = default; + + any_source& operator=(any_source const&) = default; + + /** Construct a read source. + */ + template::type, any_source>::value, + std::false_type, + is_read_source::type> + >::type::value, int>::type = 0> + any_source(ReadSource&& source); + + /** Construct a read source with a known size. + */ + template::type, any_source>::value, + std::false_type, + is_read_source::type> + >::type::value, int>::type = 0> + any_source( + std::size_t known_size, + ReadSource&& source); + + /** Construct a data source. + */ + template::type, any_source>::value, + std::false_type, + is_data_source::type> + >::type::value, int>::type = 0> + any_source(DataSource&& source); + + /** Return `true` if the size of the source is known. + */ + bool has_size() const noexcept + { + return sp_->has_size(); + } + + /** Return `true` if the source consists of buffers in memory. + When the source consists of buffers in memory, they can + also be accessed directly using @ref get_buffers. + */ + bool has_buffers() const noexcept + { + return sp_->has_buffers(); + } + + /** Return the size of the source, if available. + @throw std::invalid_argument if @ref has_size returns `false`. + @return The size of the source in bytes. + */ + auto size() const -> std::size_t + { + return sp_->size(); + } + + /** Return the buffers representing the source, if available. + @throw std::invalid_argument if @ref has_buffers returns `false`. + @return A buffer sequence representing the source. + */ + auto data() const -> + any_const_buffers + { + return sp_->data(); + } + + /** Rewind the source to the beginning. + This allows the source to be accessed from the start when calling @read. + */ + void rewind() + { + sp_->rewind(); + } + + /** Read from the source into a caller-provided buffer. + + When the last byte of data has been read, + @p ec is set to @ref error::eof. + + @param dest A pointer to the buffer to read into. + @param n The maximum number of bytes to read. + @param ec Set to the error, if any occurred. + @return The number of bytes read, which may be + less than the number requested. + */ + auto + read(void* dest, std::size_t n, + system::error_code& ec) -> + std::size_t + { + return sp_->read(dest, n, ec); + } + +private: + struct BOOST_BUFFERS_DECL + any_impl + { + virtual ~any_impl() = 0; + virtual bool has_size() const noexcept; + virtual bool has_buffers() const noexcept; + virtual std::size_t size() const; + virtual auto data() const -> any_const_buffers; + virtual void rewind() = 0; + virtual std::size_t read( + void* dest, std::size_t n, + system::error_code& ec) = 0; + }; + + std::shared_ptr sp_; +}; + +//----------------------------------------------- + +template::type, any_source>::value, + std::false_type, + is_read_source::type> + >::type::value, int>::type> +any_source:: +any_source( + ReadSource&& source) +{ + struct model : any_impl + { + system::error_code ec_; + typename std::decay::type source_; + + explicit model(ReadSource&& source) + : source_(std::forward(source)) + { + } + + void rewind() override + { + ec_ = {}; + source_.rewind(); + } + + std::size_t read( + void* dest, + std::size_t size, + system::error_code& ec) override + { + if(ec_.failed()) + { + ec = ec_; + return 0; + } + auto nread = source_.read( + mutable_buffer(dest, size), ec); + ec_ = ec; + return nread; + } + }; + + sp_ = std::make_shared( + std::forward(source)); +} + +/** Construct a streaming source source with a known size. +*/ +template::type, any_source>::value, + std::false_type, + is_read_source::type> + >::type::value, int>::type> +any_source:: +any_source( + std::size_t known_size, + ReadSource&& source) +{ + struct model : any_impl + { + std::size_t size_; + system::error_code ec_; + typename std::decay::type source_; + + model( + ReadSource&& source, + std::size_t known_size) + : size_(known_size) + , source_(std::forward(source)) + { + } + + bool has_size() const noexcept override + { + return true; + } + + std::size_t size() const override + { + return size_; + } + + void rewind() override + { + ec_ = {}; + source_.rewind(); + } + + std::size_t read( + void* dest, + std::size_t size, + system::error_code& ec) override + { + if(ec_.failed()) + { + ec = ec_; + return 0; + } + auto nread = source_.read( + mutable_buffer(dest, size), ec); + ec_ = ec; + return nread; + } + }; + + sp_ = std::make_shared( + std::forward(source), known_size); +} + +/** Construct a buffers source source. +*/ +template::type, any_source>::value, + std::false_type, + is_data_source::type> + >::type::value, int>::type> +any_source:: +any_source( + DataSource&& source) +{ + struct model : any_impl + { + typename std::decay::type source_; + std::size_t size_ = 0; + std::size_t nread_ = 0; + + explicit model( + DataSource&& source) noexcept + : source_(std::forward(source)) + , size_(buffers::size(source_.data())) + { + } + + bool has_size() const noexcept override + { + return true; + } + + bool has_buffers() const noexcept override + { + return true; + } + + std::size_t size() const override + { + return size_; + } + + any_const_buffers + data() const override + { + return source_.data(); + } + + void rewind() override + { + nread_ = 0; + } + + std::size_t read( + void* dest, + std::size_t n0, + system::error_code& ec) override + { + std::size_t n = copy( + mutable_buffer(dest, n0), + sans_prefix(source_.data(), nread_)); + nread_ += n; + if(nread_ >= size_) + ec = error::eof; + else + ec = {}; + return n; + } + }; + + // VFALCO this requires DataSource to be nothrow + // move constructible for strong exception safety. + sp_ = std::make_shared( + std::forward(source)); +} + +} // buffers +} // boost + +#endif diff --git a/include/boost/buffers/slice.hpp b/include/boost/buffers/slice.hpp index 8a06c7e..fe091ab 100644 --- a/include/boost/buffers/slice.hpp +++ b/include/boost/buffers/slice.hpp @@ -64,12 +64,9 @@ class slice_of using iter_type = decltype( std::declval().begin()); - using difference_type = - typename std::iterator_traits::difference_type; - BufferSequence bs_; - difference_type begin_ = 0; // index of first buffer in sequence - difference_type end_ = 0; // 1 + index of last buffer in sequence + iter_type begin_; + iter_type end_; std::size_t len_ = 0; // length of bs_ std::size_t size_ = 0; // total bytes std::size_t prefix_ = 0; // used prefix bytes @@ -95,12 +92,11 @@ class slice_of slice_of( BufferSequence const& bs) : bs_(bs) + , begin_(buffers::begin(bs_)) + , end_(buffers::end(bs_)) { - iter_type it = buffers::begin(bs_); - iter_type eit = buffers::end(bs_); - begin_ = 0; - end_ = std::distance(it, eit); - while(it != eit) + auto it = begin_; + while(it != end_) { value_type b(*it); size_ += b.size(); @@ -131,39 +127,18 @@ class slice_of } private: - iter_type - begin_iter_impl() const noexcept - { - iter_type it = buffers::begin(bs_); - std::advance(it, begin_); - return it; - } - - iter_type - end_iter_impl() const noexcept - { - iter_type it = buffers::begin(bs_); - std::advance(it, end_); - return it; - } - void remove_prefix_impl( std::size_t n) { - if(n > size_) - n = size_; - // nice hack to simplify the loop (M. Nejati) n += prefix_; size_ += prefix_; prefix_ = 0; - iter_type it = begin_iter_impl(); - while(n > 0 && begin_ != end_) { - value_type b = *it; + value_type b = *begin_; if(n < b.size()) { prefix_ = n; @@ -173,7 +148,6 @@ class slice_of n -= b.size(); size_ -= b.size(); ++begin_; - ++it; --len_; } } @@ -188,19 +162,12 @@ class slice_of return; } BOOST_ASSERT(begin_ != end_); - - if(n > size_) - n = size_; - n += suffix_; size_ += suffix_; suffix_ = 0; - - iter_type bit = begin_iter_impl(); - iter_type it = end_iter_impl(); - it--; - - while(it != bit) + iter_type it = end_; + --it; + while(it != begin_) { value_type b = *it; if(n < b.size()) @@ -225,7 +192,6 @@ class slice_of } end_ = begin_; len_ = 0; - size_ = 0; } void @@ -410,7 +376,7 @@ begin() const noexcept -> const_iterator { return const_iterator( - begin_iter_impl(), prefix_, suffix_, 0, len_); + this->begin_, prefix_, suffix_, 0, len_); } template @@ -420,7 +386,7 @@ end() const noexcept -> const_iterator { return const_iterator( - end_iter_impl(), prefix_, suffix_, len_, len_); + this->end_, prefix_, suffix_, len_, len_); } //------------------------------------------------ diff --git a/src/any_buffers.cpp b/src/any_buffers.cpp index 5b6601f..3a34657 100644 --- a/src/any_buffers.cpp +++ b/src/any_buffers.cpp @@ -29,13 +29,11 @@ any_buffers() noexcept return true; } - void destroy() const override + void copy( + any_buffers& dest, + std::shared_ptr const& sp) const override { - } - - void copy(any_buffers& dest) const override - { - dest.p_ = this; + dest.sp_ = sp; } void it_copy(void*, void const*) const override @@ -74,7 +72,8 @@ any_buffers() noexcept }; static impl const instance; - p_ = &instance; + sp_ = std::shared_ptr( + &instance, null_deleter{} ); } template<> @@ -93,13 +92,11 @@ any_buffers() noexcept return true; } - void destroy() const override - { - } - - void copy(any_buffers& dest) const override + void copy( + any_buffers& dest, + std::shared_ptr const& sp) const override { - dest.p_ = this; + dest.sp_ = sp; } void it_copy(void*, void const*) const override @@ -138,7 +135,8 @@ any_buffers() noexcept }; static impl const instance; - p_ = &instance; + sp_ = std::shared_ptr( + &instance, null_deleter{} ); } template<> @@ -146,7 +144,7 @@ any_buffers:: any_buffers:: const_iterator:: const_iterator() noexcept - : p_(any_buffers().begin().p_) + : sp_(any_buffers().begin().sp_) { } @@ -155,7 +153,7 @@ any_buffers:: any_buffers:: const_iterator:: const_iterator() noexcept - : p_(any_buffers().begin().p_) + : sp_(any_buffers().begin().sp_) { } diff --git a/src/any_source.cpp b/src/any_source.cpp new file mode 100644 index 0000000..fe0b092 --- /dev/null +++ b/src/any_source.cpp @@ -0,0 +1,97 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/buffers +// + +#include + +namespace boost { +namespace buffers { + +any_source:: +any_impl:: +~any_impl() = default; + +bool +any_source:: +any_impl:: +has_size() const noexcept +{ + return false; +} + +bool +any_source:: +any_impl:: +has_buffers() const noexcept +{ + return false; +} + +std::size_t +any_source:: +any_impl:: +size() const +{ + detail::throw_invalid_argument(); +} + +any_const_buffers +any_source:: +any_impl:: +data() const +{ + detail::throw_invalid_argument(); +} + +//----------------------------------------------- + +any_source:: +any_source() noexcept +{ + struct model : any_impl + { + bool has_size() const noexcept override + { + return true; + } + + bool has_buffers() const noexcept override + { + return true; + } + + std::size_t size() const override + { + return 0; + } + + any_const_buffers data() const override + { + return {}; + } + + void rewind() override + { + } + + std::size_t read( + void*, + std::size_t, + system::error_code& ec) override + { + ec = error::eof; + return 0; + } + }; + + static model instance; + sp_ = { &instance, [](any_impl*) {} }; +} + +} // buffers +} // boost diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 3795d92..8f9ae53 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -7,9 +7,7 @@ # Official repository: https://github.com/cppalliance/buffers # -if (NOT TARGET boost_url_test_suite) - add_subdirectory(../../../url/extra/test_suite test_suite) -endif() +add_subdirectory(../../../url/extra/test_suite test_suite) file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp) list(APPEND PFILES diff --git a/test/unit/any_source.cpp b/test/unit/any_source.cpp new file mode 100644 index 0000000..058a8e8 --- /dev/null +++ b/test/unit/any_source.cpp @@ -0,0 +1,257 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/beast2 +// + +// Test that header file is self-contained. +#include + +#include +#include + +#include "test_suite.hpp" + +namespace boost { +namespace buffers { + +namespace { + +struct data_source +{ + core::string_view s_; + + data_source( + core::string_view s) + : s_(s) + { + } + + buffers::const_buffer + data() const + { + return { s_.data(), s_.size() }; + } +}; + +struct read_source +{ + core::string_view s_; + std::size_t nread_ = 0; + system::error_code ec_; + + explicit read_source( + core::string_view s, + system::error_code ec = {}) + : s_(s) + , ec_(ec) + { + } + + void rewind() + { + nread_ = 0; + } + + template + std::size_t read( + MutableBufferSequence const& dest, + system::error_code& ec) + { + if( nread_ > 0 && + ec_.failed()) + { + // fail on second read + ec = ec_; + return 0; + } + auto n = buffers::copy( + dest, + buffers::const_buffer( + s_.data() + nread_, + s_.size() - nread_)); + nread_ += n; + if(nread_ >= s_.size()) + { + if(ec_.failed()) + ec = ec_; + else + ec = error::eof; + } + else + { + ec = {}; + } + return n; + } +}; + +BOOST_CORE_STATIC_ASSERT( is_data_source::value); +BOOST_CORE_STATIC_ASSERT(! is_data_source::value); +BOOST_CORE_STATIC_ASSERT( is_read_source::value); +BOOST_CORE_STATIC_ASSERT(! is_read_source::value); + +} // (anon) + +BOOST_CORE_STATIC_ASSERT(std::is_move_constructible::value); +BOOST_CORE_STATIC_ASSERT(std::is_copy_constructible::value); +BOOST_CORE_STATIC_ASSERT(std::is_constructible::value); +BOOST_CORE_STATIC_ASSERT(std::is_constructible::value); +BOOST_CORE_STATIC_ASSERT(std::is_constructible::value); +BOOST_CORE_STATIC_ASSERT(std::is_move_assignable::value); +BOOST_CORE_STATIC_ASSERT(std::is_copy_assignable::value); +BOOST_CORE_STATIC_ASSERT(std::is_assignable::value); +BOOST_CORE_STATIC_ASSERT(std::is_assignable::value); +BOOST_CORE_STATIC_ASSERT(std::is_assignable::value); +BOOST_CORE_STATIC_ASSERT(std::is_assignable::value); + +struct any_source_test +{ + void grind( + any_source& b, + core::string_view s0, + system::error_code fec = {}) + { + char buf[16]; + for(std::size_t n = 1; n <= sizeof(buf); ++n) + { + std::string s; + system::error_code ec; + b.rewind(); + for(;;) + { + auto nread = b.read(buf, n, ec); + s.append(buf, nread); + if(ec == error::eof) + { + BOOST_TEST(! fec.failed()); + BOOST_TEST_EQ(s, s0); + break; + } + if(ec.failed()) + { + BOOST_TEST_EQ(ec, fec); + break; + } + BOOST_TEST_GT(nread, 0); + } + } + } + + void testEmpty() + { + any_source b; + BOOST_TEST_EQ(b.has_size(), true); + BOOST_TEST_EQ(b.size(), 0); + BOOST_TEST_EQ(b.has_buffers(), true); + BOOST_TEST_EQ(buffers::size(b.data()), 0); + BOOST_TEST_NO_THROW(b.rewind()); + grind(b, ""); + } + + void testBuffers() + { + core::string_view s1("Hello, world!"); + core::string_view s2("Boost"); + + any_source b1((data_source(s1))); + BOOST_TEST_EQ(b1.has_size(), true); + BOOST_TEST_EQ(b1.size(), s1.size()); + BOOST_TEST_EQ(b1.has_buffers(), true); + BOOST_TEST_EQ(buffers::size(b1.data()), s1.size()); + BOOST_TEST_NO_THROW(b1.rewind()); + grind(b1, s1); + + any_source b2 = std::move(b1); + BOOST_TEST_EQ(b2.has_size(), true); + BOOST_TEST_EQ(b2.size(), s1.size()); + BOOST_TEST_EQ(b2.has_buffers(), true); + BOOST_TEST_EQ(buffers::size(b2.data()), s1.size()); + BOOST_TEST_NO_THROW(b2.rewind()); + grind(b2, s1); + + b1 = data_source(s2); + BOOST_TEST_EQ(b1.has_size(), true); + BOOST_TEST_EQ(b1.size(), s2.size()); + BOOST_TEST_EQ(b1.has_buffers(), true); + BOOST_TEST_EQ(buffers::size(b1.data()), s2.size()); + BOOST_TEST_NO_THROW(b1.rewind()); + grind(b1, s2); + } + + void testStream() + { + core::string_view s1("Hello, world!"); + core::string_view s2("Boost"); + + any_source b1((read_source(s1))); + BOOST_TEST_EQ(b1.has_size(), false); + BOOST_TEST_EQ(b1.has_buffers(), false); + BOOST_TEST_THROWS(b1.size(), std::invalid_argument); + BOOST_TEST_THROWS(b1.data(), std::invalid_argument); + BOOST_TEST_NO_THROW(b1.rewind()); + grind(b1, s1); + + any_source b2 = std::move(b1); + BOOST_TEST_EQ(b2.has_size(), false); + BOOST_TEST_EQ(b2.has_buffers(), false); + BOOST_TEST_THROWS(b2.size(), std::invalid_argument); + BOOST_TEST_THROWS(b2.data(), std::invalid_argument); + BOOST_TEST_NO_THROW(b2.rewind()); + BOOST_TEST_EQ(b1.has_size(), false); + BOOST_TEST_EQ(b1.has_buffers(), false); + BOOST_TEST_THROWS(b1.size(), std::invalid_argument); + BOOST_TEST_THROWS(b1.data(), std::invalid_argument); + BOOST_TEST_NO_THROW(b1.rewind()); + grind(b2, s1); + + b1 = read_source(s2); + BOOST_TEST_EQ(b1.has_size(), false); + BOOST_TEST_EQ(b1.has_buffers(), false); + BOOST_TEST_THROWS(b1.size(), std::invalid_argument); + BOOST_TEST_THROWS(b1.data(), std::invalid_argument); + BOOST_TEST_NO_THROW(b1.rewind()); + grind(b1, s2); + + // sized source + b2 = any_source(s2.size(), read_source(s2)); + BOOST_TEST_EQ(b2.has_size(), true); + BOOST_TEST_EQ(b2.has_buffers(), false); + BOOST_TEST_EQ(b2.size(), s2.size()); + BOOST_TEST_THROWS(b1.data(), std::invalid_argument); + BOOST_TEST_NO_THROW(b2.rewind()); + grind(b2, s2); + } + + void testFail() + { + core::string_view s1("Hello, world!"); + auto fec = make_error_code( + boost::system::errc::address_in_use ); + any_source b1((read_source(s1, fec))); + BOOST_TEST_EQ(b1.has_size(), false); + BOOST_TEST_EQ(b1.has_buffers(), false); + BOOST_TEST_THROWS(b1.size(), std::invalid_argument); + BOOST_TEST_THROWS(b1.data(), std::invalid_argument); + BOOST_TEST_NO_THROW(b1.rewind()); + grind(b1, s1, fec); + } + + void run() + { + testEmpty(); + testBuffers(); + testStream(); + testFail(); + } +}; + +TEST_SUITE( + any_source_test, + "boost.buffers.any_source"); + +} // beast2 +} // buffers diff --git a/test/unit/slice.cpp b/test/unit/slice.cpp index 0bc31e2..72c31ed 100644 --- a/test/unit/slice.cpp +++ b/test/unit/slice.cpp @@ -17,7 +17,6 @@ #include #include -#include #include "test_buffers.hpp" #include "test_suite.hpp" @@ -120,8 +119,7 @@ struct slice_test test::check_iterators(b, s); } - // Use a vector so that iterator invalidation is observable during testing. - using seq_type = std::vector; + using seq_type = std::array; void grind_back( @@ -178,8 +176,8 @@ struct slice_test run() { std::string s; - auto a = make_buffers(s, "boost.", "buffers.", "slice_"); - seq_type bs(a.begin(), a.end()); + seq_type bs = make_buffers(s, + "boost.", "buffers.", "slice_" ); test::check_sequence(bs, s); //check(bs, s); //grind(bs, s); diff --git a/test/unit/test_buffers.hpp b/test/unit/test_buffers.hpp index 5e23e57..63adff9 100644 --- a/test/unit/test_buffers.hpp +++ b/test/unit/test_buffers.hpp @@ -17,7 +17,6 @@ #include #include #include - #include // Trick boostdep into requiring URL @@ -256,8 +255,7 @@ template void grind_front( ConstBufferSequence const& bs0, - core::string_view pat0, - int levels) + core::string_view pat0) { for(std::size_t n = 0; n <= pat0.size() + 1; ++n) { @@ -267,18 +265,6 @@ grind_front( remove_prefix(bs, n); check_eq(bs, pat); check_iterators(bs, pat); - - if(levels) - { - // Take a copy, blank out the original to invalidate any - // iterators, and redo the test - slice_type bsc(bs); - { - slice_type dummy{}; - std::swap(bs, dummy); - } - grind_front(bsc, pat, levels-1); - } } { auto pat = kept_front(pat0, n); @@ -286,18 +272,6 @@ grind_front( keep_prefix(bs, n); check_eq(bs, pat); check_iterators(bs, pat); - - if(levels) - { - // Take a copy, blank out the original to invalidate any - // iterators, and redo the test - slice_type bsc(bs); - { - slice_type dummy{}; - std::swap(bs, dummy); - } - grind_front(bsc, pat, levels-1); - } } } } @@ -306,8 +280,7 @@ template void grind_back( ConstBufferSequence const& bs0, - core::string_view pat0, - int levels) + core::string_view pat0) { for(std::size_t n = 0; n <= pat0.size() + 1; ++n) { @@ -317,18 +290,6 @@ grind_back( remove_suffix(bs, n); check_eq(bs, pat); check_iterators(bs, pat); - - if(levels) - { - // Take a copy, blank out the original to invalidate any - // iterators, and redo the test - slice_type bsc(bs); - { - slice_type dummy{}; - std::swap(bs, dummy); - } - grind_back(bsc, pat, levels-1); - } } { auto pat = kept_back(pat0, n); @@ -336,70 +297,6 @@ grind_back( keep_suffix(bs, n); check_eq(bs, pat); check_iterators(bs, pat); - - if(levels) - { - // Take a copy, blank out the original to invalidate any - // iterators, and redo the test - slice_type bsc(bs); - { - slice_type dummy{}; - std::swap(bs, dummy); - } - grind_back(bsc, pat, levels-1); - } - } - } -} - -template -void -grind_both( - ConstBufferSequence const& bs0, - core::string_view pat0, - int levels) -{ - for(std::size_t n = 0; n <= pat0.size() / 2 + 2; ++n) - { - { - auto pat = trimmed_back(trimmed_front(pat0, n), n); - slice_type bs(bs0); - remove_prefix(bs, n); - remove_suffix(bs, n); - check_eq(bs, pat); - check_iterators(bs, pat); - - if(levels) - { - // Take a copy, blank out the original to invalidate any - // iterators, and redo the test - slice_type bsc(bs); - { - slice_type dummy{}; - std::swap(bs, dummy); - } - grind_both(bsc, pat, levels - 1); - } - } - { - auto pat = kept_back(kept_front(pat0, n), n); - slice_type bs(bs0); - keep_prefix(bs, n); - keep_suffix(bs, n); - check_eq(bs, pat); - check_iterators(bs, pat); - - if(levels) - { - // Take a copy, blank out the original to invalidate any - // iterators, and redo the test - slice_type bsc(bs); - { - slice_type dummy{}; - std::swap(bs, dummy); - } - grind_both(bsc, pat, levels - 1); - } } } } @@ -410,9 +307,8 @@ check_slice( ConstBufferSequence const& bs, core::string_view pat) { - grind_front(bs, pat, 1); - grind_back(bs, pat, 1); - grind_both(bs, pat, 1); + grind_front(bs, pat); + grind_back(bs, pat); } // Test API and behavior of a BufferSequence @@ -424,7 +320,6 @@ check_sequence( BOOST_STATIC_ASSERT(is_const_buffer_sequence::value); check_iterators(t, pat); - check_slice(t, pat); }