Fix etl::bitset single-element from_string shift on empty string (#1486)

The single-element from_string overloads (char, wchar_t, char16_t,
char32_t) computed element_type(1) << (string_length - 1U) before the
copy loop. For an empty string, or when active_bits is 0, string_length
is 0, so the shift count underflows to SIZE_MAX. Shifting by more than
the element width is undefined behaviour, which also breaks constexpr
evaluation.

Guard the mask with a zero-length check so the shift is only performed
when string_length > 0. The multi-element from_string overloads were
already safe.

Add test_construct_from_empty_string regression test.

Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>
This commit is contained in:
Roland Reichwein 2026-07-07 13:40:04 +02:00 committed by GitHub
parent 98cb365d4c
commit 032ec4cc80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 0 deletions

View File

@ -311,6 +311,11 @@ namespace etl
// Build from the string. // Build from the string.
string_length = etl::min(active_bits, string_length); string_length = etl::min(active_bits, string_length);
if (string_length == 0U)
{
return;
}
element_type mask = element_type(element_type(1) << (string_length - 1U)); element_type mask = element_type(element_type(1) << (string_length - 1U));
for (size_t i = 0U; i < string_length; ++i) for (size_t i = 0U; i < string_length; ++i)
@ -339,6 +344,11 @@ namespace etl
// Build from the string. // Build from the string.
string_length = etl::min(active_bits, string_length); string_length = etl::min(active_bits, string_length);
if (string_length == 0U)
{
return;
}
element_type mask = element_type(element_type(1) << (string_length - 1U)); element_type mask = element_type(element_type(1) << (string_length - 1U));
for (size_t i = 0U; i < string_length; ++i) for (size_t i = 0U; i < string_length; ++i)
@ -367,6 +377,11 @@ namespace etl
// Build from the string. // Build from the string.
string_length = etl::min(active_bits, string_length); string_length = etl::min(active_bits, string_length);
if (string_length == 0U)
{
return;
}
element_type mask = element_type(element_type(1) << (string_length - 1U)); element_type mask = element_type(element_type(1) << (string_length - 1U));
for (size_t i = 0U; i < string_length; ++i) for (size_t i = 0U; i < string_length; ++i)
@ -395,6 +410,11 @@ namespace etl
// Build from the string. // Build from the string.
string_length = etl::min(active_bits, string_length); string_length = etl::min(active_bits, string_length);
if (string_length == 0U)
{
return;
}
element_type mask = element_type(element_type(1) << (string_length - 1U)); element_type mask = element_type(element_type(1) << (string_length - 1U));
for (size_t i = 0U; i < string_length; ++i) for (size_t i = 0U; i < string_length; ++i)

View File

@ -398,6 +398,22 @@ namespace
CHECK_EQUAL(0, data.count()); CHECK_EQUAL(0, data.count());
} }
//*************************************************************************
// Empty strings exercise the string_length == 0 path in from_string,
// which must not shift by (0 - 1U).
TEST(test_construct_from_empty_string)
{
etl::bitset<64, uint64_t> data_char("");
etl::bitset<64, uint64_t> data_wchar(L"");
etl::bitset<64, uint64_t> data_u16(u"");
etl::bitset<64, uint64_t> data_u32(U"");
CHECK_EQUAL(0U, data_char.count());
CHECK_EQUAL(0U, data_wchar.count());
CHECK_EQUAL(0U, data_u16.count());
CHECK_EQUAL(0U, data_u32.count());
}
//************************************************************************* //*************************************************************************
TEST(test_construct_from_excess_string) TEST(test_construct_from_excess_string)
{ {