Merge branch 'development' into feature/optimise-cyclic_value

This commit is contained in:
John Wellbelove 2026-09-30 10:13:06 +02:00 committed by GitHub
commit 28827c1b27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 2559 additions and 92 deletions

View File

@ -36,8 +36,12 @@ concurrency:
jobs:
build-windows-msvc-stl:
name: Windows - STL
name: Windows - STL - C++${{ matrix.cxx_standard }}
runs-on: [windows-2022]
strategy:
fail-fast: false
matrix:
cxx_standard: [14, 17, 20]
steps:
- name: Checkout
@ -50,7 +54,7 @@ jobs:
- name: Build
run: |
cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=17 ./
cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=${{ matrix.cxx_standard }} ./
MSBuild.exe -version
MSBuild.exe .\etl.sln
@ -58,8 +62,12 @@ jobs:
run: test/Debug/etl_tests.exe -v
build-windows-msvc-no-stl:
name: Windows - No STL
name: Windows - No STL - C++${{ matrix.cxx_standard }}
runs-on: [windows-2022]
strategy:
fail-fast: false
matrix:
cxx_standard: [14, 17, 20]
steps:
- name: Checkout
uses: actions/checkout@v6.0.3
@ -71,7 +79,7 @@ jobs:
- name: Build
run: |
cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=ON -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=17 ./
cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=ON -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=${{ matrix.cxx_standard }} ./
MSBuild.exe -version
MSBuild.exe .\etl.sln
@ -79,8 +87,12 @@ jobs:
run: test/Debug/etl_tests.exe -v
build-windows-msvc-stl-force-cpp03:
name: Windows - STL - Force C++03
name: Windows - STL - Force C++03 - C++${{ matrix.cxx_standard }}
runs-on: [windows-2022]
strategy:
fail-fast: false
matrix:
cxx_standard: [14, 17, 20]
steps:
- name: Checkout
@ -93,7 +105,7 @@ jobs:
- name: Build
run: |
cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=ON -DETL_CXX_STANDARD=17 ./
cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=ON -DETL_CXX_STANDARD=${{ matrix.cxx_standard }} ./
MSBuild.exe -version
MSBuild.exe .\etl.sln
@ -101,8 +113,12 @@ jobs:
run: test/Debug/etl_tests.exe -v
build-windows-msvc-no-stl-force-cpp03:
name: Windows - No STL - Force C++03
name: Windows - No STL - Force C++03 - C++${{ matrix.cxx_standard }}
runs-on: [windows-2022]
strategy:
fail-fast: false
matrix:
cxx_standard: [14, 17, 20]
steps:
- name: Checkout
@ -115,7 +131,7 @@ jobs:
- name: Build
run: |
cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=ON -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=ON -DETL_CXX_STANDARD=17 ./
cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=ON -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=ON -DETL_CXX_STANDARD=${{ matrix.cxx_standard }} ./
MSBuild.exe -version
MSBuild.exe .\etl.sln

View File

@ -40,7 +40,7 @@ type: hextra-home
<div>
<a href="https://etlcpp.github.io/etl/"><img src="https://github.com/ETLCPP/etl/actions/workflows/coverage.yml/badge.svg" alt="coverage"></a>
<a href="https://www.codacy.com/manual/jwellbelove/etl"><img src="https://api.codacy.com/project/badge/Grade/3c14cd918ccf40008d0bcd7b083d5946" alt="Codacy"></a>
<a href="https://app.codacy.com/gh/ETLCPP/etl/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade"><img src="https://app.codacy.com/project/badge/Grade/c0c29f7b84554166b577e8a98fc18d0b" alt="Codacy Badge"></a>
</div>
<div>

View File

@ -459,3 +459,317 @@ ETL_CONSTEXPR14 bool operator !=(T lhs, const unaligned_type& rhs)
**Description**
Inequality operator.
Removed: `20.39.3`
---
# Arbitrary width integers
{{< callout type="info">}}
From: `20.50.0`
{{< /callout >}}
`etl::unaligned_type` is templated on a fundamental type, so it can only store
integers whose width is that of a standard integer type. For protocols that use
odd widths, such as 24 or 48 bit integers, the following types are templated on
the **number of bytes of storage** instead.
```cpp
template <size_t Size_, int Endian_>
class unaligned_uint_type
template <size_t Size_, int Endian_>
class unaligned_int_type
```
Where `Size_` is the number of bytes of storage and `Endian_` is
`etl::endian::big` or `etl::endian::little`.
`unaligned_uint_type` stores an unsigned integer.
`unaligned_int_type` stores a signed, two's complement integer, which is sign
extended when read.
The value is interfaced to via `value_type`: the smallest standard integer type
that can hold `Size_` bytes. For example, `unaligned_uint_type<3U, ...>` has a
`value_type` of `uint32_t`.
Values that do not fit in `Size_` bytes are truncated, in the same way as a
narrowing `static_cast`.
Marked as `packed` for MSVC, GCC and Clang.
## Constants
`int Endian`
&emsp;The endianness of the type.
`size_t Size`
&emsp;The size, in bytes, of the type.
`value_type Min_Value`
&emsp;The lowest value that may be stored.
`value_type Max_Value`
&emsp;The highest value that may be stored.
## Predefined types
The 40, 48 and 56 bit types are only defined if `ETL_USING_64BIT_TYPES`.
The host order types are only defined if `ETL_HAS_CONSTEXPR_ENDIANNESS`.
### Little Endian
```cpp
le_uint24_t
le_int24_t
le_uint40_t ETL_USING_64BIT_TYPES
le_int40_t ETL_USING_64BIT_TYPES
le_uint48_t ETL_USING_64BIT_TYPES
le_int48_t ETL_USING_64BIT_TYPES
le_uint56_t ETL_USING_64BIT_TYPES
le_int56_t ETL_USING_64BIT_TYPES
```
### Big Endian
```cpp
be_uint24_t
be_int24_t
be_uint40_t ETL_USING_64BIT_TYPES
be_int40_t ETL_USING_64BIT_TYPES
be_uint48_t ETL_USING_64BIT_TYPES
be_int48_t ETL_USING_64BIT_TYPES
be_uint56_t ETL_USING_64BIT_TYPES
be_int56_t ETL_USING_64BIT_TYPES
```
### Network Order
Synonyms for Big Endian.
```cpp
using net_uint24_t = be_uint24_t
using net_int24_t = be_int24_t
using net_uint40_t = be_uint40_t
using net_int40_t = be_int40_t
using net_uint48_t = be_uint48_t
using net_int48_t = be_int48_t
using net_uint56_t = be_uint56_t
using net_int56_t = be_int56_t
```
### Host order
```cpp
host_uint24_t
host_int24_t
host_uint40_t
host_int40_t
host_uint48_t
host_int48_t
host_uint56_t
host_int56_t
```
## Constructors
```cpp
unaligned_uint_type()
unaligned_int_type()
```
**Description**
Constructs an uninitialised object.
---
```cpp
ETL_CONSTEXPR14 unaligned_uint_type(value_type value)
ETL_CONSTEXPR14 unaligned_int_type(value_type value)
```
**Description**
Constructs with the supplied value.
Values that do not fit in `Size` bytes are truncated.
---
```cpp
unaligned_uint_type(const void* address)
unaligned_int_type(const void* address)
```
**Description**
Constructs from the `Size` bytes at the address.
---
```cpp
unaligned_uint_type(const void* address, size_t buffer_size)
unaligned_int_type(const void* address, size_t buffer_size)
```
**Description**
Constructs from the `Size` bytes at the address.
`buffer_size` must be greater than or equal to `Size`. This is a precondition,
checked by `ETL_ASSERT`. If checks are disabled then a smaller buffer size
results in a read beyond the end of the buffer.
---
```cpp
ETL_CONSTEXPR14 unaligned_uint_type(const unsigned char* address)
ETL_CONSTEXPR14 unaligned_int_type(const unsigned char* address)
```
**Description**
Constructs from the `Size` bytes at the address.
Unlike the `const void*` overload, this requires no `reinterpret_cast` and so
may be used in a constexpr context.
---
```cpp
ETL_CONSTEXPR14 unaligned_uint_type(const unsigned char* address, size_t buffer_size)
ETL_CONSTEXPR14 unaligned_int_type(const unsigned char* address, size_t buffer_size)
```
**Description**
Constructs from the `Size` bytes at the address.
`buffer_size` must be greater than or equal to `Size`. This is a precondition,
checked by `ETL_ASSERT`.
---
```cpp
template <int Endian_Other>
unaligned_uint_type(const unaligned_uint_type<Size, Endian_Other>& other)
```
**Description**
Constructs from another type of the same size.
The endianness is converted, if necessary.
## Member functions
The storage interface (`data()`, `size()`, the iterators and `operator[]`) is
the same as that of `etl::unaligned_type`.
```cpp
ETL_CONSTEXPR14 unaligned_uint_type& operator =(value_type value)
```
**Description**
Assignment operator.
---
```cpp
ETL_CONSTEXPR14 operator value_type() const
```
**Description**
Conversion operator.
For `unaligned_int_type` the stored value is sign extended.
---
```cpp
ETL_CONSTEXPR14 value_type value() const
```
**Description**
Gets the value.
For `unaligned_int_type` the stored value is sign extended.
---
```cpp
static ETL_CONSTEXPR14 value_type value_from(const_pointer address)
```
**Description**
Gets the value of the `Size` bytes at the address.
Unlike construction from an address, the bytes are not copied to storage first.
---
```cpp
template <int Endian_Other>
ETL_CONSTEXPR14 unaligned_uint_type& operator =(const unaligned_uint_type<Size, Endian_Other>& other)
```
**Description**
Assignment operator from other endianness.
---
# Arbitrary width integers with an external buffer
{{< callout type="info">}}
From: `20.50.0`
{{< /callout >}}
```cpp
template <size_t Size_, int Endian_>
class unaligned_uint_type_ext
template <size_t Size_, int Endian_>
class unaligned_int_type_ext
```
These are the equivalents of `unaligned_uint_type` and `unaligned_int_type`
that do not own their storage, but reference an externally supplied buffer.
They have the same `value_type`, constants and value interface.
The default constructor is deleted: the storage must always be supplied.
### Predefined types
As for the internal storage versions, but with an `_ext_t` suffix.
```cpp
le_uint24_ext_t be_uint24_ext_t net_uint24_ext_t host_uint24_ext_t
le_int24_ext_t be_int24_ext_t net_int24_ext_t host_int24_ext_t
le_uint40_ext_t be_uint40_ext_t net_uint40_ext_t host_uint40_ext_t
le_int40_ext_t be_int40_ext_t net_int40_ext_t host_int40_ext_t
le_uint48_ext_t be_uint48_ext_t net_uint48_ext_t host_uint48_ext_t
le_int48_ext_t be_int48_ext_t net_int48_ext_t host_int48_ext_t
le_uint56_ext_t be_uint56_ext_t net_uint56_ext_t host_uint56_ext_t
le_int56_ext_t be_int56_ext_t net_int56_ext_t host_int56_ext_t
```
## Constructors
```cpp
unaligned_uint_type_ext() ETL_DELETE
```
**Description**
Deleted. The storage must always be supplied.
---
```cpp
unaligned_uint_type_ext(pointer storage)
```
**Description**
Constructs, referencing the supplied storage.
The storage is not modified.
---
```cpp
unaligned_uint_type_ext(value_type value, pointer storage)
```
**Description**
Constructs, referencing the supplied storage, and writes the value to it.
---
```cpp
template <int Endian_Other>
unaligned_uint_type_ext(const unaligned_uint_type_ext<Size, Endian_Other>& other, pointer storage)
```
**Description**
Constructs, referencing the supplied storage, and copies the value of `other`
to it, converting the endianness if necessary.
**Note**
The storage of `other` must either be the same storage as that supplied, or
completely separate. Partially overlapping storage is undefined behaviour.
## Member functions
```cpp
void set_storage(pointer storage)
```
**Description**
Sets the storage for the type.
The storage is not modified.
---
```cpp
template <int Endian_Other>
unaligned_uint_type_ext& operator =(const unaligned_uint_type_ext<Size, Endian_Other>& other)
```
**Description**
Assignment operator from other endianness.
**Note**
The storage of `other` must either be the same storage as this object's, or
completely separate. Partially overlapping storage is undefined behaviour.

View File

@ -411,6 +411,16 @@ namespace etl
return false;
}
//*******************************************
/// Returns true if an attempt was ever made to insert a timer into the
/// active list when it was already there. Such an insert is rejected, as
/// it would create a circular reference in the list.
//*******************************************
bool duplicate_insert_detected() const
{
return active_list.duplicate_insert_detected();
}
//*******************************************
/// Check if there is an active timer.
//*******************************************
@ -630,6 +640,7 @@ namespace etl
: head(etl::timer::id::NO_TIMER)
, tail(etl::timer::id::NO_TIMER)
, ptimers(ptimers_)
, duplicate_insert(false)
{
}
@ -639,6 +650,15 @@ namespace etl
return head == etl::timer::id::NO_TIMER;
}
//*******************************
/// Returns true if an attempt was ever made to insert a timer that was
/// already in the list.
//*******************************
bool duplicate_insert_detected() const
{
return duplicate_insert;
}
//*******************************
// Inserts the timer at the correct delta position
//*******************************
@ -646,6 +666,16 @@ namespace etl
{
timer_data& timer = ptimers[id_];
// A timer is in this list exactly when it is the head or has a previous
// link, as remove() clears both links. Inserting one that is already
// here would link it to itself and create a circular reference, so
// record the attempt and abandon the insert to keep the list intact.
if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER))
{
duplicate_insert = true;
return;
}
if (head == etl::timer::id::NO_TIMER)
{
// No entries yet.
@ -788,6 +818,10 @@ namespace etl
etl::timer::id::type tail;
timer_data* const ptimers;
// Set if an attempt was ever made to insert a timer that was already
// in the list.
bool duplicate_insert;
};
// The array of timer data structures.

View File

@ -174,12 +174,9 @@ namespace etl
active_list.remove(timer.id, true);
remove_callback.call_if(timer.id);
if (timer.callback.is_valid())
{
// Call the delegate callback.
timer.callback();
}
// The timer is reinserted before the callback is called, so that
// a callback that restarts the timer replaces this entry instead
// of the timer being inserted into the active list twice.
if (timer.repeating)
{
// Reinsert the timer.
@ -188,6 +185,11 @@ namespace etl
insert_callback.call_if(timer.id);
}
if (timer.callback.is_valid())
{
timer.callback();
}
has_active = !active_list.empty();
}
@ -301,6 +303,16 @@ namespace etl
return false;
}
//*******************************************
/// Returns true if an attempt was ever made to insert a timer into the
/// active list when it was already there. Such an insert is rejected, as
/// it would create a circular reference in the list.
//*******************************************
bool duplicate_insert_detected() const
{
return active_list.duplicate_insert_detected();
}
//*******************************************
/// Check if there is an active timer.
//*******************************************
@ -486,6 +498,7 @@ namespace etl
: head(etl::timer::id::NO_TIMER)
, tail(etl::timer::id::NO_TIMER)
, ptimers(ptimers_)
, duplicate_insert(false)
{
}
@ -495,6 +508,15 @@ namespace etl
return head == etl::timer::id::NO_TIMER;
}
//*******************************
/// Returns true if an attempt was ever made to insert a timer that was
/// already in the list.
//*******************************
bool duplicate_insert_detected() const
{
return duplicate_insert;
}
//*******************************
// Inserts the timer at the correct delta position
//*******************************
@ -502,6 +524,16 @@ namespace etl
{
timer_data& timer = ptimers[id_];
// A timer is in this list exactly when it is the head or has a previous
// link, as remove() clears both links. Inserting one that is already
// here would link it to itself and create a circular reference, so
// record the attempt and abandon the insert to keep the list intact.
if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER))
{
duplicate_insert = true;
return;
}
if (head == etl::timer::id::NO_TIMER)
{
// No entries yet.
@ -644,6 +676,10 @@ namespace etl
etl::timer::id::type tail;
timer_data* const ptimers;
// Set if an attempt was ever made to insert a timer that was already
// in the list.
bool duplicate_insert;
};
// The array of timer data structures.

View File

@ -178,11 +178,9 @@ namespace etl
active_list.remove(timer.id, true);
remove_callback.call_if(timer.id);
if (timer.callback.is_valid())
{
timer.callback();
}
// The timer is reinserted before the callback is called, so that
// a callback that restarts the timer replaces this entry instead
// of the timer being inserted into the active list twice.
if (timer.repeating)
{
// Reinsert the timer.
@ -191,6 +189,11 @@ namespace etl
insert_callback.call_if(timer.id);
}
if (timer.callback.is_valid())
{
timer.callback();
}
has_active = !active_list.empty();
}
@ -305,6 +308,16 @@ namespace etl
return false;
}
//*******************************************
/// Returns true if an attempt was ever made to insert a timer into the
/// active list when it was already there. Such an insert is rejected, as
/// it would create a circular reference in the list.
//*******************************************
bool duplicate_insert_detected() const
{
return active_list.duplicate_insert_detected();
}
//*******************************************
/// Check if there is an active timer.
//*******************************************
@ -487,6 +500,7 @@ namespace etl
: head(etl::timer::id::NO_TIMER)
, tail(etl::timer::id::NO_TIMER)
, ptimers(ptimers_)
, duplicate_insert(false)
{
}
@ -496,6 +510,15 @@ namespace etl
return head == etl::timer::id::NO_TIMER;
}
//*******************************
/// Returns true if an attempt was ever made to insert a timer that was
/// already in the list.
//*******************************
bool duplicate_insert_detected() const
{
return duplicate_insert;
}
//*******************************
// Inserts the timer at the correct delta position
//*******************************
@ -503,6 +526,16 @@ namespace etl
{
timer_data& timer = ptimers[id_];
// A timer is in this list exactly when it is the head or has a previous
// link, as remove() clears both links. Inserting one that is already
// here would link it to itself and create a circular reference, so
// record the attempt and abandon the insert to keep the list intact.
if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER))
{
duplicate_insert = true;
return;
}
if (head == etl::timer::id::NO_TIMER)
{
// No entries yet.
@ -645,6 +678,10 @@ namespace etl
etl::timer::id::type tail;
timer_data* const ptimers;
// Set if an attempt was ever made to insert a timer that was already
// in the list.
bool duplicate_insert;
};
// The array of timer data structures.

View File

@ -264,6 +264,16 @@ namespace etl
unlock = unlock_;
}
//*******************************************
/// Returns true if an attempt was ever made to insert a timer into the
/// active list when it was already there. Such an insert is rejected, as
/// it would create a circular reference in the list.
//*******************************************
bool duplicate_insert_detected() const
{
return active_list.duplicate_insert_detected();
}
//*******************************************
/// Check if there is an active timer.
//*******************************************
@ -460,6 +470,7 @@ namespace etl
: head(etl::timer::id::NO_TIMER)
, tail(etl::timer::id::NO_TIMER)
, ptimers(ptimers_)
, duplicate_insert(false)
{
}
@ -469,6 +480,15 @@ namespace etl
return head == etl::timer::id::NO_TIMER;
}
//*******************************
/// Returns true if an attempt was ever made to insert a timer that was
/// already in the list.
//*******************************
bool duplicate_insert_detected() const
{
return duplicate_insert;
}
//*******************************
// Inserts the timer at the correct delta position
//*******************************
@ -476,6 +496,16 @@ namespace etl
{
timer_data& timer = ptimers[id_];
// A timer is in this list exactly when it is the head or has a previous
// link, as remove() clears both links. Inserting one that is already
// here would link it to itself and create a circular reference, so
// record the attempt and abandon the insert to keep the list intact.
if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER))
{
duplicate_insert = true;
return;
}
if (head == etl::timer::id::NO_TIMER)
{
// No entries yet.
@ -618,6 +648,10 @@ namespace etl
etl::timer::id::type tail;
timer_data* const ptimers;
// Set if an attempt was ever made to insert a timer that was already
// in the list.
bool duplicate_insert;
};
//*******************************************
@ -716,11 +750,9 @@ namespace etl
active_list.remove(timer.id, true);
remove_callback.call_if(timer.id);
if (timer.callback.is_valid())
{
timer.callback();
}
// The timer is reinserted before the callback is called, so that
// a callback that restarts the timer replaces this entry instead
// of the timer being inserted into the active list twice.
if (timer.repeating)
{
// Reinsert the timer.
@ -729,6 +761,11 @@ namespace etl
insert_callback.call_if(timer.id);
}
if (timer.callback.is_valid())
{
timer.callback();
}
has_active = !active_list.empty();
}

View File

@ -509,7 +509,8 @@ namespace etl
struct basic_format_string
{
inline ETL_CONSTEVAL basic_format_string(const char* fmt)
: _sv(fmt)
: _str(fmt)
, _length(etl::char_traits<char>::length(fmt))
{
#if ETL_USING_CPP20
// Compile-time validation: check_format runs at compile time via consteval.
@ -527,12 +528,22 @@ namespace etl
ETL_CONSTEXPR string_view get() const
{
return _sv;
return string_view(_str, _length);
}
private:
string_view _sv;
// The format string is deliberately stored as a pointer plus a length, and
// not as a string_view.
//
// A string_view holds a pair of pointers, so constructing one here would
// initialise a member with the pointer arithmetic 'fmt + length(fmt)'.
// Microsoft's compiler (19.44) miscompiles exactly that case in a consteval
// constructor in debug builds: the resulting end pointer, and therefore the
// size of the view, is wrong. Storing the length as an integer instead
// computes correctly, and the view is formed on demand in get().
const char* _str;
size_t _length;
};
template <class... Args>

View File

@ -158,6 +158,7 @@ namespace etl
: head(etl::timer::id::NO_TIMER)
, tail(etl::timer::id::NO_TIMER)
, ptimers(ptimers_)
, duplicate_insert(false)
{
}
@ -167,6 +168,15 @@ namespace etl
return head == etl::timer::id::NO_TIMER;
}
//*******************************
/// Returns true if an attempt was ever made to insert a timer that was
/// already in the list.
//*******************************
bool duplicate_insert_detected() const
{
return duplicate_insert;
}
//*******************************
// Inserts the timer at the correct delta position
//*******************************
@ -174,6 +184,16 @@ namespace etl
{
etl::message_timer_data& timer = ptimers[id_];
// A timer is in this list exactly when it is the head or has a previous
// link, as remove() clears both links. Inserting one that is already
// here would link it to itself and create a circular reference, so
// record the attempt and abandon the insert to keep the list intact.
if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER))
{
duplicate_insert = true;
return;
}
if (head == etl::timer::id::NO_TIMER)
{
// No entries yet.
@ -316,6 +336,10 @@ namespace etl
etl::timer::id::type tail;
etl::message_timer_data* const ptimers;
// Set if an attempt was ever made to insert a timer that was already
// in the list.
bool duplicate_insert;
};
} // namespace private_message_timer
@ -579,6 +603,16 @@ namespace etl
return false;
}
//*******************************************
/// Returns true if an attempt was ever made to insert a timer into the
/// active list when it was already there. Such an insert is rejected, as
/// it would create a circular reference in the list.
//*******************************************
bool duplicate_insert_detected() const
{
return active_list.duplicate_insert_detected();
}
//*******************************************
/// Check if there is an active timer.
//*******************************************

View File

@ -183,11 +183,9 @@ namespace etl
active_list.remove(timer.id, true);
remove_callback.call_if(timer.id);
if (timer.p_router != ETL_NULLPTR)
{
timer.p_router->receive(timer.destination_router_id, *(timer.p_message));
}
// The timer is reinserted before the message is sent, so that
// a receiver that restarts the timer replaces this entry instead
// of the timer being inserted into the active list twice.
if (timer.repeating)
{
timer.delta = timer.period;
@ -195,6 +193,11 @@ namespace etl
insert_callback.call_if(timer.id);
}
if (timer.p_router != ETL_NULLPTR)
{
timer.p_router->receive(timer.destination_router_id, *(timer.p_message));
}
has_active = !active_list.empty();
}
@ -308,6 +311,16 @@ namespace etl
return false;
}
//*******************************************
/// Returns true if an attempt was ever made to insert a timer into the
/// active list when it was already there. Such an insert is rejected, as
/// it would create a circular reference in the list.
//*******************************************
bool duplicate_insert_detected() const
{
return active_list.duplicate_insert_detected();
}
//*******************************************
/// Check if there is an active timer.
//*******************************************
@ -467,6 +480,7 @@ namespace etl
: head(etl::timer::id::NO_TIMER)
, tail(etl::timer::id::NO_TIMER)
, ptimers(ptimers_)
, duplicate_insert(false)
{
}
@ -476,6 +490,15 @@ namespace etl
return head == etl::timer::id::NO_TIMER;
}
//*******************************
/// Returns true if an attempt was ever made to insert a timer that was
/// already in the list.
//*******************************
bool duplicate_insert_detected() const
{
return duplicate_insert;
}
//*******************************
// Inserts the timer at the correct delta position
//*******************************
@ -483,6 +506,16 @@ namespace etl
{
timer_data& timer = ptimers[id_];
// A timer is in this list exactly when it is the head or has a previous
// link, as remove() clears both links. Inserting one that is already
// here would link it to itself and create a circular reference, so
// record the attempt and abandon the insert to keep the list intact.
if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER))
{
duplicate_insert = true;
return;
}
if (head == etl::timer::id::NO_TIMER)
{
// No entries yet.
@ -625,6 +658,10 @@ namespace etl
etl::timer::id::type tail;
timer_data* const ptimers;
// Set if an attempt was ever made to insert a timer that was already
// in the list.
bool duplicate_insert;
};
// The array of timer data structures.

View File

@ -189,11 +189,9 @@ namespace etl
active_list.remove(timer.id, true);
remove_callback.call_if(timer.id);
if (timer.p_router != ETL_NULLPTR)
{
timer.p_router->receive(timer.destination_router_id, *(timer.p_message));
}
// The timer is reinserted before the message is sent, so that
// a receiver that restarts the timer replaces this entry instead
// of the timer being inserted into the active list twice.
if (timer.repeating)
{
// Reinsert the timer.
@ -202,6 +200,11 @@ namespace etl
insert_callback.call_if(timer.id);
}
if (timer.p_router != ETL_NULLPTR)
{
timer.p_router->receive(timer.destination_router_id, *(timer.p_message));
}
has_active = !active_list.empty();
}
@ -316,6 +319,16 @@ namespace etl
return false;
}
//*******************************************
/// Returns true if an attempt was ever made to insert a timer into the
/// active list when it was already there. Such an insert is rejected, as
/// it would create a circular reference in the list.
//*******************************************
bool duplicate_insert_detected() const
{
return active_list.duplicate_insert_detected();
}
//*******************************************
/// Check if there is an active timer.
//*******************************************
@ -473,6 +486,7 @@ namespace etl
: head(etl::timer::id::NO_TIMER)
, tail(etl::timer::id::NO_TIMER)
, ptimers(ptimers_)
, duplicate_insert(false)
{
}
@ -482,6 +496,15 @@ namespace etl
return head == etl::timer::id::NO_TIMER;
}
//*******************************
/// Returns true if an attempt was ever made to insert a timer that was
/// already in the list.
//*******************************
bool duplicate_insert_detected() const
{
return duplicate_insert;
}
//*******************************
// Inserts the timer at the correct delta position
//*******************************
@ -489,6 +512,16 @@ namespace etl
{
timer_data& timer = ptimers[id_];
// A timer is in this list exactly when it is the head or has a previous
// link, as remove() clears both links. Inserting one that is already
// here would link it to itself and create a circular reference, so
// record the attempt and abandon the insert to keep the list intact.
if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER))
{
duplicate_insert = true;
return;
}
if (head == etl::timer::id::NO_TIMER)
{
// No entries yet.
@ -631,6 +664,10 @@ namespace etl
etl::timer::id::type tail;
timer_data* const ptimers;
// Set if an attempt was ever made to insert a timer that was already
// in the list.
bool duplicate_insert;
};
// The array of timer data structures.

View File

@ -186,11 +186,9 @@ namespace etl
active_list.remove(timer.id, true);
remove_callback.call_if(timer.id);
if (timer.p_router != ETL_NULLPTR)
{
timer.p_router->receive(timer.destination_router_id, *(timer.p_message));
}
// The timer is reinserted before the message is sent, so that
// a receiver that restarts the timer replaces this entry instead
// of the timer being inserted into the active list twice.
if (timer.repeating)
{
timer.delta = timer.period;
@ -198,6 +196,11 @@ namespace etl
insert_callback.call_if(timer.id);
}
if (timer.p_router != ETL_NULLPTR)
{
timer.p_router->receive(timer.destination_router_id, *(timer.p_message));
}
has_active = !active_list.empty();
}
@ -323,6 +326,16 @@ namespace etl
unlock = unlock_;
}
//*******************************************
/// Returns true if an attempt was ever made to insert a timer into the
/// active list when it was already there. Such an insert is rejected, as
/// it would create a circular reference in the list.
//*******************************************
bool duplicate_insert_detected() const
{
return active_list.duplicate_insert_detected();
}
//*******************************************
/// Check if there is an active timer.
//*******************************************
@ -481,6 +494,7 @@ namespace etl
: head(etl::timer::id::NO_TIMER)
, tail(etl::timer::id::NO_TIMER)
, ptimers(ptimers_)
, duplicate_insert(false)
{
}
@ -490,6 +504,15 @@ namespace etl
return head == etl::timer::id::NO_TIMER;
}
//*******************************
/// Returns true if an attempt was ever made to insert a timer that was
/// already in the list.
//*******************************
bool duplicate_insert_detected() const
{
return duplicate_insert;
}
//*******************************
// Inserts the timer at the correct delta position
//*******************************
@ -497,6 +520,16 @@ namespace etl
{
timer_data& timer = ptimers[id_];
// A timer is in this list exactly when it is the head or has a previous
// link, as remove() clears both links. Inserting one that is already
// here would link it to itself and create a circular reference, so
// record the attempt and abandon the insert to keep the list intact.
if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER))
{
duplicate_insert = true;
return;
}
if (head == etl::timer::id::NO_TIMER)
{
// No entries yet.
@ -639,6 +672,10 @@ namespace etl
etl::timer::id::type tail;
timer_data* const ptimers;
// Set if an attempt was ever made to insert a timer that was already
// in the list.
bool duplicate_insert;
};
// The array of timer data structures.

View File

@ -4310,13 +4310,53 @@ namespace etl
#endif
#if ETL_USING_CPP11
//*********************************************
// Builtin-free noexcept-constructibility/assignability detection. Used when
// the __is_nothrow_constructible/__is_nothrow_assignable builtins are
// unavailable. Mirrors the void_t-based is_constructible/is_assignable
// fallbacks above so that (e.g.) a class with implicit noexcept move
// operations is correctly reported nothrow-(move-)constructible/assignable,
// and a const-qualified type is correctly reported NOT nothrow-assignable
// (the old is_arithmetic||is_pointer fallback ignored const-qualification).
#if !ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE
namespace private_type_traits
{
template <typename, typename T, typename... TArgs>
struct is_nothrow_constructible_ : etl::false_type
{
};
template <typename T, typename... TArgs>
struct is_nothrow_constructible_<etl::void_t<decltype(T(etl::declval<TArgs>()...))>, T, TArgs...>
: etl::bool_constant<noexcept(T(etl::declval<TArgs>()...))>
{
};
} // namespace private_type_traits
#endif
#if !ETL_USING_BUILTIN_IS_NOTHROW_ASSIGNABLE
namespace private_type_traits
{
template <typename, typename T, typename U>
struct is_nothrow_assignable_ : etl::false_type
{
};
template <typename T, typename U>
struct is_nothrow_assignable_<etl::void_t<decltype(etl::declval<T>() = etl::declval<U>())>, T, U>
: etl::bool_constant<noexcept(etl::declval<T>() = etl::declval<U>())>
{
};
} // namespace private_type_traits
#endif
//*********************************************
// is_nothrow_constructible
template <typename T, typename... TArgs>
#if ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE
struct is_nothrow_constructible : public etl::bool_constant<__is_nothrow_constructible(T, TArgs...)>
#else
struct is_nothrow_constructible : public etl::bool_constant<etl::is_arithmetic<T>::value || etl::is_pointer<T>::value>
struct is_nothrow_constructible : public private_type_traits::is_nothrow_constructible_<etl::void_t<>, T, TArgs...>
#endif
{
};
@ -4324,33 +4364,21 @@ namespace etl
//*********************************************
// is_nothrow_default_constructible
template <typename T>
#if ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE
struct is_nothrow_default_constructible : public etl::is_nothrow_constructible<T>
#else
struct is_nothrow_default_constructible : public etl::bool_constant<etl::is_arithmetic<T>::value || etl::is_pointer<T>::value>
#endif
{
};
//*********************************************
// is_nothrow_copy_constructible
template <typename T>
#if ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE
struct is_nothrow_copy_constructible : public etl::is_nothrow_constructible<T, typename etl::add_lvalue_reference<const T>::type>
#else
struct is_nothrow_copy_constructible : public etl::bool_constant<etl::is_arithmetic<T>::value || etl::is_pointer<T>::value>
#endif
{
};
//*********************************************
// is_nothrow_move_constructible
template <typename T>
#if ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE
struct is_nothrow_move_constructible : public etl::is_nothrow_constructible<T, typename etl::add_rvalue_reference<T>::type>
#else
struct is_nothrow_move_constructible : public etl::bool_constant<etl::is_arithmetic<T>::value || etl::is_pointer<T>::value>
#endif
{
};
@ -4360,7 +4388,7 @@ namespace etl
#if ETL_USING_BUILTIN_IS_NOTHROW_ASSIGNABLE
struct is_nothrow_assignable : public etl::bool_constant<__is_nothrow_assignable(T, U)>
#else
struct is_nothrow_assignable : public etl::bool_constant<etl::is_arithmetic<T>::value || etl::is_pointer<T>::value>
struct is_nothrow_assignable : public private_type_traits::is_nothrow_assignable_<etl::void_t<>, T, U>
#endif
{
};
@ -4368,24 +4396,16 @@ namespace etl
//*********************************************
// is_nothrow_copy_assignable
template <typename T>
#if ETL_USING_BUILTIN_IS_NOTHROW_ASSIGNABLE
struct is_nothrow_copy_assignable
: public etl::is_nothrow_assignable<typename etl::add_lvalue_reference<T>::type, typename etl::add_lvalue_reference<const T>::type>
#else
struct is_nothrow_copy_assignable : public etl::bool_constant<etl::is_arithmetic<T>::value || etl::is_pointer<T>::value>
#endif
{
};
//*********************************************
// is_nothrow_move_assignable
template <typename T>
#if ETL_USING_BUILTIN_IS_NOTHROW_ASSIGNABLE
struct is_nothrow_move_assignable
: public etl::is_nothrow_assignable<typename etl::add_lvalue_reference<T>::type, typename etl::add_rvalue_reference<T>::type>
#else
struct is_nothrow_move_assignable : public etl::bool_constant<etl::is_arithmetic<T>::value || etl::is_pointer<T>::value>
#endif
{
};
#endif
@ -5175,20 +5195,26 @@ namespace etl
#elif ETL_USING_CPP11
namespace private_type_traits
{
template <typename T>
struct is_swappable_helper;
namespace swap_detect
{
// Bring std::swap into scope, if it is available, so that the unqualified
// call below finds it in those configurations that do not define etl::swap.
// Otherwise etl::swap is found by ordinary lookup in the enclosing
// namespace. Either way, a swap found by argument dependent lookup takes
// part in overload resolution.
#if ETL_USING_STL || defined(ETL_IN_UNIT_TEST)
using std::swap;
#endif
// A dummy declaration that can never be selected, as it takes no
// arguments, but which makes the name visible to ordinary lookup so that
// the unqualified calls below are well formed.
// Ordinary lookup stops here, so only a swap found by argument dependent
// lookup takes part in overload resolution.
// The generic swap that the standard library (or etl) provides is not
// detected this way, because not every implementation constrains it
// (Microsoft's C++14 std::swap does not), which would make every type
// appear to be swappable. Its viability is determined by the
// is_default_swappable traits below instead.
void swap();
// Selected only if 'swap(declval<T&>(), declval<T&>())' is a valid expression.
template <typename T>
struct is_swappable_test
struct is_adl_swappable_test
{
private:
@ -5206,7 +5232,7 @@ namespace etl
// Selected only if both 'swap(declval<T>(), declval<U>())' and
// 'swap(declval<U>(), declval<T>())' are valid expressions.
template <typename T, typename U>
struct is_swappable_with_test
struct is_adl_swappable_with_test
{
private:
@ -5221,15 +5247,61 @@ namespace etl
static ETL_CONSTANT bool value = decltype(test<T, U>(0))::value;
};
// Determines whether an expression of type T can be bound to a non-const
// lvalue reference to its referenced type.
// This is not simply 'is_lvalue_reference', because some compilers offer
// an extension that binds a class rvalue to a non-const lvalue reference.
template <typename T>
void binds_to_lvalue_reference_test(T&);
template <typename T, typename = void>
struct binds_to_lvalue_reference : etl::false_type
{
};
template <typename T>
struct binds_to_lvalue_reference<
T, etl::void_t<decltype(binds_to_lvalue_reference_test<typename etl::remove_reference<T>::type>(etl::declval<T>()))>> : etl::true_type
{
};
} // namespace swap_detect
// Determines whether the generic swap(T&, T&) is viable for T.
template <typename T>
struct is_swappable_helper : etl::bool_constant<swap_detect::is_swappable_test<T>::value>
struct is_default_swappable : etl::bool_constant<etl::is_move_constructible<T>::value && etl::is_move_assignable<T>::value>
{
};
// The generic swap for arrays requires the element type to be swappable.
template <typename T, size_t Size>
struct is_default_swappable<T[Size]> : is_swappable_helper<T>
{
};
// An array of unknown bound cannot be swapped.
template <typename T>
struct is_default_swappable<T[]> : etl::false_type
{
};
// The generic swap only participates when both arguments are lvalues of
// the same type.
template <typename T, typename U>
struct is_default_swappable_with
: etl::bool_constant<swap_detect::binds_to_lvalue_reference<T>::value && etl::is_same<T, U>::value
&& is_default_swappable<typename etl::remove_reference<T>::type>::value>
{
};
template <typename T>
struct is_swappable_helper : etl::bool_constant<swap_detect::is_adl_swappable_test<T>::value || is_default_swappable<T>::value>
{
};
template <typename T, typename U>
struct is_swappable_with_helper : etl::bool_constant<swap_detect::is_swappable_with_test<T, U>::value>
struct is_swappable_with_helper
: etl::bool_constant<swap_detect::is_adl_swappable_with_test<T, U>::value || is_default_swappable_with<T, U>::value>
{
};
} // namespace private_type_traits
@ -5271,43 +5343,75 @@ namespace etl
#elif ETL_USING_CPP11
namespace private_type_traits
{
template <typename T>
struct is_nothrow_swappable_helper;
namespace swap_detect
{
// Only 'true' if the type is swappable and the swap expression is noexcept.
template <typename T, bool Is_Swappable = is_swappable_test<T>::value>
struct is_nothrow_swappable_test
// Only 'true' if the type has an ADL found swap and the swap expression is noexcept.
template <typename T, bool Is_Adl_Swappable = is_adl_swappable_test<T>::value>
struct is_nothrow_adl_swappable_test
{
static ETL_CONSTANT bool value = false;
};
template <typename T>
struct is_nothrow_swappable_test<T, true>
struct is_nothrow_adl_swappable_test<T, true>
{
static ETL_CONSTANT bool value = noexcept(swap(etl::declval<T&>(), etl::declval<T&>()));
};
// Only 'true' if the types are swappable with each other and both of the
// swap expressions are noexcept.
template <typename T, typename U, bool Is_Swappable_With = is_swappable_with_test<T, U>::value>
struct is_nothrow_swappable_with_test
// Only 'true' if the types have ADL found swaps for each other and both of
// the swap expressions are noexcept.
template <typename T, typename U, bool Is_Adl_Swappable_With = is_adl_swappable_with_test<T, U>::value>
struct is_nothrow_adl_swappable_with_test
{
static ETL_CONSTANT bool value = false;
};
template <typename T, typename U>
struct is_nothrow_swappable_with_test<T, U, true>
struct is_nothrow_adl_swappable_with_test<T, U, true>
{
static ETL_CONSTANT bool value = noexcept(swap(etl::declval<T>(), etl::declval<U>())) && noexcept(swap(etl::declval<U>(), etl::declval<T>()));
};
} // namespace swap_detect
// Determines whether the generic swap(T&, T&) is viable for T and cannot throw.
template <typename T>
struct is_nothrow_swappable_helper : etl::bool_constant<swap_detect::is_nothrow_swappable_test<T>::value>
struct is_nothrow_default_swappable
: etl::bool_constant<etl::is_nothrow_move_constructible<T>::value && etl::is_nothrow_move_assignable<T>::value>
{
};
template <typename T, size_t Size>
struct is_nothrow_default_swappable<T[Size]> : is_nothrow_swappable_helper<T>
{
};
template <typename T>
struct is_nothrow_default_swappable<T[]> : etl::false_type
{
};
template <typename T, typename U>
struct is_nothrow_swappable_with_helper : etl::bool_constant<swap_detect::is_nothrow_swappable_with_test<T, U>::value>
struct is_nothrow_default_swappable_with
: etl::bool_constant<swap_detect::binds_to_lvalue_reference<T>::value && etl::is_same<T, U>::value
&& is_nothrow_default_swappable<typename etl::remove_reference<T>::type>::value>
{
};
// An ADL found swap hides the generic one, so it decides the result.
template <typename T>
struct is_nothrow_swappable_helper
: etl::bool_constant<swap_detect::is_adl_swappable_test<T>::value ? swap_detect::is_nothrow_adl_swappable_test<T>::value
: is_nothrow_default_swappable<T>::value>
{
};
template <typename T, typename U>
struct is_nothrow_swappable_with_helper
: etl::bool_constant<swap_detect::is_adl_swappable_with_test<T, U>::value ? swap_detect::is_nothrow_adl_swappable_with_test<T, U>::value
: is_nothrow_default_swappable_with<T, U>::value>
{
};
} // namespace private_type_traits

File diff suppressed because it is too large Load Diff

View File

@ -149,6 +149,35 @@ namespace
callback_type free_function_callback2 = callback_type::create<free_callback2>();
//***************************************************************************
// Fixture for a repeating timer that restarts itself from within its own
// callback (regression test for issue #967).
//***************************************************************************
struct RestartInCallback
{
etl::callback_timer_atomic<1, std::atomic_uint32_t>* p_controller = nullptr;
etl::timer::id::type id = etl::timer::id::NO_TIMER;
uint32_t new_period = 0U;
std::vector<uint64_t> call_ticks;
void callback()
{
call_ticks.push_back(ticks);
// Change the period (this stops/deactivates the timer) and restart it
// from inside the callback. Before the fix, tick() would then insert the
// timer a second time, creating a circular reference in the active list.
p_controller->set_period(id, new_period);
p_controller->start(id);
}
};
RestartInCallback restart_in_callback;
using timer_callback_type = etl::icallback_timer_atomic<std::atomic_uint32_t>::callback_type;
timer_callback_type restart_in_callback_member =
timer_callback_type::create<RestartInCallback, restart_in_callback, &RestartInCallback::callback>();
SUITE(test_callback_timer_atomic)
{
//*************************************************************************
@ -855,6 +884,54 @@ namespace
CHECK_EQUAL(3, timerInsertRemoveTest.removed);
}
//*************************************************************************
// Regression test for issue #967:
// Calling start() from within a repeating timer's callback (after the
// period has been changed, which stops the timer) must not insert the timer
// twice and create a circular reference in the active list.
//*************************************************************************
TEST(callback_timer_atomic_restart_in_callback_no_circular_reference)
{
etl::callback_timer_atomic<1, std::atomic_uint32_t> timer_controller;
restart_in_callback.p_controller = &timer_controller;
restart_in_callback.new_period = 20U;
restart_in_callback.call_ticks.clear();
etl::timer::id::type id = timer_controller.register_timer(restart_in_callback_member, 10, etl::timer::mode::Repeating);
restart_in_callback.id = id;
timer_controller.start(id);
timer_controller.enable(true);
ticks = 0;
const uint32_t step = 1U;
// Run long enough for several firings at the original (10) then new (20) period.
while (ticks < 100U)
{
ticks += step;
timer_controller.tick(step);
}
// Detect a duplicated entry in the active list directly, rather than
// relying on the resulting circular reference hanging the test.
CHECK_FALSE(timer_controller.duplicate_insert_detected());
// The timer must still be active (alive, not corrupted) and scheduled at
// the new period. A circular reference would corrupt the active list.
CHECK_TRUE(timer_controller.is_active(id));
// First firing at 10, then every 20 thereafter: 10, 30, 50, 70, 90.
std::vector<uint64_t> compare = {10, 30, 50, 70, 90};
CHECK_EQUAL(compare.size(), restart_in_callback.call_ticks.size());
CHECK_ARRAY_EQUAL(compare.data(), restart_in_callback.call_ticks.data(), compare.size());
timer_controller.clear();
}
//*************************************************************************
#if REALTIME_TEST

View File

@ -162,6 +162,33 @@ namespace
callback_type free_function_callback2 = callback_type::create<free_callback2>();
//***************************************************************************
// Fixture for a repeating timer that restarts itself from within its own
// callback (regression test for issue #967).
//***************************************************************************
struct RestartInCallback
{
etl::callback_timer_interrupt<1, ScopedGuard>* p_controller = nullptr;
etl::timer::id::type id = etl::timer::id::NO_TIMER;
uint32_t new_period = 0U;
std::vector<uint64_t> call_ticks;
void callback()
{
call_ticks.push_back(ticks);
// Change the period (this stops/deactivates the timer) and restart it
// from inside the callback. Before the fix, tick() would then insert the
// timer a second time, creating a circular reference in the active list.
p_controller->set_period(id, new_period);
p_controller->start(id);
}
};
RestartInCallback restart_in_callback;
callback_type restart_in_callback_member = callback_type::create<RestartInCallback, restart_in_callback, &RestartInCallback::callback>();
SUITE(test_callback_timer_interrupt)
{
//*************************************************************************
@ -1001,5 +1028,55 @@ namespace
// Check the
CHECK_EQUAL(0U, ScopedGuard::guard_count);
}
//*************************************************************************
// Regression test for issue #967:
// Calling start() from within a repeating timer's callback (after the
// period has been changed, which stops the timer) must not insert the timer
// twice and create a circular reference in the active list.
//*************************************************************************
TEST(callback_timer_interrupt_restart_in_callback_no_circular_reference)
{
etl::callback_timer_interrupt<1, ScopedGuard> timer_controller;
restart_in_callback.p_controller = &timer_controller;
restart_in_callback.new_period = 20U;
restart_in_callback.call_ticks.clear();
etl::timer::id::type id = timer_controller.register_timer(restart_in_callback_member, 10, etl::timer::mode::Repeating);
restart_in_callback.id = id;
timer_controller.start(id);
timer_controller.enable(true);
ticks = 0;
const uint32_t step = 1U;
// Run long enough for several firings at the original (10) then new (20) period.
while (ticks < 100U)
{
ticks += step;
timer_controller.tick(step);
}
// Detect a duplicated entry in the active list directly, rather than
// relying on the resulting circular reference hanging the test.
CHECK_FALSE(timer_controller.duplicate_insert_detected());
// The timer must still be active (alive, not corrupted) and scheduled at
// the new period. A circular reference would corrupt the active list.
CHECK_TRUE(timer_controller.is_active(id));
// First firing at 10, then every 20 thereafter: 10, 30, 50, 70, 90.
std::vector<uint64_t> compare = {10, 30, 50, 70, 90};
CHECK_EQUAL(compare.size(), restart_in_callback.call_ticks.size());
CHECK_ARRAY_EQUAL(compare.data(), restart_in_callback.call_ticks.data(), compare.size());
timer_controller.clear();
CHECK_EQUAL(0U, ScopedGuard::guard_count);
}
}
} // namespace

View File

@ -184,6 +184,33 @@ namespace
callback_type free_function_callback2 = callback_type::create<free_callback2>();
//***************************************************************************
// Fixture for a repeating timer that restarts itself from within its own
// callback (regression test for issue #967).
//***************************************************************************
struct RestartInCallback
{
etl::callback_timer_locked<1>* p_controller = nullptr;
etl::timer::id::type id = etl::timer::id::NO_TIMER;
uint32_t new_period = 0U;
std::vector<uint64_t> call_ticks;
void callback()
{
call_ticks.push_back(ticks);
// Change the period (this stops/deactivates the timer) and restart it
// from inside the callback. Before the fix, tick() would then insert the
// timer a second time, creating a circular reference in the active list.
p_controller->set_period(id, new_period);
p_controller->start(id);
}
};
RestartInCallback restart_in_callback;
callback_type restart_in_callback_member = callback_type::create<RestartInCallback, restart_in_callback, &RestartInCallback::callback>();
SUITE(test_callback_timer_locked)
{
//*************************************************************************
@ -1004,6 +1031,59 @@ namespace
CHECK_FALSE(timer_controller.has_active_timer());
}
//*************************************************************************
// Regression test for issue #967:
// Calling start() from within a repeating timer's callback (after the
// period has been changed, which stops the timer) must not insert the timer
// twice and create a circular reference in the active list.
//*************************************************************************
TEST(callback_timer_locked_restart_in_callback_no_circular_reference)
{
locks.clear();
try_lock_type try_lock = try_lock_type::create<Locks, locks, &Locks::try_lock>();
lock_type lock = lock_type::create<Locks, locks, &Locks::lock>();
unlock_type unlock = unlock_type::create<Locks, locks, &Locks::unlock>();
etl::callback_timer_locked<1> timer_controller(try_lock, lock, unlock);
restart_in_callback.p_controller = &timer_controller;
restart_in_callback.new_period = 20U;
restart_in_callback.call_ticks.clear();
etl::timer::id::type id = timer_controller.register_timer(restart_in_callback_member, 10, etl::timer::mode::Repeating);
restart_in_callback.id = id;
timer_controller.start(id);
timer_controller.enable(true);
ticks = 0;
const uint32_t step = 1U;
// Run long enough for several firings at the original (10) then new (20) period.
while (ticks < 100U)
{
ticks += step;
timer_controller.tick(step);
}
// Detect a duplicated entry in the active list directly, rather than
// relying on the resulting circular reference hanging the test.
CHECK_FALSE(timer_controller.duplicate_insert_detected());
// The timer must still be active (alive, not corrupted) and scheduled at
// the new period. A circular reference would corrupt the active list.
CHECK_TRUE(timer_controller.is_active(id));
// First firing at 10, then every 20 thereafter: 10, 30, 50, 70, 90.
std::vector<uint64_t> compare = {10, 30, 50, 70, 90};
CHECK_EQUAL(compare.size(), restart_in_callback.call_ticks.size());
CHECK_ARRAY_EQUAL(compare.data(), restart_in_callback.call_ticks.data(), compare.size());
timer_controller.clear();
}
//*************************************************************************
#if REALTIME_TEST

View File

@ -367,6 +367,208 @@ namespace
CHECK_EQUAL(f, etl::be_float_t::value_from(be_f.data()));
CHECK_EQUAL(d, etl::le_double_t::value_from(le_d.data()));
}
/// Unsigned integers that have no native equivalent, such as 24 bit.
//*************************************************************************
TEST(test_uint24)
{
CHECK_EQUAL(3U, sizeof(etl::le_uint24_t));
CHECK_EQUAL(3U, sizeof(etl::be_uint24_t));
CHECK_EQUAL(3U, size_t(etl::le_uint24_t::Size));
CHECK((etl::is_same<uint32_t, etl::le_uint24_t::value_type>::value));
CHECK_EQUAL(uint32_t(0x00FFFFFF), etl::le_uint24_t::Max_Value);
etl::le_uint24_t le_v(0x123456U);
etl::be_uint24_t be_v(0x123456U);
CHECK_EQUAL(uint32_t(0x123456), le_v.value());
CHECK_EQUAL(uint32_t(0x123456), be_v.value());
// Check the storage byte order.
CHECK_EQUAL(0x56, int(le_v.data()[0]));
CHECK_EQUAL(0x34, int(le_v.data()[1]));
CHECK_EQUAL(0x12, int(le_v.data()[2]));
CHECK_EQUAL(0x12, int(be_v.data()[0]));
CHECK_EQUAL(0x34, int(be_v.data()[1]));
CHECK_EQUAL(0x56, int(be_v.data()[2]));
// Copy construction, including from the other endianness.
etl::le_uint24_t le_v2(le_v);
etl::be_uint24_t be_v2(be_v);
etl::le_uint24_t le_v3(be_v);
etl::be_uint24_t be_v3(le_v);
CHECK_EQUAL(uint32_t(0x123456), le_v2.value());
CHECK_EQUAL(uint32_t(0x123456), be_v2.value());
CHECK_EQUAL(uint32_t(0x123456), le_v3.value());
CHECK_EQUAL(uint32_t(0x123456), be_v3.value());
// Assignment.
le_v2 = 0xFFFFFFU;
be_v2 = 0xFFFFFFU;
CHECK_EQUAL(uint32_t(0xFFFFFF), le_v2.value());
CHECK_EQUAL(uint32_t(0xFFFFFF), be_v2.value());
// Boundary values.
etl::le_uint24_t le_zero(0U);
etl::be_uint24_t be_zero(0U);
CHECK_EQUAL(uint32_t(0), le_zero.value());
CHECK_EQUAL(uint32_t(0), be_zero.value());
// Values that do not fit are truncated, like a narrowing cast.
etl::le_uint24_t le_truncated(0xAB123456U);
etl::be_uint24_t be_truncated(0xAB123456U);
CHECK_EQUAL(uint32_t(0x123456), le_truncated.value());
CHECK_EQUAL(uint32_t(0x123456), be_truncated.value());
}
//*************************************************************************
TEST(test_uint24_decode_buffer)
{
const std::array<unsigned char, 4> buffer = {0xAA, 0xBB, 0xCC, 0xDD};
etl::le_uint24_t le_v(buffer.data(), buffer.size());
etl::be_uint24_t be_v(buffer.data(), buffer.size());
CHECK_EQUAL(uint32_t(0xCCBBAA), le_v.value());
CHECK_EQUAL(uint32_t(0xAABBCC), be_v.value());
// One byte short.
CHECK_THROW(etl::le_uint24_t le_short(buffer.data(), 2U), etl::unaligned_type_buffer_size);
CHECK_THROW(etl::be_uint24_t be_short(buffer.data(), 2U), etl::unaligned_type_buffer_size);
}
#if ETL_USING_64BIT_TYPES
//*************************************************************************
TEST(test_uint40_uint48_uint56)
{
CHECK_EQUAL(5U, sizeof(etl::be_uint40_t));
CHECK_EQUAL(6U, sizeof(etl::be_uint48_t));
CHECK_EQUAL(7U, sizeof(etl::be_uint56_t));
etl::be_uint48_t be_v(0x0123456789ABULL);
etl::le_uint48_t le_v(0x0123456789ABULL);
CHECK_EQUAL(uint64_t(0x0123456789AB), be_v.value());
CHECK_EQUAL(uint64_t(0x0123456789AB), le_v.value());
CHECK_EQUAL(0x01, int(be_v.data()[0]));
CHECK_EQUAL(0xAB, int(le_v.data()[0]));
CHECK_EQUAL(uint64_t(0x000000FFFFFFFFFF), etl::be_uint40_t::Max_Value);
CHECK_EQUAL(uint64_t(0x00FFFFFFFFFFFFFF), etl::be_uint56_t::Max_Value);
}
#endif
//*************************************************************************
/// Signed integers that have no native equivalent, such as 24 bit.
//*************************************************************************
TEST(test_int24)
{
CHECK_EQUAL(3U, sizeof(etl::le_int24_t));
CHECK_EQUAL(3U, sizeof(etl::be_int24_t));
CHECK_EQUAL(3U, size_t(etl::le_int24_t::Size));
CHECK((etl::is_same<int32_t, etl::le_int24_t::value_type>::value));
CHECK_EQUAL(int32_t(8388607), etl::le_int24_t::Max_Value);
CHECK_EQUAL(int32_t(-8388608), etl::le_int24_t::Min_Value);
etl::le_int24_t le_v(0x123456);
etl::be_int24_t be_v(0x123456);
CHECK_EQUAL(int32_t(0x123456), le_v.value());
CHECK_EQUAL(int32_t(0x123456), be_v.value());
// Check the storage byte order.
CHECK_EQUAL(0x56, int(le_v.data()[0]));
CHECK_EQUAL(0x12, int(le_v.data()[2]));
CHECK_EQUAL(0x12, int(be_v.data()[0]));
CHECK_EQUAL(0x56, int(be_v.data()[2]));
// Negative values are sign extended on reading.
etl::le_int24_t le_n(-2);
etl::be_int24_t be_n(-2);
CHECK_EQUAL(int32_t(-2), le_n.value());
CHECK_EQUAL(int32_t(-2), be_n.value());
CHECK_EQUAL(0xFE, int(le_n.data()[0]));
CHECK_EQUAL(0xFF, int(le_n.data()[1]));
CHECK_EQUAL(0xFF, int(le_n.data()[2]));
CHECK_EQUAL(0xFF, int(be_n.data()[0]));
CHECK_EQUAL(0xFF, int(be_n.data()[1]));
CHECK_EQUAL(0xFE, int(be_n.data()[2]));
// Copy construction, including from the other endianness.
etl::le_int24_t le_n2(be_n);
etl::be_int24_t be_n2(le_n);
CHECK_EQUAL(int32_t(-2), le_n2.value());
CHECK_EQUAL(int32_t(-2), be_n2.value());
// Assignment.
le_n2 = -1;
be_n2 = -1;
CHECK_EQUAL(int32_t(-1), le_n2.value());
CHECK_EQUAL(int32_t(-1), be_n2.value());
// Boundary values.
etl::le_int24_t le_min(etl::le_int24_t::Min_Value);
etl::be_int24_t be_min(etl::be_int24_t::Min_Value);
etl::le_int24_t le_max(etl::le_int24_t::Max_Value);
etl::be_int24_t be_max(etl::be_int24_t::Max_Value);
CHECK_EQUAL(int32_t(-8388608), le_min.value());
CHECK_EQUAL(int32_t(-8388608), be_min.value());
CHECK_EQUAL(int32_t(8388607), le_max.value());
CHECK_EQUAL(int32_t(8388607), be_max.value());
etl::le_int24_t le_zero(0);
CHECK_EQUAL(int32_t(0), le_zero.value());
// Values that do not fit are truncated, like a narrowing cast.
etl::be_int24_t be_truncated(0x7F123456);
CHECK_EQUAL(int32_t(0x123456), be_truncated.value());
}
//*************************************************************************
TEST(test_int24_decode_buffer)
{
const std::array<unsigned char, 4> buffer = {0x80, 0x00, 0x00, 0xDD};
etl::le_int24_t le_v(buffer.data(), buffer.size());
etl::be_int24_t be_v(buffer.data(), buffer.size());
CHECK_EQUAL(int32_t(0x000080), le_v.value());
CHECK_EQUAL(int32_t(-8388608), be_v.value());
// One byte short.
CHECK_THROW(etl::le_int24_t le_short(buffer.data(), 2U), etl::unaligned_type_buffer_size);
CHECK_THROW(etl::be_int24_t be_short(buffer.data(), 2U), etl::unaligned_type_buffer_size);
}
#if ETL_USING_64BIT_TYPES
//*************************************************************************
TEST(test_int40_int48_int56)
{
CHECK_EQUAL(5U, sizeof(etl::be_int40_t));
CHECK_EQUAL(6U, sizeof(etl::be_int48_t));
CHECK_EQUAL(7U, sizeof(etl::be_int56_t));
etl::be_int48_t be_v(-1099511627775LL);
etl::le_int48_t le_v(-1099511627775LL);
CHECK_EQUAL(int64_t(-1099511627775LL), be_v.value());
CHECK_EQUAL(int64_t(-1099511627775LL), le_v.value());
CHECK_EQUAL(int64_t(549755813887LL), etl::be_int40_t::Max_Value);
CHECK_EQUAL(int64_t(-549755813888LL), etl::be_int40_t::Min_Value);
CHECK_EQUAL(int64_t(36028797018963967LL), etl::be_int56_t::Max_Value);
CHECK_EQUAL(int64_t(-36028797018963968LL), etl::be_int56_t::Min_Value);
}
#endif
//*************************************************************************
// The following tests demonstrate the 'decode' direction: given a raw byte

View File

@ -49,6 +49,8 @@ namespace
using storage_i16_t = std::array<unsigned char, sizeof(int16_t)>;
using storage_i32_t = std::array<unsigned char, sizeof(int32_t)>;
using storage_i64_t = std::array<unsigned char, sizeof(int64_t)>;
using storage_24_t = std::array<unsigned char, 3U>;
using storage_48_t = std::array<unsigned char, 6U>;
SUITE(test_unaligned_type_ext)
{
@ -506,6 +508,148 @@ namespace
CHECK_EQUAL(sizeof(uint64_t), etl::net_uint64_ext_t::Size);
}
//*************************************************************************
/// Integers that have no native equivalent, such as 24 bit.
//*************************************************************************
TEST(test_uint24)
{
storage_24_t store_le1;
storage_24_t store_be1;
storage_24_t store_le2;
storage_24_t store_be2;
CHECK_EQUAL(3U, size_t(etl::le_uint24_ext_t::Size));
CHECK((etl::is_same<uint32_t, etl::le_uint24_ext_t::value_type>::value));
CHECK_EQUAL(uint32_t(0x00FFFFFF), etl::le_uint24_ext_t::Max_Value);
CHECK_EQUAL(uint32_t(0), etl::le_uint24_ext_t::Min_Value);
etl::le_uint24_ext_t le_v1(store_le1.data());
le_v1 = 0x123456U;
etl::be_uint24_ext_t be_v1(0x123456U, store_be1.data());
CHECK_EQUAL(uint32_t(0x123456), le_v1.value());
CHECK_EQUAL(uint32_t(0x123456), be_v1.value());
// The external buffers hold the requested byte order.
CHECK_EQUAL(0x56, int(store_le1[0]));
CHECK_EQUAL(0x12, int(store_le1[2]));
CHECK_EQUAL(0x12, int(store_be1[0]));
CHECK_EQUAL(0x56, int(store_be1[2]));
// Copy construct from the other endianness.
etl::le_uint24_ext_t le_v2(be_v1, store_le2.data());
etl::be_uint24_ext_t be_v2(le_v1, store_be2.data());
CHECK_EQUAL(uint32_t(0x123456), le_v2.value());
CHECK_EQUAL(uint32_t(0x123456), be_v2.value());
// Assignment.
le_v2 = le_v1;
be_v2 = le_v1;
CHECK_EQUAL(uint32_t(0x123456), le_v2.value());
CHECK_EQUAL(uint32_t(0x123456), be_v2.value());
// Values that do not fit are truncated.
be_v2 = 0xABCDEF12U;
CHECK_EQUAL(uint32_t(0xCDEF12), be_v2.value());
}
//*************************************************************************
TEST(test_move_construction_uint24)
{
storage_24_t store_le1;
storage_24_t store_be1;
etl::le_uint24_ext_t le_v1(0x123456U, store_le1.data());
etl::be_uint24_ext_t be_v1(0x123456U, store_be1.data());
etl::le_uint24_ext_t le_v2(etl::move(le_v1));
etl::be_uint24_ext_t be_v2(etl::move(be_v1));
CHECK_TRUE(nullptr == le_v1.data());
CHECK_TRUE(nullptr == be_v1.data());
CHECK_EQUAL(uint32_t(0x123456), le_v2.value());
CHECK_EQUAL(uint32_t(0x123456), be_v2.value());
}
//*************************************************************************
TEST(test_int24)
{
storage_24_t store_le1;
storage_24_t store_be1;
storage_24_t store_le2;
storage_24_t store_be2;
CHECK_EQUAL(3U, size_t(etl::le_int24_ext_t::Size));
CHECK((etl::is_same<int32_t, etl::le_int24_ext_t::value_type>::value));
CHECK_EQUAL(int32_t(8388607), etl::le_int24_ext_t::Max_Value);
CHECK_EQUAL(int32_t(-8388608), etl::le_int24_ext_t::Min_Value);
etl::le_int24_ext_t le_v1(store_le1.data());
le_v1 = -2;
etl::be_int24_ext_t be_v1(-2, store_be1.data());
// Negative values are sign extended on reading.
CHECK_EQUAL(int32_t(-2), le_v1.value());
CHECK_EQUAL(int32_t(-2), be_v1.value());
CHECK_EQUAL(0xFE, int(store_le1[0]));
CHECK_EQUAL(0xFF, int(store_le1[2]));
CHECK_EQUAL(0xFF, int(store_be1[0]));
CHECK_EQUAL(0xFE, int(store_be1[2]));
// Copy construct from the other endianness.
etl::le_int24_ext_t le_v2(be_v1, store_le2.data());
etl::be_int24_ext_t be_v2(le_v1, store_be2.data());
CHECK_EQUAL(int32_t(-2), le_v2.value());
CHECK_EQUAL(int32_t(-2), be_v2.value());
// Boundary values.
le_v2 = etl::le_int24_ext_t::Min_Value;
be_v2 = etl::be_int24_ext_t::Max_Value;
CHECK_EQUAL(int32_t(-8388608), le_v2.value());
CHECK_EQUAL(int32_t(8388607), be_v2.value());
le_v2 = 0;
CHECK_EQUAL(int32_t(0), le_v2.value());
}
//*************************************************************************
TEST(test_move_construction_int24)
{
storage_24_t store_le1;
storage_24_t store_be1;
etl::le_int24_ext_t le_v1(-1234567, store_le1.data());
etl::be_int24_ext_t be_v1(-1234567, store_be1.data());
etl::le_int24_ext_t le_v2(etl::move(le_v1));
etl::be_int24_ext_t be_v2(etl::move(be_v1));
CHECK_TRUE(nullptr == le_v1.data());
CHECK_TRUE(nullptr == be_v1.data());
CHECK_EQUAL(int32_t(-1234567), le_v2.value());
CHECK_EQUAL(int32_t(-1234567), be_v2.value());
}
#if ETL_USING_64BIT_TYPES
//*************************************************************************
TEST(test_uint48_int48)
{
storage_48_t store_u;
storage_48_t store_i;
etl::be_uint48_ext_t be_u(0x0123456789ABULL, store_u.data());
etl::be_int48_ext_t be_i(-1099511627775LL, store_i.data());
CHECK_EQUAL(6U, size_t(etl::be_uint48_ext_t::Size));
CHECK_EQUAL(uint64_t(0x0123456789AB), be_u.value());
CHECK_EQUAL(int64_t(-1099511627775LL), be_i.value());
}
#endif
#if ETL_HAS_CONSTEXPR_ENDIANNESS
//*************************************************************************
TEST(test_host_sizes)
@ -1528,6 +1672,106 @@ namespace
CHECK_EQUAL(0x12, bev0);
CHECK_EQUAL(0x34, bev1);
}
//*************************************************************************
TEST(test_shared_storage_copy_construction_uint24)
{
storage_24_t store;
etl::be_uint24_ext_t be_v(0x123456, store.data());
// Reinterpret the same buffer with the opposite endianness.
etl::le_uint24_ext_t le_v(be_v, store.data());
CHECK_EQUAL(0x56, int(store[0]));
CHECK_EQUAL(0x34, int(store[1]));
CHECK_EQUAL(0x12, int(store[2]));
CHECK_EQUAL(0x123456, le_v.value());
}
//*************************************************************************
TEST(test_shared_storage_copy_construction_int24)
{
storage_24_t store;
etl::be_int24_ext_t be_v(-0x123456, store.data());
etl::le_int24_ext_t le_v(be_v, store.data());
CHECK_EQUAL(-0x123456, le_v.value());
}
//*************************************************************************
TEST(test_shared_storage_copy_assignment_uint24)
{
storage_24_t store;
etl::be_uint24_ext_t be_v(0x123456, store.data());
etl::le_uint24_ext_t le_v(store.data());
le_v = be_v;
CHECK_EQUAL(0x56, int(store[0]));
CHECK_EQUAL(0x34, int(store[1]));
CHECK_EQUAL(0x12, int(store[2]));
CHECK_EQUAL(0x123456, le_v.value());
}
//*************************************************************************
TEST(test_shared_storage_copy_assignment_int24)
{
storage_24_t store;
etl::be_int24_ext_t be_v(-0x123456, store.data());
etl::le_int24_ext_t le_v(store.data());
le_v = be_v;
CHECK_EQUAL(-0x123456, le_v.value());
}
//*************************************************************************
TEST(test_shared_storage_copy_assignment_uint32)
{
storage_i32_t store;
etl::be_uint32_ext_t be_v(0x12345678, store.data());
etl::le_uint32_ext_t le_v(store.data());
le_v = be_v;
CHECK_EQUAL(0x78, int(store[0]));
CHECK_EQUAL(0x56, int(store[1]));
CHECK_EQUAL(0x34, int(store[2]));
CHECK_EQUAL(0x12, int(store[3]));
CHECK_EQUAL(0x12345678, le_v.value());
}
//*************************************************************************
TEST(test_shared_storage_copy_assignment_same_endianness)
{
storage_i32_t store;
etl::le_uint32_ext_t le_v1(0x12345678, store.data());
etl::le_uint32_ext_t le_v2(store.data());
le_v2 = le_v1;
CHECK_EQUAL(0x12345678, le_v2.value());
}
//*************************************************************************
TEST(test_shared_storage_copy_assignment_float)
{
storage_f_t store;
etl::be_float_ext_t be_v(3.1415927f, store.data());
etl::le_float_ext_t le_v(store.data());
le_v = be_v;
CHECK_EQUAL(3.1415927f, le_v.value());
}
}
} // namespace