ByteVector, List: Check indices if the standard library does (#1467)

If the standard library checks the index in std::vector::operator[]
(_GLIBCXX_ASSERTIONS, libc++ hardening modes), ByteVector::operator[]
and List::operator[] now check the index against their own size and
abort if it is out of range, as std::vector does.

The std::vector check alone is not enough: a ByteVector created by
mid() shares the buffer of the original byte vector, so an index past
the end of the smaller vector still passes it. List::operator[] walks
a std::list, which these checks do not cover.

Builds without these macros are unchanged; a GCC Release build of
libtag produces identical code. The Ubuntu CI job now builds with
-D_GLIBCXX_ASSERTIONS so that the tests run with the checks enabled.
This commit is contained in:
Nathan Chandrasekar
2026-10-31 12:07:55 +01:00
committed by GitHub
parent cfb9a7d8a9
commit 50ca520b27
4 changed files with 60 additions and 0 deletions
+2
View File
@@ -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
+23
View File
@@ -28,6 +28,7 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>
@@ -749,13 +750,35 @@ long double ByteVector::toFloat80BE(size_t offset) const
return toFloat80<Utils::BigEndian>(*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<unsigned int>(index) >= v.size())
std::abort();
#else
static_cast<void>(v);
static_cast<void>(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];
}
+19
View File
@@ -24,6 +24,7 @@
***************************************************************************/
#include <algorithm>
#include <cstdlib>
#include <memory>
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<void>(i);
static_cast<void>(size);
#endif
}
};
// A generic implementation
@@ -289,6 +306,7 @@ T &List<T>::back()
template <class T>
T &List<T>::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<T>::operator[](unsigned int i)
template <class T>
const T &List<T>::operator[](unsigned int i) const
{
ListPrivateBase::checkIndex(i, d->list.size());
auto it = d->list.begin();
std::advance(it, i);
+16
View File
@@ -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");