diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 626da8b8..9ad11562 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,8 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest, windows-2025, ubuntu-26.04] include: + - os: ubuntu-latest + cmake_extra_args: '-DCMAKE_CXX_FLAGS="-D_GLIBCXX_ASSERTIONS"' - os: windows-latest cmake_extra_args: '-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"' - os: windows-2025 diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index b1641ba3..ed4835a8 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -749,13 +750,35 @@ long double ByteVector::toFloat80BE(size_t offset) const return toFloat80(*this, offset); } +namespace +{ + // If the standard library checks indices in std::vector::operator[], e.g. + // with _GLIBCXX_ASSERTIONS or a libc++ hardening mode, ByteVector::operator[] + // is checked too. This cannot be left to std::vector, because a ByteVector + // created by mid() shares the buffer of the original byte vector. + void checkIndex(const ByteVector &v, int index) + { +#if defined(_GLIBCXX_ASSERTIONS) || \ + (defined(_LIBCPP_HARDENING_MODE) && \ + _LIBCPP_HARDENING_MODE != _LIBCPP_HARDENING_MODE_NONE) + if(index < 0 || static_cast(index) >= v.size()) + std::abort(); +#else + static_cast(v); + static_cast(index); +#endif + } +} // namespace + const char &ByteVector::operator[](int index) const { + checkIndex(*this, index); return (*d->data)[d->offset + index]; } char &ByteVector::operator[](int index) { + checkIndex(*this, index); detach(); return (*d->data)[d->offset + index]; } diff --git a/taglib/toolkit/tlist.tcc b/taglib/toolkit/tlist.tcc index bc253179..539fad16 100644 --- a/taglib/toolkit/tlist.tcc +++ b/taglib/toolkit/tlist.tcc @@ -24,6 +24,7 @@ ***************************************************************************/ #include +#include #include namespace TagLib { @@ -43,6 +44,22 @@ class ListPrivateBase { public: bool autoDelete{}; + + // If the standard library checks indices in std::vector::operator[], e.g. + // with _GLIBCXX_ASSERTIONS or a libc++ hardening mode, List::operator[] + // is checked too. + static void checkIndex(unsigned int i, size_t size) + { +#if defined(_GLIBCXX_ASSERTIONS) || \ + (defined(_LIBCPP_HARDENING_MODE) && \ + _LIBCPP_HARDENING_MODE != _LIBCPP_HARDENING_MODE_NONE) + if(i >= size) + std::abort(); +#else + static_cast(i); + static_cast(size); +#endif + } }; // A generic implementation @@ -289,6 +306,7 @@ T &List::back() template T &List::operator[](unsigned int i) { + ListPrivateBase::checkIndex(i, d->list.size()); auto it = d->list.begin(); std::advance(it, i); @@ -298,6 +316,7 @@ T &List::operator[](unsigned int i) template const T &List::operator[](unsigned int i) const { + ListPrivateBase::checkIndex(i, d->list.size()); auto it = d->list.begin(); std::advance(it, i); diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index f149d619..2ffd35a1 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -50,6 +50,7 @@ class TestByteVector : public CppUnit::TestFixture CPPUNIT_TEST(testReplace); CPPUNIT_TEST(testReplaceAndDetach); CPPUNIT_TEST(testIterator); + CPPUNIT_TEST(testIndex); CPPUNIT_TEST(testResize); CPPUNIT_TEST(testAppend1); CPPUNIT_TEST(testAppend2); @@ -420,6 +421,21 @@ public: CPPUNIT_ASSERT_EQUAL('3', *it4); } + void testIndex() + { + const ByteVector a("0123456789"); + const ByteVector b = a.mid(3, 4); + CPPUNIT_ASSERT_EQUAL('3', b[0]); + CPPUNIT_ASSERT_EQUAL('6', b[3]); + + ByteVector c = a.mid(7); + CPPUNIT_ASSERT_EQUAL('7', c[0]); + CPPUNIT_ASSERT_EQUAL('9', c[2]); + c[2] = 'X'; + CPPUNIT_ASSERT_EQUAL(ByteVector("78X"), c); + CPPUNIT_ASSERT_EQUAL('9', a[9]); + } + void testResize() { auto a = ByteVector("0123456789");