From 4f411c66a92298bb4dfd1ab6b1d02d97af8453bb Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sun, 3 May 2026 22:49:02 +0200 Subject: [PATCH 1/7] Remove dead code (#1427) Removing private class members, code unused by ETL in "private" namespaces, code unreachable via preprocessor guards (C++11 inside C++03). For code still to be kept, even though unused at first sight, add tests. --- include/etl/algorithm.h | 20 -------------- include/etl/basic_string.h | 18 ++----------- include/etl/callback_timer.h | 20 +++----------- include/etl/callback_timer_atomic.h | 20 +++----------- include/etl/callback_timer_interrupt.h | 20 +++----------- include/etl/callback_timer_locked.h | 20 +++----------- include/etl/deque.h | 26 ------------------- include/etl/flat_map.h | 20 -------------- include/etl/memory.h | 20 +++++++------- include/etl/message_timer.h | 20 +++----------- include/etl/message_timer_atomic.h | 20 +++----------- include/etl/message_timer_interrupt.h | 20 +++----------- include/etl/message_timer_locked.h | 20 +++----------- include/etl/ranges.h | 14 ---------- include/etl/vector.h | 11 -------- test/test_memory.cpp | 26 +++++++++++++++++++ test/test_ranges.cpp | 36 ++++++++++++++++++++++++++ 17 files changed, 106 insertions(+), 245 deletions(-) diff --git a/include/etl/algorithm.h b/include/etl/algorithm.h index b1d570e2..577ea394 100644 --- a/include/etl/algorithm.h +++ b/include/etl/algorithm.h @@ -91,26 +91,6 @@ namespace etl template ETL_CONSTEXPR14 void insertion_sort(TIterator first, TIterator last, TCompare compare); - class algorithm_exception : public etl::exception - { - public: - - algorithm_exception(string_type reason_, string_type file_name_, numeric_type line_number_) - : exception(reason_, file_name_, line_number_) - { - } - }; - - class algorithm_error : public algorithm_exception - { - public: - - algorithm_error(string_type file_name_, numeric_type line_number_) - : algorithm_exception(ETL_ERROR_TEXT("algorithm:error", ETL_ALGORITHM_FILE_ID"A"), file_name_, line_number_) - { - } - }; - } // namespace etl //***************************************************************************** diff --git a/include/etl/basic_string.h b/include/etl/basic_string.h index 1a38cc99..4b834c74 100644 --- a/include/etl/basic_string.h +++ b/include/etl/basic_string.h @@ -93,20 +93,6 @@ namespace etl } }; - //*************************************************************************** - ///\ingroup string - /// String empty exception. - //*************************************************************************** - class string_empty : public etl::string_exception - { - public: - - string_empty(string_type file_name_, numeric_type line_number_) - : string_exception(ETL_ERROR_TEXT("string:empty", ETL_BASIC_STRING_FILE_ID"A"), file_name_, line_number_) - { - } - }; - //*************************************************************************** ///\ingroup string /// String out of bounds exception. @@ -2742,6 +2728,7 @@ namespace etl iterator>::type copy_characters(TIterator1 from, size_t n, iterator to) { +#include "etl/private/diagnostic_stringop_overflow_push.h" size_t count = 0; while (count != n) @@ -2750,6 +2737,7 @@ namespace etl ++count; } +#include "etl/private/diagnostic_pop.h" return to; } @@ -3218,8 +3206,6 @@ namespace etl #endif } // namespace etl -#undef ETL_USING_WCHAR_T_H - #include "private/minmax_pop.h" #endif diff --git a/include/etl/callback_timer.h b/include/etl/callback_timer.h index 0b3da8ba..2cc94651 100644 --- a/include/etl/callback_timer.h +++ b/include/etl/callback_timer.h @@ -629,7 +629,6 @@ namespace etl timer_list(timer_data* ptimers_) : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) - , current(etl::timer::id::NO_TIMER) , ptimers(ptimers_) { } @@ -758,22 +757,13 @@ namespace etl //******************************* etl::timer::id::type begin() { - current = head; - return current; - } - - //******************************* - etl::timer::id::type previous(etl::timer::id::type last) - { - current = ptimers[last].previous; - return current; + return head; } //******************************* etl::timer::id::type next(etl::timer::id::type last) { - current = ptimers[last].next; - return current; + return ptimers[last].next; } //******************************* @@ -788,16 +778,14 @@ namespace etl timer.next = etl::timer::id::NO_TIMER; } - head = etl::timer::id::NO_TIMER; - tail = etl::timer::id::NO_TIMER; - current = etl::timer::id::NO_TIMER; + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; } private: etl::timer::id::type head; etl::timer::id::type tail; - etl::timer::id::type current; timer_data* const ptimers; }; diff --git a/include/etl/callback_timer_atomic.h b/include/etl/callback_timer_atomic.h index 0aba1f97..a95c191f 100644 --- a/include/etl/callback_timer_atomic.h +++ b/include/etl/callback_timer_atomic.h @@ -487,7 +487,6 @@ namespace etl timer_list(timer_data* ptimers_) : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) - , current(etl::timer::id::NO_TIMER) , ptimers(ptimers_) { } @@ -616,22 +615,13 @@ namespace etl //******************************* etl::timer::id::type begin() { - current = head; - return current; - } - - //******************************* - etl::timer::id::type previous(etl::timer::id::type last) - { - current = ptimers[last].previous; - return current; + return head; } //******************************* etl::timer::id::type next(etl::timer::id::type last) { - current = ptimers[last].next; - return current; + return ptimers[last].next; } //******************************* @@ -646,16 +636,14 @@ namespace etl timer.next = etl::timer::id::NO_TIMER; } - head = etl::timer::id::NO_TIMER; - tail = etl::timer::id::NO_TIMER; - current = etl::timer::id::NO_TIMER; + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; } private: etl::timer::id::type head; etl::timer::id::type tail; - etl::timer::id::type current; timer_data* const ptimers; }; diff --git a/include/etl/callback_timer_interrupt.h b/include/etl/callback_timer_interrupt.h index 3522bd22..59cdf845 100644 --- a/include/etl/callback_timer_interrupt.h +++ b/include/etl/callback_timer_interrupt.h @@ -487,7 +487,6 @@ namespace etl timer_list(timer_data* ptimers_) : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) - , current(etl::timer::id::NO_TIMER) , ptimers(ptimers_) { } @@ -616,22 +615,13 @@ namespace etl //******************************* etl::timer::id::type begin() { - current = head; - return current; - } - - //******************************* - etl::timer::id::type previous(etl::timer::id::type last) - { - current = ptimers[last].previous; - return current; + return head; } //******************************* etl::timer::id::type next(etl::timer::id::type last) { - current = ptimers[last].next; - return current; + return ptimers[last].next; } //******************************* @@ -646,16 +636,14 @@ namespace etl timer.next = etl::timer::id::NO_TIMER; } - head = etl::timer::id::NO_TIMER; - tail = etl::timer::id::NO_TIMER; - current = etl::timer::id::NO_TIMER; + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; } private: etl::timer::id::type head; etl::timer::id::type tail; - etl::timer::id::type current; timer_data* const ptimers; }; diff --git a/include/etl/callback_timer_locked.h b/include/etl/callback_timer_locked.h index 4c53f259..85119025 100644 --- a/include/etl/callback_timer_locked.h +++ b/include/etl/callback_timer_locked.h @@ -460,7 +460,6 @@ namespace etl timer_list(timer_data* ptimers_) : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) - , current(etl::timer::id::NO_TIMER) , ptimers(ptimers_) { } @@ -589,22 +588,13 @@ namespace etl //******************************* etl::timer::id::type begin() { - current = head; - return current; - } - - //******************************* - etl::timer::id::type previous(etl::timer::id::type last) - { - current = ptimers[last].previous; - return current; + return head; } //******************************* etl::timer::id::type next(etl::timer::id::type last) { - current = ptimers[last].next; - return current; + return ptimers[last].next; } //******************************* @@ -619,16 +609,14 @@ namespace etl timer.next = etl::timer::id::NO_TIMER; } - head = etl::timer::id::NO_TIMER; - tail = etl::timer::id::NO_TIMER; - current = etl::timer::id::NO_TIMER; + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; } private: etl::timer::id::type head; etl::timer::id::type tail; - etl::timer::id::type current; timer_data* const ptimers; }; diff --git a/include/etl/deque.h b/include/etl/deque.h index 888ac06a..697436fc 100644 --- a/include/etl/deque.h +++ b/include/etl/deque.h @@ -462,19 +462,6 @@ namespace etl private: - //*************************************************** - difference_type distance(difference_type firstIndex, difference_type index_) const - { - if (index_ < firstIndex) - { - return static_cast(p_deque->Buffer_Size) + index_ - firstIndex; - } - else - { - return index_ - firstIndex; - } - } - //*************************************************** iterator(difference_type index_, ideque& the_deque, pointer p_buffer_) : index(index_) @@ -721,19 +708,6 @@ namespace etl private: - //*************************************************** - difference_type distance(difference_type firstIndex, difference_type index_) const - { - if (index_ < firstIndex) - { - return static_cast(p_deque->Buffer_Size) + index_ - firstIndex; - } - else - { - return index_ - firstIndex; - } - } - //*************************************************** const_iterator(difference_type index_, ideque& the_deque, pointer p_buffer_) : index(index_) diff --git a/include/etl/flat_map.h b/include/etl/flat_map.h index 8e2cd39f..a9188642 100644 --- a/include/etl/flat_map.h +++ b/include/etl/flat_map.h @@ -100,26 +100,6 @@ namespace etl private: - //********************************************************************* - /// How to compare elements and keys. - //********************************************************************* - class compare - { - public: - - bool operator()(const value_type& element, key_type key) const - { - return comp(element.first, key); - } - - bool operator()(key_type key, const value_type& element) const - { - return comp(key, element.first); - } - - key_compare comp; - }; - public: //********************************************************************* diff --git a/include/etl/memory.h b/include/etl/memory.h index e4301260..c25af960 100644 --- a/include/etl/memory.h +++ b/include/etl/memory.h @@ -795,12 +795,8 @@ namespace etl template TOutputIterator uninitialized_move_n(TInputIterator i_begin, TSize n, TOutputIterator o_begin) { - // Move not supported. Defer to copy. - #if ETL_USING_CPP11 - return std::uninitialized_copy_n(i_begin, n, o_begin); - #else + // Move not supported. Defer to copy. return etl::uninitialized_copy_n(i_begin, n, o_begin); - #endif } //***************************************************************************** @@ -814,12 +810,8 @@ namespace etl { count += TCounter(n); - // Move not supported. Defer to copy. - #if ETL_USING_CPP11 - return std::uninitialized_copy_n(i_begin, n, o_begin); - #else + // Move not supported. Defer to copy. return etl::uninitialized_copy_n(i_begin, n, o_begin); - #endif } #endif @@ -2643,6 +2635,14 @@ namespace etl { *p++ = 0; } + + // Prevent the compiler from optimising away the volatile stores + // as dead stores (observed with GCC -O3 in C++23 mode). +#if defined(ETL_COMPILER_GCC) || defined(ETL_COMPILER_CLANG) + __asm__ __volatile__("" : : : "memory"); +#elif defined(ETL_COMPILER_MICROSOFT) + _ReadWriteBarrier(); +#endif } //***************************************************************************** diff --git a/include/etl/message_timer.h b/include/etl/message_timer.h index 378ff36f..a6d44a33 100644 --- a/include/etl/message_timer.h +++ b/include/etl/message_timer.h @@ -157,7 +157,6 @@ namespace etl list(etl::message_timer_data* ptimers_) : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) - , current(etl::timer::id::NO_TIMER) , ptimers(ptimers_) { } @@ -286,22 +285,13 @@ namespace etl //******************************* etl::timer::id::type begin() { - current = head; - return current; - } - - //******************************* - etl::timer::id::type previous(etl::timer::id::type last) - { - current = ptimers[last].previous; - return current; + return head; } //******************************* etl::timer::id::type next(etl::timer::id::type last) { - current = ptimers[last].next; - return current; + return ptimers[last].next; } //******************************* @@ -316,16 +306,14 @@ namespace etl timer.next = etl::timer::id::NO_TIMER; } - head = etl::timer::id::NO_TIMER; - tail = etl::timer::id::NO_TIMER; - current = etl::timer::id::NO_TIMER; + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; } private: etl::timer::id::type head; etl::timer::id::type tail; - etl::timer::id::type current; etl::message_timer_data* const ptimers; }; diff --git a/include/etl/message_timer_atomic.h b/include/etl/message_timer_atomic.h index 584cc491..93e328c3 100644 --- a/include/etl/message_timer_atomic.h +++ b/include/etl/message_timer_atomic.h @@ -466,7 +466,6 @@ namespace etl timer_list(timer_data* ptimers_) : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) - , current(etl::timer::id::NO_TIMER) , ptimers(ptimers_) { } @@ -595,22 +594,13 @@ namespace etl //******************************* etl::timer::id::type begin() { - current = head; - return current; - } - - //******************************* - etl::timer::id::type previous(etl::timer::id::type last) - { - current = ptimers[last].previous; - return current; + return head; } //******************************* etl::timer::id::type next(etl::timer::id::type last) { - current = ptimers[last].next; - return current; + return ptimers[last].next; } //******************************* @@ -625,16 +615,14 @@ namespace etl timer.next = etl::timer::id::NO_TIMER; } - head = etl::timer::id::NO_TIMER; - tail = etl::timer::id::NO_TIMER; - current = etl::timer::id::NO_TIMER; + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; } private: etl::timer::id::type head; etl::timer::id::type tail; - etl::timer::id::type current; timer_data* const ptimers; }; diff --git a/include/etl/message_timer_interrupt.h b/include/etl/message_timer_interrupt.h index acb3c344..5aaeb424 100644 --- a/include/etl/message_timer_interrupt.h +++ b/include/etl/message_timer_interrupt.h @@ -472,7 +472,6 @@ namespace etl timer_list(timer_data* ptimers_) : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) - , current(etl::timer::id::NO_TIMER) , ptimers(ptimers_) { } @@ -601,22 +600,13 @@ namespace etl //******************************* etl::timer::id::type begin() { - current = head; - return current; - } - - //******************************* - etl::timer::id::type previous(etl::timer::id::type last) - { - current = ptimers[last].previous; - return current; + return head; } //******************************* etl::timer::id::type next(etl::timer::id::type last) { - current = ptimers[last].next; - return current; + return ptimers[last].next; } //******************************* @@ -631,16 +621,14 @@ namespace etl timer.next = etl::timer::id::NO_TIMER; } - head = etl::timer::id::NO_TIMER; - tail = etl::timer::id::NO_TIMER; - current = etl::timer::id::NO_TIMER; + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; } private: etl::timer::id::type head; etl::timer::id::type tail; - etl::timer::id::type current; timer_data* const ptimers; }; diff --git a/include/etl/message_timer_locked.h b/include/etl/message_timer_locked.h index 8826ad5e..4eb689ee 100644 --- a/include/etl/message_timer_locked.h +++ b/include/etl/message_timer_locked.h @@ -480,7 +480,6 @@ namespace etl timer_list(timer_data* ptimers_) : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) - , current(etl::timer::id::NO_TIMER) , ptimers(ptimers_) { } @@ -609,22 +608,13 @@ namespace etl //******************************* etl::timer::id::type begin() { - current = head; - return current; - } - - //******************************* - etl::timer::id::type previous(etl::timer::id::type last) - { - current = ptimers[last].previous; - return current; + return head; } //******************************* etl::timer::id::type next(etl::timer::id::type last) { - current = ptimers[last].next; - return current; + return ptimers[last].next; } //******************************* @@ -639,16 +629,14 @@ namespace etl timer.next = etl::timer::id::NO_TIMER; } - head = etl::timer::id::NO_TIMER; - tail = etl::timer::id::NO_TIMER; - current = etl::timer::id::NO_TIMER; + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; } private: etl::timer::id::type head; etl::timer::id::type tail; - etl::timer::id::type current; timer_data* const ptimers; }; diff --git a/include/etl/ranges.h b/include/etl/ranges.h index 8e875b83..5fd8b6fe 100644 --- a/include/etl/ranges.h +++ b/include/etl/ranges.h @@ -3639,20 +3639,6 @@ namespace etl template concat_view(Ranges&&...) -> concat_view...>; - struct concat_range_adapter_closure : public range_adapter_closure - { - template - using target_view_type = concat_view; - - constexpr concat_range_adapter_closure() = default; - - template - constexpr auto operator()(Ranges&&... r) const - { - return concat_view(views::all(etl::forward(r))...); - } - }; - namespace views { namespace private_views diff --git a/include/etl/vector.h b/include/etl/vector.h index ebdc5d1e..75afe585 100644 --- a/include/etl/vector.h +++ b/include/etl/vector.h @@ -1125,17 +1125,6 @@ namespace etl private: - //********************************************************************* - /// Create a new element with a default value at the back. - //********************************************************************* - void create_back() - { - etl::create_value_at(p_end); - ETL_INCREMENT_DEBUG_COUNT; - - ++p_end; - } - //********************************************************************* /// Create a new element with a value at the back //********************************************************************* diff --git a/test/test_memory.cpp b/test/test_memory.cpp index eeddc426..e0e457c6 100644 --- a/test/test_memory.cpp +++ b/test/test_memory.cpp @@ -2980,5 +2980,31 @@ namespace CHECK_EQUAL(0, relocatable_t::destructor_count); } #endif + + //************************************************************************* + TEST(test_wipe_on_destruct) + { + struct Data : public etl::wipe_on_destruct + { + uint32_t d1; + uint32_t d2; + char d3; + }; + + alignas(Data) unsigned char buffer[sizeof(Data)] = {0}; + + // Construct a Data object in the buffer with known non-zero values. + Data* p = new (buffer) Data(); + p->d1 = 0x12345678UL; + p->d2 = 0xAABBCCDDUL; + p->d3 = char(0xEE); + + // Destroy the object; wipe_on_destruct should zero the memory. + p->~Data(); + + // Verify the memory occupied by the object has been cleared. + unsigned char zeroes[sizeof(Data)] = {0}; + CHECK(memcmp(buffer, zeroes, sizeof(Data)) == 0); + } } } // namespace diff --git a/test/test_ranges.cpp b/test/test_ranges.cpp index 01b20c95..3f4b4e00 100644 --- a/test/test_ranges.cpp +++ b/test/test_ranges.cpp @@ -3384,6 +3384,42 @@ namespace CHECK(it == ev.end()); } + //************************************************************************* + TEST(test_ranges_keys_view_alias) + { + std::vector> v = {{10, 1.1}, {20, 2.2}, {30, 3.3}}; + + using range_t = etl::ranges::views::all_t; + etl::ranges::keys_view kv(etl::ranges::views::all(v)); + + auto it = kv.begin(); + CHECK_EQUAL(10, *it); + ++it; + CHECK_EQUAL(20, *it); + ++it; + CHECK_EQUAL(30, *it); + ++it; + CHECK(it == kv.end()); + } + + //************************************************************************* + TEST(test_ranges_values_view_alias) + { + std::vector> v = {{10, 1.1}, {20, 2.2}, {30, 3.3}}; + + using range_t = etl::ranges::views::all_t; + etl::ranges::values_view vv(etl::ranges::views::all(v)); + + auto it = vv.begin(); + CHECK_CLOSE(1.1, *it, 0.001); + ++it; + CHECK_CLOSE(2.2, *it, 0.001); + ++it; + CHECK_CLOSE(3.3, *it, 0.001); + ++it; + CHECK(it == vv.end()); + } + //************************************************************************* TEST(test_ranges_views_keys) { From fdfd17a1c2d7259ed5c9093b354fe5fd31fad92b Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Wed, 8 Apr 2026 15:30:53 +0100 Subject: [PATCH 2/7] Added missing format and print headers from VS2022 project --- test/vs2022/etl.vcxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index 0e0b6601..57fee1e8 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -3541,6 +3541,7 @@ + From e8206cca83d2525bc054507d0efc218dfe6d951d Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 6 May 2026 10:10:10 +0200 Subject: [PATCH 3/7] Remove unnecessary includes (#1434) Co-authored-by: John Wellbelove --- include/etl/algorithm.h | 2 +- include/etl/array.h | 3 --- include/etl/base64.h | 3 --- include/etl/basic_format_spec.h | 1 - include/etl/basic_string.h | 1 - include/etl/bip_buffer_spsc_atomic.h | 1 - include/etl/bresenham_line.h | 2 +- include/etl/buffer_descriptors.h | 3 +-- include/etl/byte_stream.h | 4 +--- include/etl/callback_timer_atomic.h | 2 -- include/etl/callback_timer_interrupt.h | 1 - include/etl/callback_timer_locked.h | 1 - include/etl/container.h | 2 -- include/etl/cyclic_value.h | 3 --- include/etl/debounce.h | 3 --- include/etl/delegate_service.h | 3 ++- include/etl/deque.h | 1 - include/etl/flags.h | 2 -- include/etl/fnv_1.h | 1 - include/etl/gamma.h | 1 - include/etl/ihash.h | 2 -- include/etl/index_of_type.h | 3 ++- include/etl/integral_limits.h | 1 - include/etl/intrusive_links.h | 2 +- include/etl/invert.h | 2 -- include/etl/ipool.h | 3 ++- include/etl/jenkins.h | 1 - include/etl/limiter.h | 2 -- include/etl/limits.h | 1 - include/etl/memory.h | 1 - include/etl/message.h | 2 -- include/etl/message_broker.h | 2 -- include/etl/message_bus.h | 3 +-- include/etl/message_packet.h | 2 -- include/etl/message_router.h | 4 +--- include/etl/message_router_registry.h | 2 +- include/etl/not_null.h | 1 - include/etl/parameter_pack.h | 2 +- include/etl/quantize.h | 3 ++- include/etl/queue.h | 1 - include/etl/queue_lockable.h | 2 -- include/etl/queue_spsc_atomic.h | 1 - include/etl/queue_spsc_isr.h | 1 - include/etl/queue_spsc_locked.h | 1 - include/etl/ratio.h | 1 - include/etl/rescale.h | 1 - include/etl/scaled_rounding.h | 1 - include/etl/stack.h | 1 - include/etl/string_utilities.h | 2 +- include/etl/threshold.h | 1 - include/etl/type_lookup.h | 4 ++-- include/etl/type_select.h | 4 +++- include/etl/unordered_map.h | 1 - include/etl/unordered_multimap.h | 1 - include/etl/unordered_multiset.h | 1 - include/etl/unordered_set.h | 1 - include/etl/variant_pool.h | 2 +- include/etl/vector.h | 1 - 58 files changed, 24 insertions(+), 83 deletions(-) diff --git a/include/etl/algorithm.h b/include/etl/algorithm.h index 577ea394..91252319 100644 --- a/include/etl/algorithm.h +++ b/include/etl/algorithm.h @@ -51,7 +51,7 @@ SOFTWARE. #include "type_traits.h" #include "utility.h" -#include +#include #include #include "private/minmax_push.h" diff --git a/include/etl/array.h b/include/etl/array.h index 2051807b..ffa3a307 100644 --- a/include/etl/array.h +++ b/include/etl/array.h @@ -35,10 +35,7 @@ SOFTWARE. #include "algorithm.h" #include "error_handler.h" #include "exception.h" -#include "functional.h" -#include "initializer_list.h" #include "iterator.h" -#include "nth_type.h" #include "parameter_type.h" #include "static_assert.h" #include "type_traits.h" diff --git a/include/etl/base64.h b/include/etl/base64.h index 4ce98b3c..03c73648 100644 --- a/include/etl/base64.h +++ b/include/etl/base64.h @@ -31,11 +31,8 @@ SOFTWARE. #include "error_handler.h" #include "exception.h" #include "integral_limits.h" -#include "static_assert.h" #include "type_traits.h" -#include - /************************************************************************************************************************************************************************** * See https://en.wikipedia.org/wiki/Base64 * diff --git a/include/etl/basic_format_spec.h b/include/etl/basic_format_spec.h index f80d0ec6..5c585d85 100644 --- a/include/etl/basic_format_spec.h +++ b/include/etl/basic_format_spec.h @@ -34,7 +34,6 @@ SOFTWARE. ///\ingroup string #include "platform.h" -#include "static_assert.h" #include "type_traits.h" #include "utility.h" diff --git a/include/etl/basic_string.h b/include/etl/basic_string.h index 4b834c74..7bc73534 100644 --- a/include/etl/basic_string.h +++ b/include/etl/basic_string.h @@ -39,7 +39,6 @@ SOFTWARE. #include "error_handler.h" #include "exception.h" #include "flags.h" -#include "functional.h" #include "integral_limits.h" #include "iterator.h" #include "memory.h" diff --git a/include/etl/bip_buffer_spsc_atomic.h b/include/etl/bip_buffer_spsc_atomic.h index 783e3ea6..fcb955da 100644 --- a/include/etl/bip_buffer_spsc_atomic.h +++ b/include/etl/bip_buffer_spsc_atomic.h @@ -51,7 +51,6 @@ SOFTWARE. #include "utility.h" #include -#include #if ETL_HAS_ATOMIC diff --git a/include/etl/bresenham_line.h b/include/etl/bresenham_line.h index 02ebc49a..a2888d9d 100644 --- a/include/etl/bresenham_line.h +++ b/include/etl/bresenham_line.h @@ -33,10 +33,10 @@ SOFTWARE. #include "platform.h" #include "iterator.h" -#include "static_assert.h" #include "type_traits.h" #include "utility.h" +#include #include namespace etl diff --git a/include/etl/buffer_descriptors.h b/include/etl/buffer_descriptors.h index 100846d7..61ec7890 100644 --- a/include/etl/buffer_descriptors.h +++ b/include/etl/buffer_descriptors.h @@ -36,10 +36,9 @@ SOFTWARE. #include "array.h" #include "cyclic_value.h" #include "delegate.h" -#include "static_assert.h" #include "type_traits.h" -#include +#include #if ETL_USING_CPP11 diff --git a/include/etl/byte_stream.h b/include/etl/byte_stream.h index 4af209ed..51dc9e92 100644 --- a/include/etl/byte_stream.h +++ b/include/etl/byte_stream.h @@ -40,13 +40,11 @@ SOFTWARE. #include "integral_limits.h" #include "iterator.h" #include "memory.h" -#include "nullptr.h" #include "optional.h" #include "span.h" #include "type_traits.h" -#include -#include +#include namespace etl { diff --git a/include/etl/callback_timer_atomic.h b/include/etl/callback_timer_atomic.h index a95c191f..98d92651 100644 --- a/include/etl/callback_timer_atomic.h +++ b/include/etl/callback_timer_atomic.h @@ -33,8 +33,6 @@ SOFTWARE. #include "algorithm.h" #include "delegate.h" #include "error_handler.h" -#include "function.h" -#include "nullptr.h" #include "placement_new.h" #include "static_assert.h" #include "timer.h" diff --git a/include/etl/callback_timer_interrupt.h b/include/etl/callback_timer_interrupt.h index 59cdf845..a67754cb 100644 --- a/include/etl/callback_timer_interrupt.h +++ b/include/etl/callback_timer_interrupt.h @@ -33,7 +33,6 @@ SOFTWARE. #include "algorithm.h" #include "delegate.h" #include "error_handler.h" -#include "nullptr.h" #include "placement_new.h" #include "static_assert.h" #include "timer.h" diff --git a/include/etl/callback_timer_locked.h b/include/etl/callback_timer_locked.h index 85119025..40c1161b 100644 --- a/include/etl/callback_timer_locked.h +++ b/include/etl/callback_timer_locked.h @@ -33,7 +33,6 @@ SOFTWARE. #include "algorithm.h" #include "delegate.h" #include "error_handler.h" -#include "nullptr.h" #include "placement_new.h" #include "static_assert.h" #include "timer.h" diff --git a/include/etl/container.h b/include/etl/container.h index c020dc7d..0452d2b4 100644 --- a/include/etl/container.h +++ b/include/etl/container.h @@ -34,6 +34,4 @@ SOFTWARE. #include "platform.h" #include "iterator.h" -#include - #endif diff --git a/include/etl/cyclic_value.h b/include/etl/cyclic_value.h index d1c2f47b..c33b306b 100644 --- a/include/etl/cyclic_value.h +++ b/include/etl/cyclic_value.h @@ -38,11 +38,8 @@ SOFTWARE. #include "platform.h" #include "algorithm.h" #include "exception.h" -#include "static_assert.h" #include "type_traits.h" -#include - namespace etl { //*************************************************************************** diff --git a/include/etl/debounce.h b/include/etl/debounce.h index f554c8b9..2618d5e3 100644 --- a/include/etl/debounce.h +++ b/include/etl/debounce.h @@ -32,9 +32,6 @@ SOFTWARE. #define ETL_DEBOUNCE_INCLUDED #include "platform.h" -#include "static_assert.h" - -#include namespace etl { diff --git a/include/etl/delegate_service.h b/include/etl/delegate_service.h index 8b7ef718..6ca7b40c 100644 --- a/include/etl/delegate_service.h +++ b/include/etl/delegate_service.h @@ -34,9 +34,10 @@ SOFTWARE. #include "platform.h" #include "array.h" #include "delegate.h" -#include "nullptr.h" #include "static_assert.h" +#include + namespace etl { //*************************************************************************** diff --git a/include/etl/deque.h b/include/etl/deque.h index 697436fc..e88a0fd3 100644 --- a/include/etl/deque.h +++ b/include/etl/deque.h @@ -44,7 +44,6 @@ SOFTWARE. #include "utility.h" #include -#include #include "private/minmax_push.h" diff --git a/include/etl/flags.h b/include/etl/flags.h index 4fd77d89..3896458e 100644 --- a/include/etl/flags.h +++ b/include/etl/flags.h @@ -39,8 +39,6 @@ SOFTWARE. #include "type_traits.h" #include -#include -#include namespace etl { diff --git a/include/etl/fnv_1.h b/include/etl/fnv_1.h index 0ac9c5d7..69b7c5c0 100644 --- a/include/etl/fnv_1.h +++ b/include/etl/fnv_1.h @@ -34,7 +34,6 @@ SOFTWARE. #include "platform.h" #include "frame_check_sequence.h" #include "ihash.h" -#include "static_assert.h" #include "type_traits.h" #include diff --git a/include/etl/gamma.h b/include/etl/gamma.h index 4def080e..4ad83ffb 100644 --- a/include/etl/gamma.h +++ b/include/etl/gamma.h @@ -36,7 +36,6 @@ SOFTWARE. #include "type_traits.h" #include -#include namespace etl { diff --git a/include/etl/ihash.h b/include/etl/ihash.h index 10890044..08db0a3b 100644 --- a/include/etl/ihash.h +++ b/include/etl/ihash.h @@ -36,8 +36,6 @@ SOFTWARE. #include "exception.h" #include "utility.h" -#include - ///\defgroup ihash Common data for all hash type classes. ///\ingroup hash diff --git a/include/etl/index_of_type.h b/include/etl/index_of_type.h index 53128447..2d799832 100644 --- a/include/etl/index_of_type.h +++ b/include/etl/index_of_type.h @@ -31,7 +31,8 @@ SOFTWARE. #include "platform.h" #include "integral_limits.h" -#include "static_assert.h" + +#include namespace etl { diff --git a/include/etl/integral_limits.h b/include/etl/integral_limits.h index 45d7a026..cf741e30 100644 --- a/include/etl/integral_limits.h +++ b/include/etl/integral_limits.h @@ -35,7 +35,6 @@ SOFTWARE. #include "type_traits.h" #include -#include #include "private/minmax_push.h" diff --git a/include/etl/intrusive_links.h b/include/etl/intrusive_links.h index 6355ee0a..30966714 100644 --- a/include/etl/intrusive_links.h +++ b/include/etl/intrusive_links.h @@ -39,7 +39,7 @@ SOFTWARE. #include "type_traits.h" #include "utility.h" -#include +#include //***************************************************************************** // Note: diff --git a/include/etl/invert.h b/include/etl/invert.h index fc1a4e50..fddcebf9 100644 --- a/include/etl/invert.h +++ b/include/etl/invert.h @@ -35,8 +35,6 @@ SOFTWARE. #include "functional.h" #include "limits.h" -#include - namespace etl { //*************************************************************************** diff --git a/include/etl/ipool.h b/include/etl/ipool.h index e53c4014..1fdd996a 100644 --- a/include/etl/ipool.h +++ b/include/etl/ipool.h @@ -37,9 +37,10 @@ SOFTWARE. #include "iterator.h" #include "memory.h" #include "placement_new.h" -#include "static_assert.h" #include "utility.h" +#include + #define ETL_POOL_CPP03_CODE 0 namespace etl diff --git a/include/etl/jenkins.h b/include/etl/jenkins.h index 682a2187..5ea72099 100644 --- a/include/etl/jenkins.h +++ b/include/etl/jenkins.h @@ -36,7 +36,6 @@ SOFTWARE. #include "frame_check_sequence.h" #include "ihash.h" #include "iterator.h" -#include "static_assert.h" #include "type_traits.h" #include diff --git a/include/etl/limiter.h b/include/etl/limiter.h index 7876582a..5b285df2 100644 --- a/include/etl/limiter.h +++ b/include/etl/limiter.h @@ -36,8 +36,6 @@ SOFTWARE. #include "functional.h" #include "type_traits.h" -#include - namespace etl { namespace private_limiter diff --git a/include/etl/limits.h b/include/etl/limits.h index 5f98fd73..c274a337 100644 --- a/include/etl/limits.h +++ b/include/etl/limits.h @@ -43,7 +43,6 @@ SOFTWARE. #include #include #include -#include #include "private/minmax_push.h" diff --git a/include/etl/memory.h b/include/etl/memory.h index c25af960..6bd07d61 100644 --- a/include/etl/memory.h +++ b/include/etl/memory.h @@ -42,7 +42,6 @@ SOFTWARE. #include "private/addressof.h" -#include #include #if defined(ETL_IN_UNIT_TEST) || ETL_USING_STL diff --git a/include/etl/message.h b/include/etl/message.h index 4002023c..a21769c6 100644 --- a/include/etl/message.h +++ b/include/etl/message.h @@ -36,8 +36,6 @@ SOFTWARE. #include "static_assert.h" #include "type_traits.h" -#include - namespace etl { //*************************************************************************** diff --git a/include/etl/message_broker.h b/include/etl/message_broker.h index b8dc8ad8..25e54f62 100644 --- a/include/etl/message_broker.h +++ b/include/etl/message_broker.h @@ -36,8 +36,6 @@ SOFTWARE. #include "nullptr.h" #include "span.h" -#include - namespace etl { //*************************************************************************** diff --git a/include/etl/message_bus.h b/include/etl/message_bus.h index c2e7bfbc..7899b7d9 100644 --- a/include/etl/message_bus.h +++ b/include/etl/message_bus.h @@ -36,10 +36,9 @@ SOFTWARE. #include "message.h" #include "message_router.h" #include "message_types.h" -#include "nullptr.h" #include "vector.h" -#include +#include namespace etl { diff --git a/include/etl/message_packet.h b/include/etl/message_packet.h index d42eca61..fdaed2b3 100644 --- a/include/etl/message_packet.h +++ b/include/etl/message_packet.h @@ -39,8 +39,6 @@ SOFTWARE. #include "type_list.h" #include "utility.h" -#include - namespace etl { #if ETL_USING_CPP17 && !defined(ETL_MESSAGE_PACKET_FORCE_CPP03_IMPLEMENTATION) diff --git a/include/etl/message_router.h b/include/etl/message_router.h index 216b4710..e1454c4b 100644 --- a/include/etl/message_router.h +++ b/include/etl/message_router.h @@ -38,14 +38,12 @@ SOFTWARE. #include "message.h" #include "message_packet.h" #include "message_types.h" -#include "nullptr.h" -#include "placement_new.h" #include "shared_message.h" #include "successor.h" #include "type_list.h" #include "type_traits.h" -#include +#include namespace etl { diff --git a/include/etl/message_router_registry.h b/include/etl/message_router_registry.h index b57ac6b9..87942090 100644 --- a/include/etl/message_router_registry.h +++ b/include/etl/message_router_registry.h @@ -38,7 +38,7 @@ SOFTWARE. #include "memory.h" #include "message_router.h" -#include +#include namespace etl { diff --git a/include/etl/not_null.h b/include/etl/not_null.h index fc8366f3..25823d68 100644 --- a/include/etl/not_null.h +++ b/include/etl/not_null.h @@ -35,7 +35,6 @@ SOFTWARE. #include "error_handler.h" #include "exception.h" #include "memory.h" -#include "static_assert.h" #include "type_traits.h" namespace etl diff --git a/include/etl/parameter_pack.h b/include/etl/parameter_pack.h index 267065bb..bed2949c 100644 --- a/include/etl/parameter_pack.h +++ b/include/etl/parameter_pack.h @@ -32,7 +32,7 @@ SOFTWARE. #include "platform.h" #include "type_traits.h" -#include +#include #if ETL_CPP11_SUPPORTED namespace etl diff --git a/include/etl/quantize.h b/include/etl/quantize.h index b36add55..a789a380 100644 --- a/include/etl/quantize.h +++ b/include/etl/quantize.h @@ -35,8 +35,9 @@ SOFTWARE. #include "functional.h" #include "type_traits.h" +#include + ////#include -#include namespace etl { diff --git a/include/etl/queue.h b/include/etl/queue.h index 0c702507..fef81d77 100644 --- a/include/etl/queue.h +++ b/include/etl/queue.h @@ -44,7 +44,6 @@ SOFTWARE. #include "utility.h" #include -#include //***************************************************************************** ///\defgroup queue queue diff --git a/include/etl/queue_lockable.h b/include/etl/queue_lockable.h index c05a4faf..8821a9b7 100644 --- a/include/etl/queue_lockable.h +++ b/include/etl/queue_lockable.h @@ -32,7 +32,6 @@ SOFTWARE. #define ETL_QUEUE_LOCKABLE_INCLUDED #include "platform.h" -#include "function.h" #include "integral_limits.h" #include "memory.h" #include "memory_model.h" @@ -42,7 +41,6 @@ SOFTWARE. #include "utility.h" #include -#include namespace etl { diff --git a/include/etl/queue_spsc_atomic.h b/include/etl/queue_spsc_atomic.h index b14d749b..c162500f 100644 --- a/include/etl/queue_spsc_atomic.h +++ b/include/etl/queue_spsc_atomic.h @@ -41,7 +41,6 @@ SOFTWARE. #include "utility.h" #include -#include #if ETL_HAS_ATOMIC diff --git a/include/etl/queue_spsc_isr.h b/include/etl/queue_spsc_isr.h index fcfd557e..d25e6d14 100644 --- a/include/etl/queue_spsc_isr.h +++ b/include/etl/queue_spsc_isr.h @@ -41,7 +41,6 @@ SOFTWARE. #include "utility.h" #include -#include namespace etl { diff --git a/include/etl/queue_spsc_locked.h b/include/etl/queue_spsc_locked.h index bc940612..18424100 100644 --- a/include/etl/queue_spsc_locked.h +++ b/include/etl/queue_spsc_locked.h @@ -42,7 +42,6 @@ SOFTWARE. #include "utility.h" #include -#include namespace etl { diff --git a/include/etl/ratio.h b/include/etl/ratio.h index 21a2d894..df16ecb0 100644 --- a/include/etl/ratio.h +++ b/include/etl/ratio.h @@ -37,7 +37,6 @@ SOFTWARE. #include "type_traits.h" -#include #include ///\defgroup ratio ratio diff --git a/include/etl/rescale.h b/include/etl/rescale.h index 54f8d265..fa183f22 100644 --- a/include/etl/rescale.h +++ b/include/etl/rescale.h @@ -37,7 +37,6 @@ SOFTWARE. #include "type_traits.h" // #include -#include namespace etl { diff --git a/include/etl/scaled_rounding.h b/include/etl/scaled_rounding.h index d09d0203..1c72a02a 100644 --- a/include/etl/scaled_rounding.h +++ b/include/etl/scaled_rounding.h @@ -33,7 +33,6 @@ SOFTWARE. #include "platform.h" #include "absolute.h" -#include "static_assert.h" #include "type_traits.h" namespace etl diff --git a/include/etl/stack.h b/include/etl/stack.h index 24d0165d..98c18812 100644 --- a/include/etl/stack.h +++ b/include/etl/stack.h @@ -44,7 +44,6 @@ SOFTWARE. #include "utility.h" #include -#include //***************************************************************************** ///\defgroup stack stack diff --git a/include/etl/string_utilities.h b/include/etl/string_utilities.h index aa540d9c..b00cb6ba 100644 --- a/include/etl/string_utilities.h +++ b/include/etl/string_utilities.h @@ -39,7 +39,7 @@ SOFTWARE. #include "optional.h" #include -#include +#include #include "private/minmax_push.h" diff --git a/include/etl/threshold.h b/include/etl/threshold.h index 76e9c00c..f1fbf09c 100644 --- a/include/etl/threshold.h +++ b/include/etl/threshold.h @@ -36,7 +36,6 @@ SOFTWARE. #include "type_traits.h" // #include -#include namespace etl { diff --git a/include/etl/type_lookup.h b/include/etl/type_lookup.h index 19b3b6f9..cb6dc803 100644 --- a/include/etl/type_lookup.h +++ b/include/etl/type_lookup.h @@ -32,10 +32,10 @@ SOFTWARE. #include "platform.h" #include "integral_limits.h" #include "null_type.h" -#include "static_assert.h" #include "type_traits.h" -#include +#include + namespace etl { //*************************************************************************** diff --git a/include/etl/type_select.h b/include/etl/type_select.h index 7be28812..ba386d65 100644 --- a/include/etl/type_select.h +++ b/include/etl/type_select.h @@ -31,8 +31,10 @@ SOFTWARE. #include "platform.h" #include "null_type.h" -#include "static_assert.h" #include "type_traits.h" + +#include + namespace etl { #if ETL_USING_CPP11 && !defined(ETL_TYPE_SELECT_FORCE_CPP03_IMPLEMENTATION) diff --git a/include/etl/unordered_map.h b/include/etl/unordered_map.h index cda34d8d..69531c7d 100644 --- a/include/etl/unordered_map.h +++ b/include/etl/unordered_map.h @@ -43,7 +43,6 @@ SOFTWARE. #include "intrusive_forward_list.h" #include "iterator.h" #include "nth_type.h" -#include "nullptr.h" #include "parameter_type.h" #include "placement_new.h" #include "pool.h" diff --git a/include/etl/unordered_multimap.h b/include/etl/unordered_multimap.h index 7e26a4ca..aa330abd 100644 --- a/include/etl/unordered_multimap.h +++ b/include/etl/unordered_multimap.h @@ -42,7 +42,6 @@ SOFTWARE. #include "intrusive_forward_list.h" #include "iterator.h" #include "nth_type.h" -#include "nullptr.h" #include "parameter_type.h" #include "placement_new.h" #include "pool.h" diff --git a/include/etl/unordered_multiset.h b/include/etl/unordered_multiset.h index 30c174c5..c8b6de7a 100644 --- a/include/etl/unordered_multiset.h +++ b/include/etl/unordered_multiset.h @@ -42,7 +42,6 @@ SOFTWARE. #include "intrusive_forward_list.h" #include "iterator.h" #include "nth_type.h" -#include "nullptr.h" #include "parameter_type.h" #include "placement_new.h" #include "pool.h" diff --git a/include/etl/unordered_set.h b/include/etl/unordered_set.h index b099f0bf..66b1cfe6 100644 --- a/include/etl/unordered_set.h +++ b/include/etl/unordered_set.h @@ -42,7 +42,6 @@ SOFTWARE. #include "intrusive_forward_list.h" #include "iterator.h" #include "nth_type.h" -#include "nullptr.h" #include "parameter_type.h" #include "placement_new.h" #include "pool.h" diff --git a/include/etl/variant_pool.h b/include/etl/variant_pool.h index b308a80d..690a9677 100644 --- a/include/etl/variant_pool.h +++ b/include/etl/variant_pool.h @@ -35,7 +35,7 @@ SOFTWARE. #include "static_assert.h" #include "type_traits.h" -#include +#include namespace etl { diff --git a/include/etl/vector.h b/include/etl/vector.h index 75afe585..73540e2b 100644 --- a/include/etl/vector.h +++ b/include/etl/vector.h @@ -40,7 +40,6 @@ SOFTWARE. #include "debug_count.h" #include "error_handler.h" #include "exception.h" -#include "functional.h" #include "initializer_list.h" #include "iterator.h" #include "memory.h" From fe17d32e9bcbfabcb25cc4d00a810bcd8527e8c4 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 6 May 2026 11:10:14 +0200 Subject: [PATCH 4/7] Fix meson build (#1431) Co-authored-by: John Wellbelove --- .github/workflows/meson-gcc-c++23-no-stl.yml | 31 ++++ docs/meson.md | 116 +++++++++++++ meson_options.txt | 1 + test/meson.build | 162 ++++++++++++++----- 4 files changed, 274 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/meson-gcc-c++23-no-stl.yml create mode 100644 docs/meson.md diff --git a/.github/workflows/meson-gcc-c++23-no-stl.yml b/.github/workflows/meson-gcc-c++23-no-stl.yml new file mode 100644 index 00000000..d8767317 --- /dev/null +++ b/.github/workflows/meson-gcc-c++23-no-stl.yml @@ -0,0 +1,31 @@ +name: meson-gcc-c++23-no-stl +on: + push: + branches: [ master, development, pull-request/* ] + pull_request: + branches: [ master, development, pull-request/* ] + types: [opened, synchronize, reopened] + +jobs: + + build-meson-gcc-cpp23-no-stl: + name: Meson GCC C++23 Linux - No STL + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + + - name: Install Meson + run: sudo apt-get install -y meson + + - name: Configure + run: | + export CC=gcc + export CXX=g++ + meson setup builddir -Duse_stl=false -Dcpp_std=c++23 + + - name: Build + run: meson compile -C builddir + + - name: Run tests + run: meson test -C builddir -v diff --git a/docs/meson.md b/docs/meson.md new file mode 100644 index 00000000..3fb318d3 --- /dev/null +++ b/docs/meson.md @@ -0,0 +1,116 @@ +# Building ETL with Meson + +## Prerequisites + +- [Meson](https://mesonbuild.com/) >= 0.57.0 +- A C++17 compiler (GCC, Clang, or MSVC) +- [Ninja](https://ninja-build.org/) (default Meson backend) + +UnitTest++ is fetched automatically as a Meson subproject — no manual dependency installation is needed. + +## Quick Start + +```bash +# Configure (from the project root) +meson setup builddir + +# Build +meson compile -C builddir + +# Run tests +meson test -C builddir +``` + +## Build Options + +### ETL project options + +| Option | Type | Default | Description | +|---------------------|------|---------|-------------------------------------------------------------------| +| `use_stl` | bool | `true` | Build with STL support. When `false`, defines `ETL_NO_STL`. | +| `enable_sanitizer` | bool | `false` | Enable AddressSanitizer and UndefinedBehaviorSanitizer (GCC/Clang only). | + +### Meson built-in options + +| Option | Type | Default | Description | +|-------------|--------|----------|----------------------------------------------------------| +| `cpp_std` | string | `c++17` | C++ standard to compile with (e.g. `c++20`, `c++23`). | +| `buildtype` | string | `debug` | Build type: `plain`, `debug`, `debugoptimized`, `release`, `minsize`. | +| `werror` | bool | `false` | Treat compiler warnings as errors. | + +These are handled by Meson directly — no `get_option()` call is needed in the build files. + +### Examples + +```bash +# No STL, C++23 +meson setup builddir -Duse_stl=false -Dcpp_std=c++23 + +# Release build with sanitizers +meson setup builddir -Dbuildtype=release -Denable_sanitizer=true + +# Override the C++ standard on an existing build directory +meson configure builddir -Dcpp_std=c++20 +``` + +## Selecting a Compiler + +The compiler is chosen at configure time via environment variables: + +```bash +# GCC +CC=gcc CXX=g++ meson setup builddir + +# Clang +CC=clang CXX=clang++ meson setup builddir + +# Specific versions +CC=gcc-14 CXX=g++-14 meson setup builddir +CC=clang-18 CXX=clang++-18 meson setup builddir +``` + +To switch compilers on an existing build directory, wipe it first: + +```bash +CC=clang CXX=clang++ meson setup --wipe builddir +``` + +Or use separate directories per compiler: + +```bash +CC=gcc CXX=g++ meson setup build-gcc +CC=clang CXX=clang++ meson setup build-clang +``` + +## Running Tests + +```bash +# Run all tests +meson test -C builddir + +# Verbose output (shows individual test results) +meson test -C builddir -v + +# Run the test binary directly +./builddir/test/etl_unit_tests +``` + +## Sanitizers + +On GCC and Clang, AddressSanitizer and UndefinedBehaviorSanitizer can be enabled via the `enable_sanitizer` option: + +```bash +meson setup builddir -Denable_sanitizer=true +``` + +Note: UBSan may prevent certain `constexpr` evaluations involving function pointers from compiling (e.g. in the closure tests). This matches the CMake build, where sanitizers are also opt-in via `ETL_ENABLE_SANITIZER=ON`. + +## Using ETL as a Subproject + +ETL can be consumed as a Meson subproject. In your project's `subprojects/` directory, create an `etl.wrap` file, then use: + +```meson +etl_dep = dependency('etl', fallback: ['etl', 'etl_dep']) +``` + +When built as a subproject, the ETL test suite is not compiled. diff --git a/meson_options.txt b/meson_options.txt index 6f64d611..5845d5f3 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1 +1,2 @@ option('use_stl', description: 'Compiling for STL', type: 'boolean', value: true) +option('enable_sanitizer', description: 'Enable address and undefined behavior sanitizers', type: 'boolean', value: false) diff --git a/test/meson.build b/test/meson.build index b84a21be..8de41419 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,4 +1,3 @@ - etl_test_sources = files( 'main.cpp', 'murmurhash3.cpp', @@ -8,48 +7,91 @@ etl_test_sources = files( 'test_array_view.cpp', 'test_array_wrapper.cpp', 'test_atomic.cpp', - 'test_base64_RFC2152_decoder.cppp', - 'test_base64_RFC2152_encoder.cppp', - 'test_base64_RFC3501_decoder.cppp', - 'test_base64_RFC3501_encoder.cppp', - 'test_base64_RFC4648_decoder_with_no_padding.cppp', - 'test_base64_RFC4648_decoder_with_padding.cppp', - 'test_base64_RFC4648_encoder_with_no_padding.cppp', - 'test_base64_RFC4648_encoder_with_padding.cppp', - 'test_base64_RFC4648_URL_decoder_with_no_padding.cppp', - 'test_base64_RFC4648_URL_decoder_with_padding.cppp', - 'test_base64_RFC4648_URL_encoder_with_no_padding.cppp', + 'test_base64_RFC2152_decoder.cpp', + 'test_base64_RFC2152_encoder.cpp', + 'test_base64_RFC3501_decoder.cpp', + 'test_base64_RFC3501_encoder.cpp', + 'test_base64_RFC4648_URL_decoder_with_no_padding.cpp', + 'test_base64_RFC4648_URL_decoder_with_padding.cpp', + 'test_base64_RFC4648_URL_encoder_with_no_padding.cpp', 'test_base64_RFC4648_URL_encoder_with_padding.cpp', + 'test_base64_RFC4648_decoder_with_no_padding.cpp', + 'test_base64_RFC4648_decoder_with_padding.cpp', + 'test_base64_RFC4648_encoder_with_no_padding.cpp', + 'test_base64_RFC4648_encoder_with_padding.cpp', 'test_binary.cpp', 'test_bip_buffer_spsc_atomic.cpp', 'test_bit.cpp', - 'test_bitset_legacy.cpp', - 'test_bitset_new_default_element_type.cpp', - 'test_bitset_new_explicit_single_element_type.cpp', - 'test_bitset_new_ext_default_element_type.cpp', - 'test_bitset_new_ext_explicit_single_element_type.cpp', 'test_bit_stream.cpp', 'test_bit_stream_reader_big_endian.cpp', 'test_bit_stream_reader_little_endian.cpp', 'test_bit_stream_writer_big_endian.cpp', 'test_bit_stream_writer_little_endian.cpp', - 'test_byte.cpp', - 'test_byte_stream.cpp', + 'test_bitset_legacy.cpp', + 'test_bitset_new_comparisons.cpp', + 'test_bitset_new_default_element_type.cpp', + 'test_bitset_new_explicit_single_element_type.cpp', + 'test_bitset_new_ext_default_element_type.cpp', + 'test_bitset_new_ext_explicit_single_element_type.cpp', 'test_bloom_filter.cpp', 'test_bresenham_line.cpp', 'test_bsd_checksum.cpp', 'test_buffer_descriptors.cpp', + 'test_byte.cpp', + 'test_byte_stream.cpp', 'test_callback_service.cpp', 'test_callback_timer.cpp', 'test_callback_timer_atomic.cpp', + 'test_callback_timer_deferred_locked.cpp', 'test_callback_timer_interrupt.cpp', 'test_callback_timer_locked.cpp', + 'test_char_traits.cpp', 'test_checksum.cpp', + 'test_chrono_clocks.cpp', + 'test_chrono_day.cpp', + 'test_chrono_duration.cpp', + 'test_chrono_hh_mm_ss.cpp', + 'test_chrono_literals.cpp', + 'test_chrono_month.cpp', + 'test_chrono_month_day.cpp', + 'test_chrono_month_day_last.cpp', + 'test_chrono_month_weekday.cpp', + 'test_chrono_month_weekday_last.cpp', + 'test_chrono_operators.cpp', + 'test_chrono_time_point.cpp', + 'test_chrono_weekday.cpp', + 'test_chrono_weekday_indexed.cpp', + 'test_chrono_weekday_last.cpp', + 'test_chrono_year.cpp', + 'test_chrono_year_month.cpp', + 'test_chrono_year_month_day.cpp', + 'test_chrono_year_month_day_last.cpp', + 'test_chrono_year_month_weekday.cpp', + 'test_chrono_year_month_weekday_last.cpp', 'test_circular_buffer.cpp', 'test_circular_buffer_external_buffer.cpp', 'test_circular_iterator.cpp', + 'test_closure_with_default_delegate.cpp', + 'test_closure_with_default_delegate_constexpr.cpp', + 'test_closure_with_inplace_function.cpp', 'test_compare.cpp', - 'test_compiler_settings.cpp', + 'test_concepts.cpp', + 'test_const_map.cpp', + 'test_const_map_constexpr.cpp', + 'test_const_map_ext.cpp', + 'test_const_map_ext_constexpr.cpp', + 'test_const_multimap.cpp', + 'test_const_multimap_constexpr.cpp', + 'test_const_multimap_ext.cpp', + 'test_const_multimap_ext_constexpr.cpp', + 'test_const_multiset.cpp', + 'test_const_multiset_constexpr.cpp', + 'test_const_multiset_ext.cpp', + 'test_const_multiset_ext_constexpr.cpp', + 'test_const_set.cpp', + 'test_const_set_constexpr.cpp', + 'test_const_set_ext.cpp', + 'test_const_set_ext_constexpr.cpp', 'test_constant.cpp', 'test_container.cpp', 'test_correlation.cpp', @@ -69,9 +111,12 @@ etl_test_sources = files( 'test_crc16_en13757.cpp', 'test_crc16_genibus.cpp', 'test_crc16_kermit.cpp', + 'test_crc16_m17.cpp', 'test_crc16_maxim.cpp', 'test_crc16_mcrf4xx.cpp', 'test_crc16_modbus.cpp', + 'test_crc16_opensafety_a.cpp', + 'test_crc16_opensafety_b.cpp', 'test_crc16_profibus.cpp', 'test_crc16_riello.cpp', 'test_crc16_t10dif.cpp', @@ -90,6 +135,7 @@ etl_test_sources = files( 'test_crc32_q.cpp', 'test_crc32_xfer.cpp', 'test_crc64_ecma.cpp', + 'test_crc64_iso.cpp', 'test_crc8_ccitt.cpp', 'test_crc8_cdma2000.cpp', 'test_crc8_darc.cpp', @@ -97,21 +143,29 @@ etl_test_sources = files( 'test_crc8_ebu.cpp', 'test_crc8_icode.cpp', 'test_crc8_itu.cpp', + 'test_crc8_j1850.cpp', + 'test_crc8_j1850_zero.cpp', 'test_crc8_maxim.cpp', + 'test_crc8_nrsc5.cpp', + 'test_crc8_opensafety.cpp', 'test_crc8_rohc.cpp', 'test_crc8_wcdma.cpp', 'test_cyclic_value.cpp', 'test_debounce.cpp', 'test_delegate.cpp', 'test_delegate_cpp03.cpp', + 'test_delegate_observable.cpp', 'test_delegate_service.cpp', 'test_delegate_service_compile_time.cpp', + 'test_delegate_service_cpp03.cpp', 'test_deque.cpp', 'test_endian.cpp', 'test_enum_type.cpp', 'test_error_handler.cpp', + 'test_etl_assert.cpp', 'test_etl_traits.cpp', 'test_exception.cpp', + 'test_expected.cpp', 'test_fixed_iterator.cpp', 'test_fixed_sized_memory_block_allocator.cpp', 'test_flags.cpp', @@ -120,18 +174,24 @@ etl_test_sources = files( 'test_flat_multiset.cpp', 'test_flat_set.cpp', 'test_fnv_1.cpp', + 'test_format.cpp', 'test_format_spec.cpp', 'test_forward_list.cpp', 'test_forward_list_shared_pool.cpp', 'test_fsm.cpp', 'test_function.cpp', + 'test_function_traits.cpp', 'test_functional.cpp', 'test_gamma.cpp', 'test_hash.cpp', 'test_hfsm.cpp', + 'test_hfsm_recurse_to_inner_state_on_start.cpp', + 'test_hfsm_transition_on_enter.cpp', 'test_histogram.cpp', + 'test_index_of_type.cpp', 'test_indirect_vector.cpp', 'test_indirect_vector_external_buffer.cpp', + 'test_inplace_function.cpp', 'test_instance_count.cpp', 'test_integral_limits.cpp', 'test_intrusive_forward_list.cpp', @@ -140,7 +200,9 @@ etl_test_sources = files( 'test_intrusive_queue.cpp', 'test_intrusive_stack.cpp', 'test_invert.cpp', + 'test_invoke.cpp', 'test_io_port.cpp', + 'test_is_invocable.cpp', 'test_iterator.cpp', 'test_jenkins.cpp', 'test_largest.cpp', @@ -148,14 +210,17 @@ etl_test_sources = files( 'test_limits.cpp', 'test_list.cpp', 'test_list_shared_pool.cpp', + 'test_macros.cpp', 'test_make_string.cpp', + 'test_manchester.cpp', 'test_map.cpp', 'test_math.cpp', 'test_math_functions.cpp', 'test_mean.cpp', 'test_mem_cast.cpp', 'test_mem_cast_ptr.cpp', - 'test_memory.cpp', + 'test_memory.cpp', + 'test_message.cpp', 'test_message_broker.cpp', 'test_message_bus.cpp', 'test_message_packet.cpp', @@ -163,18 +228,23 @@ etl_test_sources = files( 'test_message_router_registry.cpp', 'test_message_timer.cpp', 'test_message_timer_atomic.cpp', - 'test_message_timer_interrupt.cpp', + 'test_message_timer_interrupt.cpp', 'test_message_timer_locked.cpp', - 'test_multimap.cpp', - 'test_multiset.cpp', 'test_multi_array.cpp', 'test_multi_range.cpp', + 'test_multi_span.cpp', 'test_multi_vector.cpp', + 'test_multimap.cpp', + 'test_multiset.cpp', 'test_murmur3.cpp', + 'test_not_null_pointer.cpp', + 'test_not_null_pointer_constexpr.cpp', + 'test_not_null_unique_pointer.cpp', 'test_nth_type.cpp', 'test_numeric.cpp', 'test_observer.cpp', 'test_optional.cpp', + 'test_overload.cpp', 'test_packet.cpp', 'test_parameter_pack.cpp', 'test_parameter_type.cpp', @@ -184,6 +254,7 @@ etl_test_sources = files( 'test_poly_span_fixed_extent.cpp', 'test_pool.cpp', 'test_pool_external_buffer.cpp', + 'test_print.cpp', 'test_priority_queue.cpp', 'test_pseudo_moving_average.cpp', 'test_quantize.cpp', @@ -200,45 +271,54 @@ etl_test_sources = files( 'test_queue_spsc_locked.cpp', 'test_queue_spsc_locked_small.cpp', 'test_random.cpp', + 'test_ranges.cpp', + 'test_ratio.cpp', 'test_reference_flat_map.cpp', 'test_reference_flat_multimap.cpp', 'test_reference_flat_multiset.cpp', 'test_reference_flat_set.cpp', 'test_rescale.cpp', + 'test_result.cpp', 'test_rms.cpp', + 'test_rounded_integral_division.cpp', 'test_scaled_rounding.cpp', 'test_set.cpp', 'test_shared_message.cpp', + 'test_signal.cpp', 'test_singleton.cpp', + 'test_singleton_base.cpp', 'test_smallest.cpp', 'test_span_dynamic_extent.cpp', 'test_span_fixed_extent.cpp', 'test_stack.cpp', 'test_standard_deviation.cpp', 'test_state_chart.cpp', - 'test_state_chart_with_data_parameter.cpp', - 'test_state_chart_with_rvalue_data_parameter.cpp', 'test_state_chart_compile_time.cpp', 'test_state_chart_compile_time_with_data_parameter.cpp', + 'test_state_chart_with_data_parameter.cpp', + 'test_state_chart_with_rvalue_data_parameter.cpp', 'test_string_char.cpp', 'test_string_char_external_buffer.cpp', 'test_string_stream.cpp', - 'test_string_u8.cpp', - 'test_string_u8_external_buffer.cpp', 'test_string_stream_u16.cpp', 'test_string_stream_u32.cpp', + 'test_string_stream_u8.cpp', 'test_string_stream_wchar_t.cpp', 'test_string_u16.cpp', 'test_string_u16_external_buffer.cpp', 'test_string_u32.cpp', 'test_string_u32_external_buffer.cpp', + 'test_string_u8.cpp', + 'test_string_u8_external_buffer.cpp', 'test_string_utilities.cpp', 'test_string_utilities_std.cpp', 'test_string_utilities_std_u16.cpp', 'test_string_utilities_std_u32.cpp', + 'test_string_utilities_std_u8.cpp', 'test_string_utilities_std_wchar_t.cpp', 'test_string_utilities_u16.cpp', 'test_string_utilities_u32.cpp', + 'test_string_utilities_u8.cpp', 'test_string_utilities_wchar_t.cpp', 'test_string_view.cpp', 'test_string_wchar_t.cpp', @@ -246,17 +326,25 @@ etl_test_sources = files( 'test_successor.cpp', 'test_task_scheduler.cpp', 'test_threshold.cpp', + 'test_to_arithmetic.cpp', + 'test_to_arithmetic_u16.cpp', + 'test_to_arithmetic_u32.cpp', + 'test_to_arithmetic_u8.cpp', + 'test_to_arithmetic_wchar_t.cpp', 'test_to_string.cpp', - 'test_to_u8string.cpp', 'test_to_u16string.cpp', 'test_to_u32string.cpp', + 'test_to_u8string.cpp', 'test_to_wstring.cpp', + 'test_tuple.cpp', 'test_type_def.cpp', + 'test_type_list.cpp', 'test_type_lookup.cpp', 'test_type_select.cpp', 'test_type_traits.cpp', 'test_unaligned_type.cpp', - 'test_unaligned_type_constexpr.cpp', + 'test_unaligned_type_ext.cpp', + 'test_uncopyable.cpp', 'test_unordered_map.cpp', 'test_unordered_multimap.cpp', 'test_unordered_multiset.cpp', @@ -265,9 +353,9 @@ etl_test_sources = files( 'test_utility.cpp', 'test_variance.cpp', 'test_variant_legacy.cpp', - 'test_variant_variadic.cpp', 'test_variant_pool.cpp', 'test_variant_pool_external_buffer.cpp', + 'test_variant_variadic.cpp', 'test_vector.cpp', 'test_vector_external_buffer.cpp', 'test_vector_non_trivial.cpp', @@ -280,24 +368,26 @@ etl_test_sources = files( compile_args = [ '-DENABLE_ETL_UNIT_TESTS', - '-DETL_DEBUG', ] link_args = [] if get_option('use_stl') compile_args += '-DETL_NO_STL=0' -elif +else compile_args += '-DETL_NO_STL=1' endif if meson.get_compiler('cpp').get_argument_syntax() == 'gcc' - compile_args += '-fsanitize=address,undefined' compile_args += '-fexceptions' compile_args += '-Wall' - compile_args += '-Wextra' + compile_args += '-Wextra' compile_args += '-Wno-non-virtual-dtor' #TODO remove and fix warning in code compile_args += '-Werror' - link_args += '-fsanitize=address,undefined' + + if get_option('enable_sanitizer') + compile_args += '-fsanitize=address,undefined' + link_args += '-fsanitize=address,undefined' + endif endif threads_dep = dependency('threads') From a5d279d5e450732af3a7a23c35f7ed648d419ad3 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 6 May 2026 11:13:02 +0200 Subject: [PATCH 5/7] Fix sanitizer use (#1429) * Fix sanitizer use A case issue prevented adding sanitizer in the tests. * Fix compiler warnings from actual sanitizer use --- .devcontainer/run-tests.sh | 2 +- include/etl/private/delegate_cpp11.h | 8 ++++++++ test/run-coverage.sh | 2 +- test/run-tests.sh | 6 +++--- test/test_bsd_checksum.cpp | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.devcontainer/run-tests.sh b/.devcontainer/run-tests.sh index 218dc032..9618a15b 100755 --- a/.devcontainer/run-tests.sh +++ b/.devcontainer/run-tests.sh @@ -41,7 +41,7 @@ elif [ "$2" = "inside_container" ] ; then cd build-$ARCH cmake -DCMAKE_TOOLCHAIN_FILE=../.devcontainer/$ARCH/toolchain-$ARCH.cmake \ -DBUILD_TESTS=ON -DNO_STL=ON -DETL_CXX_STANDARD=23 \ - -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_OPTIMISATION=-O0 -DETL_ENABLE_SANITIZER=Off -DETL_MESSAGES_ARE_NOT_VIRTUAL=OFF \ + -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_OPTIMISATION=-O0 -DETL_ENABLE_SANITIZER=OFF -DETL_MESSAGES_ARE_NOT_VIRTUAL=OFF \ ../test export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) cmake --build . diff --git a/include/etl/private/delegate_cpp11.h b/include/etl/private/delegate_cpp11.h index 9664797b..27b443c0 100644 --- a/include/etl/private/delegate_cpp11.h +++ b/include/etl/private/delegate_cpp11.h @@ -566,6 +566,14 @@ namespace etl //************************************************************************* ETL_NODISCARD ETL_CONSTEXPR14 bool is_valid() const ETL_NOEXCEPT { + // GCC's UBSan instruments function pointer comparisons, which prevents + // constexpr evaluation. Use implicit bool conversion at compile time + // to avoid the instrumented != comparison while still checking validity. + if (etl::is_constant_evaluated()) + { + return static_cast(invocation.stub); + } + return invocation.stub != ETL_NULLPTR; } diff --git a/test/run-coverage.sh b/test/run-coverage.sh index 9266cd1b..bf795262 100755 --- a/test/run-coverage.sh +++ b/test/run-coverage.sh @@ -58,7 +58,7 @@ for CXXSTD in 11 14 17 20 23 26; do -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF \ -DETL_OPTIMISATION=-O0 \ -DETL_CXX_STANDARD=$CXXSTD \ - -DETL_ENABLE_SANITIZER=Off \ + -DETL_ENABLE_SANITIZER=OFF \ -DETL_MESSAGES_ARE_NOT_VIRTUAL=OFF \ -DETL_USE_BUILTIN_MEM_FUNCTIONS=ON .. cmake --build . diff --git a/test/run-tests.sh b/test/run-tests.sh index 0381ac75..ba309963 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -163,11 +163,11 @@ fi # Set the sanitizer enable. Default OFF #****************************************************************************** if [ "$4" = "s" ]; then - sanitize="On" + sanitize="ON" elif [ "$4" = "n" ]; then - sanitize="Off" + sanitize="OFF" else - sanitize="Off" + sanitize="OFF" fi #****************************************************************************** diff --git a/test/test_bsd_checksum.cpp b/test/test_bsd_checksum.cpp index 9274b112..f95dcf12 100644 --- a/test/test_bsd_checksum.cpp +++ b/test/test_bsd_checksum.cpp @@ -50,7 +50,7 @@ namespace for (size_t i = 0UL; i < sizeof(value_type); ++i) { - uint8_t byte = static_cast((static_cast(value) >> (i * 8UL)) & 0xFFU); + uint8_t byte = static_cast((static_cast(value) >> (i * 8U)) & 0xFFU); checksum = etl::rotate_right(checksum) + byte; } } From c9198d089c96e66f0547e9b91914d62d8e43838f Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 6 May 2026 11:16:20 +0200 Subject: [PATCH 6/7] Add format checks at compile time to format.h (#1419) * Add format checks at compile time to format.h * format.h: Refactor padding calculation * format.h: Code cleanup --- docs/format.md | 13 +- include/etl/format.h | 701 +++++++++++++++++++++++++++---------------- test/test_format.cpp | 22 +- 3 files changed, 476 insertions(+), 260 deletions(-) diff --git a/docs/format.md b/docs/format.md index 1f3a5e37..02b4edda 100644 --- a/docs/format.md +++ b/docs/format.md @@ -365,8 +365,15 @@ etl::format_to(s, "a}b"); // unescaped } etl::format_to(s, "{:d}", sv); // invalid type for string_view ``` -> **Note:** On C++20 and later, compile-time format string validation through -> `consteval` is planned but not yet fully implemented. +> **Note:** On C++20 and later, format strings are validated at compile time +> via `consteval`. The checks cover syntax (balanced braces, valid format spec +> grammar, index bounds, no mixing of automatic and manual indexing) as well as +> type/specifier compatibility (e.g. `{:d}` is rejected for string arguments). +> A malformed format string produces a compile error whose diagnostic mentions +> `please_note_this_is_error_message_format_string_syntax_error`. +> +> On C++11–C++17, the same checks run at runtime and throw +> `etl::bad_format_string_exception`. ## 10. Differences from `std::format` @@ -378,5 +385,5 @@ etl::format_to(s, "{:d}", sv); // invalid type for string_view | **Floating-point support** | Always available | Opt-in via `ETL_USING_FORMAT_FLOATING_POINT`. | | **User-defined formatters** | `std::formatter` specialisations | Not yet supported. | | **Locale** | `L` flag uses `std::locale` | `L` flag is parsed but has no effect. | -| **Compile-time validation** | Enforced via `consteval` on C++20 | Planned; currently validates at run time and throws `etl::bad_format_string_exception`. | +| **Compile-time validation** | Enforced via `consteval` on C++20 | Enforced via `consteval` on C++20 (syntax and type/specifier compatibility); validates at run time and throws `etl::bad_format_string_exception` on C++11–C++17. | | **`format_to_n` return type** | `std::format_to_n_result` | Returns the underlying `OutputIt` directly. | diff --git a/include/etl/format.h b/include/etl/format.h index 8ee8514e..b237caa5 100644 --- a/include/etl/format.h +++ b/include/etl/format.h @@ -76,17 +76,409 @@ namespace etl } }; - template - ETL_CONSTEXPR14 bool check_f(const char* fmt) + #if ETL_USING_CPP20 + namespace private_format_check { - // to be implemented later - // return fmt[0] == 0; // actual check + // Type category for compile-time type/specifier compatibility checking + enum class type_category + { + NONE, // monostate + BOOLEAN, // bool + CHAR, // char + INTEGER, // int, unsigned, long long, unsigned long long, short, etc. + FLOAT, // float, double, long double + STRING, // const char*, string_view + POINTER // const void* + }; + + // Map a type to its category. Decays and removes cv-qualifiers. + template + constexpr type_category get_type_category() + { + using U = typename etl::remove_cv::type>::type; + + // Order matters: bool before integral, char before integral + if (etl::is_same::value) + return type_category::BOOLEAN; + if (etl::is_same::value) + return type_category::CHAR; + if (etl::is_same::value) + return type_category::CHAR; + if (etl::is_same::value) + return type_category::CHAR; + if (etl::is_integral::value) + return type_category::INTEGER; + if (etl::is_same::value) + return type_category::FLOAT; + if (etl::is_same::value) + return type_category::FLOAT; + if (etl::is_same::value) + return type_category::FLOAT; + if (etl::is_same::value) + return type_category::STRING; + if (etl::is_same::value) + return type_category::STRING; + if (etl::is_same::value) + return type_category::STRING; + if (etl::is_base_of::value) + return type_category::STRING; + if (etl::is_pointer::value) + return type_category::POINTER; + if (etl::is_same::value) + return type_category::POINTER; + if (etl::is_same::value) + return type_category::POINTER; + return type_category::NONE; // unknown type: custom formatter, be permissive + } + + // Check if a format type character is valid for a given type category. + // '\0' means no explicit type was specified (always valid — uses default presentation). + inline constexpr bool ct_check_type_spec(type_category cat, char type_char) + { + if (type_char == '\0') + { + return true; // no explicit type: always OK, uses default + } + + switch (cat) + { + case type_category::BOOLEAN: + // bool: s (as "true"/"false"), b, B, c, d, o, x, X (as integer 0/1) + return type_char == 's' || type_char == 'b' || type_char == 'B' || type_char == 'c' || type_char == 'd' || type_char == 'o' + || type_char == 'x' || type_char == 'X'; + + case type_category::CHAR: + // char: c (default), b, B, d, o, x, X (as integer), s, ? + return type_char == 'c' || type_char == '?' || type_char == 'b' || type_char == 'B' || type_char == 'd' || type_char == 'o' + || type_char == 'x' || type_char == 'X' || type_char == 's'; + + case type_category::INTEGER: + // integers: b, B, c, d, o, x, X + return type_char == 'b' || type_char == 'B' || type_char == 'c' || type_char == 'd' || type_char == 'o' || type_char == 'x' + || type_char == 'X'; + + case type_category::FLOAT: + // floats: a, A, e, E, f, F, g, G + return type_char == 'a' || type_char == 'A' || type_char == 'e' || type_char == 'E' || type_char == 'f' || type_char == 'F' + || type_char == 'g' || type_char == 'G'; + + case type_category::STRING: + // strings: s, ? + return type_char == 's' || type_char == '?'; + + case type_category::POINTER: + // pointers: p, P + return type_char == 'p' || type_char == 'P'; + + case type_category::NONE: + default: return true; // unknown/custom type: be permissive, let runtime handle it + } + } + + inline constexpr bool ct_is_digit(char c) + { + return c >= '0' && c <= '9'; + } + + // Parse an unsigned integer from fmt starting at pos. Updates pos past the digits. + // Returns the parsed number, or -1 if no digits found, or -2 on overflow. + inline constexpr int ct_parse_num(const char* fmt, int& pos) + { + if (!ct_is_digit(fmt[pos])) + { + return -1; + } + int result = 0; + while (ct_is_digit(fmt[pos])) + { + int new_result = result * 10 + (fmt[pos] - '0'); + if (new_result < result) + { + // Overflow detected + return -2; + } + result = new_result; + ++pos; + } + return result; + } + + inline constexpr bool ct_is_align(char c) + { + return c == '<' || c == '>' || c == '^'; + } + + inline constexpr bool ct_is_sign(char c) + { + return c == '+' || c == '-' || c == ' '; + } + + inline constexpr bool ct_is_type(char c) + { + // All valid type characters from the format spec + return (c == 's') || (c == '?') || (c == 'b') || (c == 'B') || (c == 'c') || (c == 'd') || (c == 'o') || (c == 'x') || (c == 'X') || (c == 'a') + || (c == 'A') || (c == 'e') || (c == 'E') || (c == 'f') || (c == 'F') || (c == 'g') || (c == 'G') || (c == 'p') || (c == 'P'); + } + + // Validate a nested replacement field like {}, {0}, {1} inside width/precision. + // pos should be at the '{'. Updates pos past the closing '}'. + // Updates auto_count / has_manual / has_auto. Returns false on error. + inline constexpr bool ct_parse_nested_replacement(const char* fmt, int& pos, int n_args, int& auto_count, bool& has_auto, bool& has_manual) + { + if (fmt[pos] != '{') + return false; + ++pos; // skip '{' + + int num = ct_parse_num(fmt, pos); + if (num == -2) + return false; // overflow + if (num >= 0) + { + // manual index + if (has_auto) + return false; // mixing + has_manual = true; + if (num >= n_args) + return false; + } + else + { + // automatic index + if (has_manual) + return false; // mixing + has_auto = true; + if (auto_count >= n_args) + return false; + ++auto_count; + } + + if (fmt[pos] != '}') + return false; + ++pos; // skip '}' + return true; + } + + // Skip/validate the format-spec portion after the colon: + // [[fill]align][sign][#][0][width][.precision][L][type] + // pos is right after ':'. Returns false on invalid spec. + // parsed_type is set to the type character found, or '\0' if none. + inline constexpr bool ct_skip_format_spec(const char* fmt, int& pos, int n_args, int& auto_count, bool& has_auto, bool& has_manual, + char& parsed_type, bool& parsed_has_precision) + { + parsed_type = '\0'; + parsed_has_precision = false; + + if (fmt[pos] == '\0' || fmt[pos] == '}') + { + return true; // empty spec is valid + } + + // fill-and-align: either [align] or [fill][align] + // Look ahead: if second char is an align char, first is fill + if (fmt[pos + 1] != '\0' && ct_is_align(fmt[pos + 1])) + { + char fill = fmt[pos]; + if (fill == '{' || fill == '}') + return false; // { and } not allowed as fill + pos += 2; // skip fill + align + } + else if (ct_is_align(fmt[pos])) + { + ++pos; // skip align only + } + + // sign + if (ct_is_sign(fmt[pos])) + { + ++pos; + } + + // '#' + if (fmt[pos] == '#') + { + ++pos; + } + + // '0' + if (fmt[pos] == '0') + { + ++pos; + } + + // width: number or nested replacement + if (ct_is_digit(fmt[pos])) + { + if (ct_parse_num(fmt, pos) == -2) + return false; // overflow + } + else if (fmt[pos] == '{') + { + if (!ct_parse_nested_replacement(fmt, pos, n_args, auto_count, has_auto, has_manual)) + { + return false; + } + } + + // precision: '.' followed by number or nested replacement + bool has_precision = false; + if (fmt[pos] == '.') + { + has_precision = true; + ++pos; + if (ct_is_digit(fmt[pos])) + { + if (ct_parse_num(fmt, pos) == -2) + return false; // overflow + } + else if (fmt[pos] == '{') + { + if (!ct_parse_nested_replacement(fmt, pos, n_args, auto_count, has_auto, has_manual)) + { + return false; + } + } + // else: '.' with no precision number/replacement — still valid (empty precision) + } + + // locale-specific: 'L' + if (fmt[pos] == 'L') + { + ++pos; + } + + // type + if (ct_is_type(fmt[pos])) + { + parsed_type = fmt[pos]; + ++pos; + } + + // After parsing the spec, we must be at '}' (the closing brace is consumed by the caller) + // Any remaining characters before '}' means invalid spec + if (fmt[pos] != '}') + { + return false; + } + + parsed_has_precision = has_precision; + + return true; + } + } // namespace private_format_check + + template + constexpr bool check_format(const char* fmt) + { + const int n_args = static_cast(sizeof...(Args)); + int pos = 0; + int auto_count = 0; + bool has_auto = false; + bool has_manual = false; + + // Build a constexpr array mapping arg index -> type category + const private_format_check::type_category arg_categories[] = { + private_format_check::get_type_category()..., + private_format_check::type_category::NONE // sentinel for zero-arg case + }; + + while (fmt[pos] != '\0') + { + char c = fmt[pos]; + ++pos; + + if (c == '{') + { + if (fmt[pos] == '{') + { + // escaped '{' + ++pos; + continue; + } + + // Start of a replacement field: [arg_id][:format_spec] + int resolved_index = -1; + int arg_index = private_format_check::ct_parse_num(fmt, pos); + if (arg_index == -2) + return false; // overflow in arg index + if (arg_index >= 0) + { + // manual index + if (has_auto) + return false; // mixing auto and manual + has_manual = true; + if (arg_index >= n_args) + return false; // index out of range + resolved_index = arg_index; + } + else + { + // automatic index + if (has_manual) + return false; // mixing auto and manual + has_auto = true; + if (auto_count >= n_args) + return false; // too many arguments + resolved_index = auto_count; + ++auto_count; + } + + char type_char = '\0'; + bool has_precision = false; + if (fmt[pos] == ':') + { + ++pos; // skip ':' + if (!private_format_check::ct_skip_format_spec(fmt, pos, n_args, auto_count, has_auto, has_manual, type_char, has_precision)) + { + return false; + } + } + + // Validate type specifier against argument type + if (resolved_index >= 0 && resolved_index < n_args) + { + if (!private_format_check::ct_check_type_spec(arg_categories[resolved_index], type_char)) + { + return false; + } + + // Precision is not allowed for integer, boolean, or pointer types + if (has_precision) + { + auto cat = arg_categories[resolved_index]; + if (cat == private_format_check::type_category::INTEGER || cat == private_format_check::type_category::BOOLEAN + || cat == private_format_check::type_category::POINTER) + { + return false; + } + // Precision is also invalid for char when presented as char (not as integer) + if (cat == private_format_check::type_category::CHAR && (type_char == '\0' || type_char == 'c' || type_char == '?')) + { + return false; + } + } + } + + if (fmt[pos] != '}') + { + return false; // missing closing brace + } + ++pos; // skip '}' + } + else if (c == '}') + { + if (fmt[pos] != '}') + { + return false; // unmatched '}' + } + ++pos; // skip second '}' + } + } - (void)fmt; return true; } - inline void please_note_this_is_error_message_1() noexcept {} + inline void please_note_this_is_error_message_format_string_syntax_error() noexcept {} + #endif // ETL_USING_CPP20 template struct basic_format_string @@ -94,20 +486,15 @@ namespace etl inline ETL_CONSTEVAL basic_format_string(const char* fmt) : _sv(fmt) { - bool format_string_ok = check_f(fmt); - - if (!format_string_ok) + #if ETL_USING_CPP20 + // Compile-time validation: check_format runs at compile time via consteval. + // In pre-C++20, runtime checks in vformat_to/parse_format_spec/etc. are sufficient. + if (!check_format(fmt)) { - // if (etl::is_constant_evaluated()) // compile time error path - //{ - // // calling a non-constexpr function in a consteval context to - // trigger a compile error please_note_this_is_error_message_1(); - // } - // else // run time error path - //{ - ETL_ASSERT_FAIL_AND_RETURN(ETL_ERROR(bad_format_string_exception)); - //} + // Calling a non-constexpr function in a consteval context triggers a compile error. + please_note_this_is_error_message_format_string_syntax_error(); } + #endif } ETL_CONSTEXPR basic_format_string(const basic_format_string& other) = default; @@ -181,7 +568,6 @@ namespace etl // automatic number generation only allowed if not already in manual mode ETL_ASSERT(manual_mode == false, ETL_ERROR(bad_format_string_exception)); automatic_mode = true; - // TODO: compile time check ETL_ASSERT(current < num_args, ETL_ERROR(bad_format_string_exception) /* not enough arguments for generated index */); return current++; } @@ -598,18 +984,6 @@ namespace etl return false; } - inline bool parse_sequence(format_parse_context& parse_ctx, etl::string_view sequence) - { - auto fmt_it = parse_ctx.begin(); - if (etl::equal(sequence.cbegin(), sequence.cend(), fmt_it)) - { - fmt_it += sequence.size(); - parse_ctx.advance_to(fmt_it); - return true; - } - return false; - } - inline bool is_align_character(char c) { return c == '<' || c == '>' || c == '^'; @@ -1459,6 +1833,40 @@ namespace etl fmt_context.advance_to(tmp); } + // Compute prefix/suffix padding sizes for alignment. + // default_align_start: if true, NONE defaults to left-align (START); otherwise right-align (END). + inline void compute_padding(size_t pad, spec_align_t align, bool default_align_start, size_t& prefix_size, size_t& suffix_size) + { + switch (align) + { + case spec_align_t::START: + prefix_size = 0; + suffix_size = pad; + break; + case spec_align_t::CENTER: + prefix_size = pad / 2; + suffix_size = pad - prefix_size; + break; + case spec_align_t::END: + prefix_size = pad; + suffix_size = 0; + break; + case spec_align_t::NONE: + default: + if (default_align_start) + { + prefix_size = 0; + suffix_size = pad; + } + else + { + prefix_size = pad; + suffix_size = 0; + } + break; + } + } + template typename format_context::iterator format_aligned_int(Int arg, format_context& fmt_ctx) { @@ -1467,32 +1875,13 @@ namespace etl if (fmt_ctx.format_spec.width) { - // calculate size private_format::counter_iterator counter; private_format::format_num(counter, arg, fmt_ctx.format_spec); if (counter.value() < fmt_ctx.format_spec.width.value()) { size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); - switch (fmt_ctx.format_spec.align) - { - case private_format::spec_align_t::START: - prefix_size = 0; - suffix_size = pad; - break; - case private_format::spec_align_t::CENTER: - prefix_size = pad / 2; - suffix_size = pad - prefix_size; - break; - case private_format::spec_align_t::NONE: // default - case private_format::spec_align_t::END: - prefix_size = pad; - suffix_size = 0; - break; - default: - // invalid alignment specification - ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); - } + compute_padding(pad, fmt_ctx.format_spec.align, false, prefix_size, suffix_size); } } @@ -1513,32 +1902,13 @@ namespace etl if (fmt_ctx.format_spec.width) { - // calculate size private_format::counter_iterator counter; private_format::format_floating(counter, arg, fmt_ctx.format_spec); if (counter.value() < fmt_ctx.format_spec.width.value()) { size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); - switch (fmt_ctx.format_spec.align) - { - case private_format::spec_align_t::START: - prefix_size = 0; - suffix_size = pad; - break; - case private_format::spec_align_t::CENTER: - prefix_size = pad / 2; - suffix_size = pad - prefix_size; - break; - case private_format::spec_align_t::NONE: // default - case private_format::spec_align_t::END: - prefix_size = pad; - suffix_size = 0; - break; - default: - // invalid alignment specification - ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); - } + compute_padding(pad, fmt_ctx.format_spec.align, false, prefix_size, suffix_size); } } @@ -1609,32 +1979,13 @@ namespace etl if (fmt_ctx.format_spec.width) { - // calculate size private_format::counter_iterator counter; private_format::format_string_view(counter, arg, fmt_ctx.format_spec); if (counter.value() < fmt_ctx.format_spec.width.value()) { size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); - switch (fmt_ctx.format_spec.align) - { - case private_format::spec_align_t::NONE: // default - case private_format::spec_align_t::START: - prefix_size = 0; - suffix_size = pad; - break; - case private_format::spec_align_t::CENTER: - prefix_size = pad / 2; - suffix_size = pad - prefix_size; - break; - case private_format::spec_align_t::END: - prefix_size = pad; - suffix_size = 0; - break; - default: - // invalid alignment specification - ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); - } + compute_padding(pad, fmt_ctx.format_spec.align, true, prefix_size, suffix_size); } } @@ -1646,99 +1997,10 @@ namespace etl return it; } - template - void format_chars(OutputIt& it, const char* arg, const format_spec_t& spec) - { - bool escaped = false; - if (spec.type.has_value()) - { - switch (spec.type.value()) - { - case 's': - // default output - break; - case '?': - // escaped string - escaped = true; - break; - default: - // invalid type for string - ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); - } - } - size_t limit = etl::numeric_limits::max(); - if (spec.precision.has_value()) - { - limit = spec.precision.value(); - } - - if (escaped) - { - format_plain_char(it, '"'); - } - const char_type* arg_it = arg; - while (*arg_it != '\0' && limit > 0) - { - if (escaped) - { - format_escaped_char(it, *arg_it); - } - else - { - format_plain_char(it, *arg_it); - } - ++arg_it; - --limit; - } - if (escaped) - { - format_plain_char(it, '"'); - } - } - template typename format_context::iterator format_aligned_chars(const char* arg, format_context& fmt_ctx) { - size_t prefix_size = 0; - size_t suffix_size = 0; - - if (fmt_ctx.format_spec.width) - { - // calculate size - private_format::counter_iterator counter; - private_format::format_chars(counter, arg, fmt_ctx.format_spec); - - if (counter.value() < fmt_ctx.format_spec.width.value()) - { - size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); - switch (fmt_ctx.format_spec.align) - { - case private_format::spec_align_t::NONE: // default - case private_format::spec_align_t::START: - prefix_size = 0; - suffix_size = pad; - break; - case private_format::spec_align_t::CENTER: - prefix_size = pad / 2; - suffix_size = pad - prefix_size; - break; - case private_format::spec_align_t::END: - prefix_size = pad; - suffix_size = 0; - break; - default: - // invalid alignment specification - ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); - } - } - } - - // actual output - OutputIt it = fmt_ctx.out(); - private_format::fill(it, prefix_size, fmt_ctx.format_spec.fill); - private_format::format_chars(it, arg, fmt_ctx.format_spec); - private_format::fill(it, suffix_size, fmt_ctx.format_spec.fill); - return it; + return format_aligned_string_view(etl::string_view(arg), fmt_ctx); } inline void check_char_spec(const format_spec_t& spec) @@ -1793,43 +2055,16 @@ namespace etl if (fmt_ctx.format_spec.width) { - // calculate size private_format::counter_iterator counter; private_format::format_char(counter, arg, fmt_ctx.format_spec); if (counter.value() < fmt_ctx.format_spec.width.value()) { size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); - switch (fmt_ctx.format_spec.align) - { - case private_format::spec_align_t::NONE: // default - if (!fmt_ctx.format_spec.type.has_value() || fmt_ctx.format_spec.type.value() == 'c' || fmt_ctx.format_spec.type.value() == '?') - { - prefix_size = 0; - suffix_size = pad; - } - else - { - prefix_size = pad; - suffix_size = 0; - } - break; - case private_format::spec_align_t::START: - prefix_size = 0; - suffix_size = pad; - break; - case private_format::spec_align_t::CENTER: - prefix_size = pad / 2; - suffix_size = pad - prefix_size; - break; - case private_format::spec_align_t::END: - prefix_size = pad; - suffix_size = 0; - break; - default: - // invalid alignment specification - ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); - } + // char type defaults to left-align, integer presentation defaults to right-align + bool default_start = + !fmt_ctx.format_spec.type.has_value() || fmt_ctx.format_spec.type.value() == 'c' || fmt_ctx.format_spec.type.value() == '?'; + compute_padding(pad, fmt_ctx.format_spec.align, default_start, prefix_size, suffix_size); } } @@ -1877,32 +2112,13 @@ namespace etl if (fmt_ctx.format_spec.width) { - // calculate size private_format::counter_iterator counter; private_format::format_bool(counter, arg, fmt_ctx.format_spec); if (counter.value() < fmt_ctx.format_spec.width.value()) { size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); - switch (fmt_ctx.format_spec.align) - { - case private_format::spec_align_t::START: - prefix_size = 0; - suffix_size = pad; - break; - case private_format::spec_align_t::CENTER: - prefix_size = pad / 2; - suffix_size = pad - prefix_size; - break; - case private_format::spec_align_t::NONE: // default - case private_format::spec_align_t::END: - prefix_size = pad; - suffix_size = 0; - break; - default: - // invalid alignment specification - ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); - } + compute_padding(pad, fmt_ctx.format_spec.align, false, prefix_size, suffix_size); } } @@ -1946,32 +2162,13 @@ namespace etl if (fmt_ctx.format_spec.width) { - // calculate size private_format::counter_iterator counter; private_format::format_pointer(counter, arg, fmt_ctx.format_spec); if (counter.value() < fmt_ctx.format_spec.width.value()) { size_t pad = fmt_ctx.format_spec.width.value() - counter.value(); - switch (fmt_ctx.format_spec.align) - { - case private_format::spec_align_t::START: - prefix_size = 0; - suffix_size = pad; - break; - case private_format::spec_align_t::CENTER: - prefix_size = pad / 2; - suffix_size = pad - prefix_size; - break; - case private_format::spec_align_t::NONE: // default - case private_format::spec_align_t::END: - prefix_size = pad; - suffix_size = 0; - break; - default: - // invalid alignment specification - ETL_ASSERT_FAIL(ETL_ERROR(bad_format_string_exception)); - } + compute_padding(pad, fmt_ctx.format_spec.align, false, prefix_size, suffix_size); } } diff --git a/test/test_format.cpp b/test/test_format.cpp index 1a5e1476..78f2e413 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -403,7 +403,9 @@ namespace CHECK_EQUAL("data1", test_format(s, "{}", sv)); CHECK_EQUAL("data1", test_format(s, "{:s}", sv)); + #if !ETL_USING_CPP20 CHECK_THROW(test_format(s, "{:d}", sv), etl::bad_format_string_exception); + #endif CHECK_EQUAL("data1 ", test_format(s, "{:10s}", sv)); CHECK_EQUAL("data1 ", test_format(s, "{:<10s}", sv)); CHECK_EQUAL(" data1", test_format(s, "{:>10s}", sv)); @@ -432,7 +434,9 @@ namespace CHECK_EQUAL("data1", test_format(s, "{}", s_arg)); CHECK_EQUAL("data1", test_format(s, "{:s}", s_arg)); + #if !ETL_USING_CPP20 CHECK_THROW(test_format(s, "{:d}", s_arg), etl::bad_format_string_exception); + #endif CHECK_EQUAL("data1 ", test_format(s, "{:10s}", s_arg)); CHECK_EQUAL("data1 ", test_format(s, "{:<10s}", s_arg)); CHECK_EQUAL(" data1", test_format(s, "{:>10s}", s_arg)); @@ -466,7 +470,9 @@ namespace CHECK_EQUAL("data1", test_format(s, "{}", string_t(data))); CHECK_EQUAL("data1", test_format(s, "{:s}", string_t(data))); + #if !ETL_USING_CPP20 CHECK_THROW(test_format(s, "{:d}", string_t(data)), etl::bad_format_string_exception); + #endif CHECK_EQUAL("data1 ", test_format(s, "{:10s}", string_t(data))); CHECK_EQUAL("data1 ", test_format(s, "{:<10s}", string_t(data))); CHECK_EQUAL(" data1", test_format(s, "{:>10s}", string_t(data))); @@ -495,7 +501,9 @@ namespace CHECK_EQUAL("data1", test_format(s, "{}", chars)); CHECK_EQUAL("data1", test_format(s, "{:s}", chars)); + #if !ETL_USING_CPP20 CHECK_THROW(test_format(s, "{:d}", chars), etl::bad_format_string_exception); + #endif CHECK_EQUAL("data1 ", test_format(s, "{:10s}", chars)); CHECK_EQUAL("data1 ", test_format(s, "{:<10s}", chars)); CHECK_EQUAL(" data1", test_format(s, "{:>10s}", chars)); @@ -640,28 +648,28 @@ namespace { etl::string<100> s; + #if !ETL_USING_CPP20 + // These are caught at compile time in C++20 (consteval), so only test at runtime for pre-C++20 CHECK_THROW(test_format(s, "a{b}", 1), etl::bad_format_string_exception); // bad format index spec - // goal: rejected at compile time on C++20, error on <= C++17 CHECK_THROW(test_format(s, "a{b"), etl::bad_format_string_exception); // closing brace missing - // goal: rejected at compile time on C++20, error on <= C++17 CHECK_THROW(test_format(s, "a{b}"), etl::bad_format_string_exception); // arg missing - // goal: rejected at compile time on C++20, error on <= C++17 CHECK_THROW(test_format(s, "a}b"), etl::bad_format_string_exception); // bad format: only escaped // }} allowed - // goal: rejected at compile time on C++20, error on <= C++17 - CHECK_EQUAL("123", test_format(s, "{:}", 123)); // valid CHECK_THROW(test_format(s, "{::}", 123), etl::bad_format_string_exception); // bad format spec CHECK_THROW(test_format(s, "{1}", 123), etl::bad_format_string_exception); // bad index + #endif + + CHECK_EQUAL("123", test_format(s, "{:}", 123)); // valid } //************************************************************************* @@ -720,7 +728,9 @@ namespace CHECK_EQUAL(" 34 ", test_format(s, "{:^5}", 34)); CHECK_EQUAL(" -65 ", test_format(s, "{:^5}", -65)); CHECK_EQUAL("34 ", test_format(s, "{:<4}", 34)); + #if !ETL_USING_CPP20 CHECK_THROW(test_format(s, "a{:*5}", 34), etl::bad_format_string_exception); + #endif CHECK_EQUAL("a*34**", test_format(s, "a{:*^5}", 34)); CHECK_EQUAL("a*34**", test_format(s, "a{:*^5}", static_cast(34))); CHECK_EQUAL("a***-341234567890****", test_format(s, "a{:*^20}", static_cast(-341234567890))); @@ -773,7 +783,9 @@ namespace CHECK_EQUAL("00067", test_format(s, "{:05d}", 67)); CHECK_EQUAL("+00067", test_format(s, "{:+05d}", 67)); CHECK_EQUAL("+0X00EF1", test_format(s, "{:+#05X}", 0xEF1)); + #if !ETL_USING_CPP20 CHECK_THROW(test_format(s, "{:+#05.5X}", 0xEF1), etl::bad_format_string_exception); + #endif } } } // namespace From 079b3345d9f2e0b6048e40e9e0735dcef119ec04 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 6 May 2026 19:26:10 +0200 Subject: [PATCH 7/7] Various bugfixes (#1428) Co-authored-by: John Wellbelove --- include/etl/basic_string.h | 4 +- include/etl/binary.h | 16 +++--- include/etl/circular_buffer.h | 2 +- include/etl/deque.h | 6 +- include/etl/expected.h | 4 +- include/etl/forward_list.h | 6 +- include/etl/histogram.h | 4 +- include/etl/intrusive_links.h | 5 +- include/etl/string_view.h | 4 +- include/etl/type_traits.h | 24 +++++++- include/etl/utility.h | 2 +- test/test_binary.cpp | 102 ++++++++++++++++++++++++++++++++++ test/test_circular_buffer.cpp | 25 +++++++++ test/test_deque.cpp | 23 ++++++++ test/test_expected.cpp | 21 +++++++ test/test_forward_list.cpp | 22 ++++++++ test/test_histogram.cpp | 54 ++++++++++++++++++ test/test_intrusive_links.cpp | 10 ++++ test/test_string_char.cpp | 27 +++++++++ test/test_string_view.cpp | 37 +++++++++++- test/test_type_traits.cpp | 19 +++++++ test/test_utility.cpp | 46 +++++++++++++++ 22 files changed, 433 insertions(+), 30 deletions(-) diff --git a/include/etl/basic_string.h b/include/etl/basic_string.h index 7bc73534..59db587a 100644 --- a/include/etl/basic_string.h +++ b/include/etl/basic_string.h @@ -1466,9 +1466,7 @@ namespace etl //********************************************************************* size_type find(const_pointer s, size_type pos, size_type n) const { - size_t sz = etl::strlen(s); - - return find_impl(s, s + n, sz, pos); + return find_impl(s, s + n, n, pos); } //********************************************************************* diff --git a/include/etl/binary.h b/include/etl/binary.h index e7d9eec0..230fbf4a 100644 --- a/include/etl/binary.h +++ b/include/etl/binary.h @@ -1742,7 +1742,7 @@ namespace etl { count = 1U; - if ((value & 0xFFFFFFFFF0000000ULL) == 0U) + if ((value & 0xFFFFFFFF00000000ULL) == 0U) { value <<= 32U; count += 32U; @@ -1795,7 +1795,7 @@ namespace etl { typedef typename etl::make_unsigned::type unsigned_t; - return static_cast(count_trailing_ones(static_cast(value))); + return static_cast(count_leading_zeros(static_cast(value))); } #if ETL_USING_8BIT_TYPES @@ -1927,8 +1927,8 @@ namespace etl if ((value & 0xFFFF0000UL) == 0xFFFF0000UL) { - value <<= 8U; - count += 8U; + value <<= 16U; + count += 16U; } if ((value & 0xFF000000UL) == 0xFF000000UL) @@ -1988,14 +1988,14 @@ namespace etl if ((value & 0xFFFFFFFF00000000ULL) == 0xFFFFFFFF00000000ULL) { - value <<= 8U; - count += 8U; + value <<= 32U; + count += 32U; } if ((value & 0xFFFF000000000000ULL) == 0xFFFF000000000000ULL) { - value <<= 8U; - count += 8U; + value <<= 16U; + count += 16U; } if ((value & 0xFF00000000000000ULL) == 0xFF00000000000000ULL) diff --git a/include/etl/circular_buffer.h b/include/etl/circular_buffer.h index da6877e3..4c8290c4 100644 --- a/include/etl/circular_buffer.h +++ b/include/etl/circular_buffer.h @@ -1206,7 +1206,7 @@ namespace etl { this->clear(); - for (typename etl::icircular_buffer::const_iterator itr = other.begin(); itr != other.end(); ++itr) + for (typename etl::icircular_buffer::iterator itr = other.begin(); itr != other.end(); ++itr) { this->push(etl::move(*itr)); } diff --git a/include/etl/deque.h b/include/etl/deque.h index e88a0fd3..bd9afa46 100644 --- a/include/etl/deque.h +++ b/include/etl/deque.h @@ -605,10 +605,10 @@ namespace etl } //*************************************************** - reference operator[](size_t i) + const_reference operator[](size_t i) const { - iterator result(*this); - result += i; + const_iterator result(*this); + result += static_cast(i); return *result; } diff --git a/include/etl/expected.h b/include/etl/expected.h index 829c4247..655be8b1 100644 --- a/include/etl/expected.h +++ b/include/etl/expected.h @@ -206,7 +206,7 @@ namespace etl //******************************************* /// Get the error. //******************************************* - ETL_CONSTEXPR14 TError&& error() const&& ETL_NOEXCEPT + ETL_CONSTEXPR14 const TError&& error() const&& ETL_NOEXCEPT { return etl::move(error_value); } @@ -1309,7 +1309,7 @@ namespace etl template < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result::type>::type> auto transform_error(F&& f) const&& -> expected { - return transform_error_impl(etl::forward(f), *this); + return transform_error_impl(etl::forward(f), etl::move(*this)); } #endif diff --git a/include/etl/forward_list.h b/include/etl/forward_list.h index 4518be05..cfba7bf9 100644 --- a/include/etl/forward_list.h +++ b/include/etl/forward_list.h @@ -116,15 +116,15 @@ namespace etl }; //*************************************************************************** - /// Unsorted exception for the list. - ///\ingroup list + /// Unsorted exception for the forward_list. + ///\ingroup forward_list //*************************************************************************** class forward_list_no_pool : public forward_list_exception { public: forward_list_no_pool(string_type file_name_, numeric_type line_number_) - : forward_list_exception(ETL_ERROR_TEXT("list:no pool", ETL_FORWARD_LIST_FILE_ID"D"), file_name_, line_number_) + : forward_list_exception(ETL_ERROR_TEXT("forward_list:no pool", ETL_FORWARD_LIST_FILE_ID"D"), file_name_, line_number_) { } }; diff --git a/include/etl/histogram.h b/include/etl/histogram.h index 195076dd..95943b76 100644 --- a/include/etl/histogram.h +++ b/include/etl/histogram.h @@ -479,7 +479,7 @@ namespace etl //********************************* const_iterator end() const { - return accumulator.begin(); + return accumulator.end(); } //********************************* @@ -487,7 +487,7 @@ namespace etl //********************************* const_iterator cend() const { - return accumulator.cbegin(); + return accumulator.cend(); } //********************************* diff --git a/include/etl/intrusive_links.h b/include/etl/intrusive_links.h index 30966714..a1a9e653 100644 --- a/include/etl/intrusive_links.h +++ b/include/etl/intrusive_links.h @@ -890,7 +890,10 @@ namespace etl template typename etl::enable_if< etl::is_same >::value, void>::type link_clear_range(TLink* start) { - etl::link_clear_range(*start); + if (start != ETL_NULLPTR) + { + etl::link_clear_range(*start); + } } #if ETL_USING_CPP17 diff --git a/include/etl/string_view.h b/include/etl/string_view.h index 7f0162ec..1178e4c4 100644 --- a/include/etl/string_view.h +++ b/include/etl/string_view.h @@ -589,9 +589,9 @@ namespace etl return npos; } - position = etl::min(position, size()); + position = etl::min(position, size() - view.size()); - const_iterator iposition = etl::find_end(begin(), begin() + position, view.begin(), view.end()); + const_iterator iposition = etl::find_end(begin(), begin() + position + view.size(), view.begin(), view.end()); if (iposition == end()) { diff --git a/include/etl/type_traits.h b/include/etl/type_traits.h index 2b4372af..6f3b21bd 100644 --- a/include/etl/type_traits.h +++ b/include/etl/type_traits.h @@ -499,19 +499,19 @@ namespace etl }; #if ETL_HAS_NATIVE_CHAR8_T template <> - struct is_signed : true_type + struct is_signed : false_type { }; #endif #if ETL_HAS_NATIVE_CHAR16_T template <> - struct is_signed : true_type + struct is_signed : false_type { }; #endif #if ETL_HAS_NATIVE_CHAR32_T template <> - struct is_signed : true_type + struct is_signed : false_type { }; #endif @@ -569,6 +569,24 @@ namespace etl struct is_unsigned : true_type { }; + #if ETL_HAS_NATIVE_CHAR8_T + template <> + struct is_unsigned : true_type + { + }; + #endif + #if ETL_HAS_NATIVE_CHAR16_T + template <> + struct is_unsigned : true_type + { + }; + #endif + #if ETL_HAS_NATIVE_CHAR32_T + template <> + struct is_unsigned : true_type + { + }; + #endif template struct is_unsigned : is_unsigned { diff --git a/include/etl/utility.h b/include/etl/utility.h index 5a77b5b5..4c7e4719 100644 --- a/include/etl/utility.h +++ b/include/etl/utility.h @@ -377,7 +377,7 @@ namespace etl inline bool operator==(const pair& a, const pair& b) { #include "private/diagnostic_float_equal_push.h" - return (a.first == b.first) && !(a.second < b.second) && !(a.second > b.second); + return (a.first == b.first) && (a.second == b.second); #include "private/diagnostic_pop.h" } diff --git a/test/test_binary.cpp b/test/test_binary.cpp index d2c772e6..acbdd7fa 100644 --- a/test/test_binary.cpp +++ b/test/test_binary.cpp @@ -3040,6 +3040,108 @@ namespace CHECK_ARRAY_EQUAL(expected.data(), output.data(), expected.size()); } + + //************************************************************************* + TEST(test_count_leading_zeros_signed_32) + { + // int32_t(-1) = 0xFFFFFFFF → 0 leading zeros + CHECK_EQUAL(0, int(etl::count_leading_zeros(int32_t(-1)))); + + // int32_t(1) = 0x00000001 → 31 leading zeros + CHECK_EQUAL(31, int(etl::count_leading_zeros(int32_t(1)))); + + // int32_t(0) = 0x00000000 → 32 leading zeros + CHECK_EQUAL(32, int(etl::count_leading_zeros(int32_t(0)))); + + // int32_t(256) = 0x00000100 → 23 leading zeros + CHECK_EQUAL(23, int(etl::count_leading_zeros(int32_t(256)))); + + // Verify against unsigned version + for (int i = 0; i < 32; ++i) + { + int32_t value = int32_t(1) << i; + CHECK_EQUAL(int(etl::count_leading_zeros(uint32_t(value))), int(etl::count_leading_zeros(value))); + } + } + + //************************************************************************* + TEST(test_count_leading_zeros_signed_64) + { + CHECK_EQUAL(0, int(etl::count_leading_zeros(int64_t(-1)))); + CHECK_EQUAL(63, int(etl::count_leading_zeros(int64_t(1)))); + CHECK_EQUAL(64, int(etl::count_leading_zeros(int64_t(0)))); + + for (int i = 0; i < 64; ++i) + { + int64_t value = int64_t(1) << i; + CHECK_EQUAL(int(etl::count_leading_zeros(uint64_t(value))), int(etl::count_leading_zeros(value))); + } + } + + //************************************************************************* + TEST(test_count_leading_zeros_64_specific_values) + { + // Value that specifically triggers the upper-32-bit mask path + // Bit 35 set: 0x0000000800000000 → 28 leading zeros + CHECK_EQUAL(28, int(etl::count_leading_zeros(uint64_t(0x0000000800000000ULL)))); + // Bit 31 set: 0x0000000080000000 → 32 leading zeros + CHECK_EQUAL(32, int(etl::count_leading_zeros(uint64_t(0x0000000080000000ULL)))); + // All zeros in upper 32: 0x00000000FFFFFFFF → 32 leading zeros + CHECK_EQUAL(32, int(etl::count_leading_zeros(uint64_t(0x00000000FFFFFFFFULL)))); + // Upper half has bit: 0x0000000100000000 → 31 leading zeros + CHECK_EQUAL(31, int(etl::count_leading_zeros(uint64_t(0x0000000100000000ULL)))); + } + + //************************************************************************* + TEST(test_count_leading_ones_32_specific_values) + { + // 0xFFFF0000 → upper 16 bits set → exactly 16 leading ones + CHECK_EQUAL(16, int(etl::count_leading_ones(uint32_t(0xFFFF0000UL)))); + // 0xFFFF8000 → 17 leading ones + CHECK_EQUAL(17, int(etl::count_leading_ones(uint32_t(0xFFFF8000UL)))); + // 0xFFFFF000 → 20 leading ones + CHECK_EQUAL(20, int(etl::count_leading_ones(uint32_t(0xFFFFF000UL)))); + // 0xFFFFFF00 → 24 leading ones + CHECK_EQUAL(24, int(etl::count_leading_ones(uint32_t(0xFFFFFF00UL)))); + // 0xFFFFFFFE → 31 leading ones + CHECK_EQUAL(31, int(etl::count_leading_ones(uint32_t(0xFFFFFFFEUL)))); + // 0xFFFFFFFF → 32 leading ones + CHECK_EQUAL(32, int(etl::count_leading_ones(uint32_t(0xFFFFFFFFUL)))); + // 0x80000000 → 1 leading one + CHECK_EQUAL(1, int(etl::count_leading_ones(uint32_t(0x80000000UL)))); + + // Verify against reference for boundary values + for (uint32_t i = 0; i < 33; ++i) + { + // Create value with exactly 'i' leading ones + uint32_t value = (i == 0) ? 0U : (i == 32) ? 0xFFFFFFFFUL : ~((1UL << (32U - i)) - 1UL); + CHECK_EQUAL(int(test_leading_ones(value)), int(etl::count_leading_ones(value))); + } + } + + //************************************************************************* + TEST(test_count_leading_ones_64_specific_values) + { + // 0xFFFFFFFF00000000 → 32 leading ones + CHECK_EQUAL(32, int(etl::count_leading_ones(uint64_t(0xFFFFFFFF00000000ULL)))); + // 0xFFFFFFFFFFFF0000 → 48 leading ones + CHECK_EQUAL(48, int(etl::count_leading_ones(uint64_t(0xFFFFFFFFFFFF0000ULL)))); + // 0xFFFF000000000000 → 16 leading ones + CHECK_EQUAL(16, int(etl::count_leading_ones(uint64_t(0xFFFF000000000000ULL)))); + // 0xFFFFFFFFFFFFFF00 → 56 leading ones + CHECK_EQUAL(56, int(etl::count_leading_ones(uint64_t(0xFFFFFFFFFFFFFF00ULL)))); + // 0xFFFFFFFFFFFFFFFE → 63 leading ones + CHECK_EQUAL(63, int(etl::count_leading_ones(uint64_t(0xFFFFFFFFFFFFFFFEULL)))); + // 0xFFFFFFFFFFFFFFFF → 64 leading ones + CHECK_EQUAL(64, int(etl::count_leading_ones(uint64_t(0xFFFFFFFFFFFFFFFFULL)))); + + // Verify against reference for boundary values + for (uint64_t i = 0; i < 65; ++i) + { + uint64_t value = (i == 0) ? 0ULL : (i == 64) ? 0xFFFFFFFFFFFFFFFFULL : ~((1ULL << (64ULL - i)) - 1ULL); + CHECK_EQUAL(int(test_leading_ones(value)), int(etl::count_leading_ones(value))); + } + } } } // namespace diff --git a/test/test_circular_buffer.cpp b/test/test_circular_buffer.cpp index a9897e43..34452537 100644 --- a/test/test_circular_buffer.cpp +++ b/test/test_circular_buffer.cpp @@ -1072,5 +1072,30 @@ namespace CHECK(!is_equal); } + + //************************************************************************* + TEST(test_move_assignment_actually_moves) + { + DataM data; + data.push(ItemM("A")); + data.push(ItemM("B")); + data.push(ItemM("C")); + + DataM data2; + data2 = std::move(data); + + CHECK_EQUAL(3U, data2.size()); + + // If the move was correct, data2 should hold the moved-to values + CHECK_EQUAL("A", data2[0].value); + CHECK_EQUAL("B", data2[1].value); + CHECK_EQUAL("C", data2[2].value); + + // Original should be moved-from (empty strings with move-only type) + for (size_t i = 0; i < data.size(); ++i) + { + CHECK(data[i].value.empty()); + } + } } } // namespace diff --git a/test/test_deque.cpp b/test/test_deque.cpp index 22abd04a..d26096d1 100644 --- a/test/test_deque.cpp +++ b/test/test_deque.cpp @@ -2272,6 +2272,29 @@ namespace CHECK(std::equal(blank_data.begin(), blank_data.end(), data.begin())); } + + //************************************************************************* + TEST(test_const_iterator_subscript) + { + DataNDC data(initial_data.begin(), initial_data.end()); + + const DataNDC& cdata = data; + + // Access via const_iterator operator[] + DataNDC::const_iterator cit = cdata.begin(); + + // Verify operator[] returns values matching sequential access + for (size_t i = 0; i < cdata.size(); ++i) + { + CHECK(cit[i] == cdata[i]); + } + + // Verify const_iterator operator[] returns const_reference + // (This is a compile-time check - if the fix is reverted, + // the type would be non-const reference which is incorrect for const_iterator) + const NDC& ref = cit[0]; + CHECK(ref == cdata[0]); + } } } // namespace diff --git a/test/test_expected.cpp b/test/test_expected.cpp index a28e2f54..621727b2 100644 --- a/test/test_expected.cpp +++ b/test/test_expected.cpp @@ -1616,5 +1616,26 @@ namespace Error result = exp.error_or(Error("default")); CHECK_EQUAL("real_error", result.e); } + + //************************************************************************* + TEST(test_unexpected_error_const_rvalue_ref) + { + const etl::unexpected ue(Error("test_error")); + + // Move from const rvalue — should get const Error&& + const Error&& ref = std::move(ue).error(); + CHECK_EQUAL("test_error", ref.e); + } + + //************************************************************************* + TEST(test_transform_error_void_const_rvalue) + { + const etl::expected exp(etl::unexpected(Error("original"))); + + auto result = std::move(exp).transform_error([](const Error& e) { return Error(e.e + "_transformed"); }); + + CHECK_FALSE(result.has_value()); + CHECK_EQUAL("original_transformed", result.error().e); + } } } // namespace diff --git a/test/test_forward_list.cpp b/test/test_forward_list.cpp index 02d928de..3f805b7f 100644 --- a/test/test_forward_list.cpp +++ b/test/test_forward_list.cpp @@ -1444,6 +1444,28 @@ namespace CHECK_EQUAL(ItemNDC("F"), *itr++); } #endif + + //************************************************************************* + TEST(test_forward_list_exception_types) + { + // Verify exception class hierarchy is correct + CHECK(true == (std::is_base_of::value)); + CHECK(true == (std::is_base_of::value)); + CHECK(true == (std::is_base_of::value)); + CHECK(true == (std::is_base_of::value)); + +#if defined(ETL_VERBOSE_ERRORS) + // When verbose errors are enabled, check the error text contains "forward_list" + etl::forward_list_full ex_full(__FILE__, __LINE__); + etl::forward_list_empty ex_empty(__FILE__, __LINE__); + + std::string full_msg(ex_full.what()); + std::string empty_msg(ex_empty.what()); + + CHECK(full_msg.find("forward_list") != std::string::npos); + CHECK(empty_msg.find("forward_list") != std::string::npos); +#endif + } } #include "etl/private/diagnostic_pop.h" } // namespace diff --git a/test/test_histogram.cpp b/test/test_histogram.cpp index a68970f9..624c0e68 100644 --- a/test/test_histogram.cpp +++ b/test/test_histogram.cpp @@ -332,5 +332,59 @@ namespace isEqual = std::equal(output2.begin(), output2.end(), histogram.begin()); CHECK(isEqual); } + + //************************************************************************* + TEST(test_sparse_histogram_iteration) + { + StringHistogram histogram; + + // Empty histogram: begin == end + CHECK(histogram.begin() == histogram.end()); + CHECK(histogram.cbegin() == histogram.cend()); + + // Add items + histogram.add(std::string("apple")); + histogram.add(std::string("banana")); + histogram.add(std::string("apple")); + + // Non-empty: begin != end + CHECK(histogram.begin() != histogram.end()); + CHECK(histogram.cbegin() != histogram.cend()); + + // Count elements by iterating + size_t count = 0; + for (auto it = histogram.begin(); it != histogram.end(); ++it) + { + ++count; + } + CHECK_EQUAL(2U, count); // "apple" and "banana" + + // Same with cbegin/cend + count = 0; + for (auto it = histogram.cbegin(); it != histogram.cend(); ++it) + { + ++count; + } + CHECK_EQUAL(2U, count); + + // Verify we can find the expected values + bool found_apple = false; + bool found_banana = false; + for (auto it = histogram.begin(); it != histogram.end(); ++it) + { + if (it->first == "apple") + { + CHECK_EQUAL(2, it->second); + found_apple = true; + } + if (it->first == "banana") + { + CHECK_EQUAL(1, it->second); + found_banana = true; + } + } + CHECK(found_apple); + CHECK(found_banana); + } } } // namespace diff --git a/test/test_intrusive_links.cpp b/test/test_intrusive_links.cpp index c90a56c3..7bdbeb1b 100644 --- a/test/test_intrusive_links.cpp +++ b/test/test_intrusive_links.cpp @@ -1845,5 +1845,15 @@ namespace CHECK(c.etl_left == nullptr); CHECK(c.etl_right == nullptr); } + + //************************************************************************* + TEST(test_link_clear_range_bidirectional_nullptr) + { + // Passing nullptr should be a no-op, not a crash + BData* null_ptr = nullptr; + etl::link_clear_range(null_ptr); + // If we get here without crashing, the test passes + CHECK(true); + } } } // namespace diff --git a/test/test_string_char.cpp b/test/test_string_char.cpp index 63b84400..b8271af4 100644 --- a/test/test_string_char.cpp +++ b/test/test_string_char.cpp @@ -5475,5 +5475,32 @@ namespace CHECK(text1 == sstream_view); } #endif + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_char_pointer_n_shorter_than_strlen) + { + const value_t haystack_str[] = STR("Hello World"); + + TextSTD compare(haystack_str); + TextL text(haystack_str); + + // Haystack is "Hello World" (size 11). + // search_str is "Worldly" (strlen 7). + // We search for first 5 chars ("World") starting at pos=6. + // Old code: (6 + 7 = 13) > 11 → premature npos (BUG) + // Fixed: (6 + 5 = 11) <= 11 → proceeds to search → finds at 6 + const value_t search_str[] = STR("Worldly"); + + size_t pos_std = compare.find(search_str, 6, 5); + size_t pos_etl = text.find(search_str, 6, 5); + CHECK_EQUAL(6U, pos_std); + CHECK_EQUAL(pos_std, pos_etl); + + // pos=5 also triggers: (5 + 7 = 12) > 11 with old code + pos_std = compare.find(search_str, 5, 5); + pos_etl = text.find(search_str, 5, 5); + CHECK_EQUAL(6U, pos_std); + CHECK_EQUAL(pos_std, pos_etl); + } } } // namespace diff --git a/test/test_string_view.cpp b/test/test_string_view.cpp index 658a6fea..a9314dd2 100644 --- a/test/test_string_view.cpp +++ b/test/test_string_view.cpp @@ -877,7 +877,7 @@ namespace CHECK(2U == view.rfind(s2, 5)); CHECK(View::npos == view.rfind(s4)); - CHECK(1U == view.rfind(s3, 5, 2)); + CHECK(5U == view.rfind(s3, 5, 2)); CHECK(View::npos == view.rfind(s4, 0, 11)); } @@ -1183,5 +1183,40 @@ namespace CHECK_TRUE(u32view == u32sstream_view); } #endif + + //************************************************************************* + TEST(test_rfind_boundary_positions) + { + etl::string_view sv("abcabc"); + + // rfind "abc" with position=3: should find the second "abc" at position 3 + size_t pos = sv.rfind(etl::string_view("abc"), 3); + CHECK_EQUAL(3U, pos); + + // rfind "abc" with position=2: should find only the first "abc" at position 0 + pos = sv.rfind(etl::string_view("abc"), 2); + CHECK_EQUAL(0U, pos); + + // rfind "abc" with position=0: should find at position 0 + pos = sv.rfind(etl::string_view("abc"), 0); + CHECK_EQUAL(0U, pos); + + // rfind with npos (search entire string) + pos = sv.rfind(etl::string_view("abc")); + CHECK_EQUAL(3U, pos); + + // rfind something not found + pos = sv.rfind(etl::string_view("xyz")); + CHECK_EQUAL(etl::string_view::npos, pos); + + // Compare with std::string to verify exact behavior + std::string std_sv("abcabc"); + for (size_t p = 0; p <= std_sv.size(); ++p) + { + size_t std_pos = std_sv.rfind("abc", p); + size_t etl_pos = sv.rfind(etl::string_view("abc"), p); + CHECK_EQUAL(std_pos, etl_pos); + } + } } } // namespace diff --git a/test/test_type_traits.cpp b/test/test_type_traits.cpp index bb4d73f1..6756d6d9 100644 --- a/test/test_type_traits.cpp +++ b/test/test_type_traits.cpp @@ -2247,6 +2247,25 @@ namespace CHECK_FALSE((etl::is_object_v)); CHECK_FALSE((etl::is_object_v)); CHECK_FALSE((etl::is_object_v)); +#endif + } + + //************************************************************************* + TEST(test_is_signed_unsigned_char_types) + { +#if ETL_HAS_NATIVE_CHAR8_T + CHECK_FALSE(etl::is_signed::value); + CHECK_TRUE(etl::is_unsigned::value); +#endif + +#if ETL_HAS_NATIVE_CHAR16_T + CHECK_FALSE(etl::is_signed::value); + CHECK_TRUE(etl::is_unsigned::value); +#endif + +#if ETL_HAS_NATIVE_CHAR32_T + CHECK_FALSE(etl::is_signed::value); + CHECK_TRUE(etl::is_unsigned::value); #endif } } diff --git a/test/test_utility.cpp b/test/test_utility.cpp index 06d0d325..08cf2e75 100644 --- a/test/test_utility.cpp +++ b/test/test_utility.cpp @@ -1047,5 +1047,51 @@ namespace CHECK_EQUAL(expect1c, result1g); #endif } + + //************************************************************************* + TEST(test_pair_equality_uses_equality_operator) + { + // Basic equality + etl::pair p1(1, 2); + etl::pair p2(1, 2); + etl::pair p3(1, 3); + etl::pair p4(2, 2); + + CHECK_TRUE(p1 == p2); + CHECK_FALSE(p1 == p3); // different second + CHECK_FALSE(p1 == p4); // different first + + // Custom type where operator== and operator< can disagree + // The old code used !(ab), which is NOT equivalent to a==b + // for types that don't define a total order consistent with equality. + struct WeirdType + { + int value; + bool equal_flag; + + bool operator==(const WeirdType& other) const + { + return equal_flag && other.equal_flag; + } + bool operator<(const WeirdType& other) const + { + return value < other.value; + } + bool operator>(const WeirdType& other) const + { + return value > other.value; + } + }; + + WeirdType w1{1, false}; + WeirdType w2{1, false}; // same value, but equal_flag is false + + // With proper ==: w1 == w2 should be false (both equal_flags are false) + // With old !(w1w2): would be true (same value) + etl::pair pw1(0, w1); + etl::pair pw2(0, w2); + + CHECK_FALSE(pw1 == pw2); // This would FAIL with the old < > based comparison + } } } // namespace