Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Наше внимание привлёк репозиторий Electronic Arts на GitHub. Он очень маленький и из двадцати трёх проектов нас заинтересовали только несколько C++ библиотек: EASTL, EAStdC, EABase, EAThread, EATest, EAMain и EAAssert. Проекты оказались тоже очень маленькими (около 10 файлов), поэтому мы нашли ошибки только в «самом большом» из 20 файлов :D Но нашли, и интересные! Пока писалась заметка, мы с коллегами также бурно обсудили игры компании EA и её стратегию :D
Electronic Arts (EA) — американская компания, которая занимается распространением видеоигр. На сайте GitHub у неё есть небольшой репозиторий и несколько C++ проектов. Это C++ библиотеки: EASTL, EAStdC, EABase, EAThread, EATest, EAMain и EAAssert. Они очень маленькие и мы нашли интересные ошибки с помощью анализатора PVS-Studio только в «самом большом» из них — EAStdC (20 файлов). При таких объёмах сложно говорить о качестве кода в целом. Просто оцените 5 предупреждений и решите сами.
V524 It is odd that the body of '>>' function is fully equivalent to the body of '<<' function. EAFixedPoint.h 287
При перегрузке операторов сдвига программист допустил опечатку в одном из них, перепутав операторы << и >>. Скорее всего, это результат Copy-Paste-программирования.
V557 Array overrun is possible. The value of 'nFormatLength' index could reach 16. EASprintfOrdered.cpp 246
Массив spans[spanIndex].mFormat состоит из 16 элементов, т.е. последний валидный элемент имеет индекс 15. Сейчас код функции OVprintfCore написан так, что если индекс nFormatLength будет иметь максимально возможный индекс — 15, то произойдёт инкремент до 16. Далее в операторе switch возможен выход за границу массива.
Этот фрагмент кода скопирован ещё в 2 места:
V560 A part of conditional expression is always true: (result >= 0). EASprintfOrdered.cpp 489
Условие result >= 0 всегда истинно, потому что переменная result нигде в цикле не меняется. Код выглядит очень подозрительно и, скорее всего, имеет место ошибка в этом коде.
Этот фрагмент кода скопирован ещё в 2 места:
V1009 Check the array initialization. Only the first element is initialized explicitly. The rest elements are initialized with zeros. EASprintfOrdered.cpp 151
Возможно, это не является ошибкой, но разработчиков следует предупредить, что значением -1 инициализируется только первый элемент массива spanArgOrder, а все остальные будут иметь значение 0.
Этот фрагмент кода скопирован ещё в 2 места:
V728 An excessive check can be simplified. The '(A && !B) || (!A && B)' expression is equivalent to the 'bool(A) != bool(B)' expression. int128.h 1242
Этот фрагмент кода был отформатирован для удобства, но в оригинале это очень длинное условие, которое сложно читать. Другое дело, если мы упростим условное выражение, как советует анализатор:
Код стал значительно короче, что сильно упростило понимание логики условного выражения.
Как вы могли заметить, большинство подозрительных предупреждений продублированы в трех местах, причём в пределах одного и того же файла. Дублирование кода является очень плохой практикой разработки, т.к. усложняет поддержку кода. А если в такой код попадает ошибка, то стабильность программы резко снижается из-за распространения ошибок по всему коду.
Будем надеется, что опубликуют ещё что-то интересное и мы вновь вернёмся в этот репозиторий :). Ну а пока предлагаю желающим скачать PVS-Studio и попробовать проверить собственные проекты.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. Almost Perfect Libraries by Electronic Arts
Введение
Electronic Arts (EA) — американская компания, которая занимается распространением видеоигр. На сайте GitHub у неё есть небольшой репозиторий и несколько C++ проектов. Это C++ библиотеки: EASTL, EAStdC, EABase, EAThread, EATest, EAMain и EAAssert. Они очень маленькие и мы нашли интересные ошибки с помощью анализатора PVS-Studio только в «самом большом» из них — EAStdC (20 файлов). При таких объёмах сложно говорить о качестве кода в целом. Просто оцените 5 предупреждений и решите сами.
Предупреждение 1
V524 It is odd that the body of '>>' function is fully equivalent to the body of '<<' function. EAFixedPoint.h 287
template <class T,
int upShiftInt, int downShiftInt,
int upMulInt, int downDivInt>
struct FPTemplate
{
....
FPTemplate operator<<(int numBits) const { return value << numBits; }
FPTemplate operator>>(int numBits) const { return value << numBits; }
FPTemplate& operator<<=(int numBits) { value <<= numBits; return *this;}
FPTemplate& operator>>=(int numBits) { value >>= numBits; return *this;}
....
}
При перегрузке операторов сдвига программист допустил опечатку в одном из них, перепутав операторы << и >>. Скорее всего, это результат Copy-Paste-программирования.
Предупреждение 2
V557 Array overrun is possible. The value of 'nFormatLength' index could reach 16. EASprintfOrdered.cpp 246
static const int kSpanFormatCapacity = 16;
struct Span8
{
....
char mFormat[kSpanFormatCapacity];
....
};
static int OVprintfCore(....)
{
....
EA_ASSERT(nFormatLength < kSpanFormatCapacity);
if(nFormatLength < kSpanFormatCapacity)
spans[spanIndex].mFormat[nFormatLength++] = *p; // <=
else
return -1;
switch(*p)
{
case 'b': case 'd': case 'i': case 'u': case 'o': case 'x': case 'X':
case 'g': case 'G': case 'e': case 'E': case 'f': case 'F': case 'a':
case 'A': case 'p': case 'c': case 'C': case 's': case 'S': case 'n':
{
// Finalize the current span.
spans[spanIndex].mpEnd = p + 1;
spans[spanIndex].mFormat[nFormatLength] = 0; // <=
spans[spanIndex].mFormatChar = *p;
if(++spanIndex == kSpanCapacity)
break;
....
}
Массив spans[spanIndex].mFormat состоит из 16 элементов, т.е. последний валидный элемент имеет индекс 15. Сейчас код функции OVprintfCore написан так, что если индекс nFormatLength будет иметь максимально возможный индекс — 15, то произойдёт инкремент до 16. Далее в операторе switch возможен выход за границу массива.
Этот фрагмент кода скопирован ещё в 2 места:
- V557 Array overrun is possible. The value of 'nFormatLength' index could reach 16. EASprintfOrdered.cpp 614
- V557 Array overrun is possible. The value of 'nFormatLength' index could reach 16. EASprintfOrdered.cpp 977
Предупреждение 3
V560 A part of conditional expression is always true: (result >= 0). EASprintfOrdered.cpp 489
static int OVprintfCore(....)
{
....
for(result = 1; (result >= 0) && (p < pEnd); ++p)
{
if(pWriteFunction8(p, 1, pWriteFunctionContext8, kWFSIntermediate) < 0)
return -1;
nWriteCountSum += result;
}
....
}
Условие result >= 0 всегда истинно, потому что переменная result нигде в цикле не меняется. Код выглядит очень подозрительно и, скорее всего, имеет место ошибка в этом коде.
Этот фрагмент кода скопирован ещё в 2 места:
- V560 A part of conditional expression is always true: (result >= 0). EASprintfOrdered.cpp 852
- V560 A part of conditional expression is always true: (result >= 0). EASprintfOrdered.cpp 1215
Предупреждение 4
V1009 Check the array initialization. Only the first element is initialized explicitly. The rest elements are initialized with zeros. EASprintfOrdered.cpp 151
static int OVprintfCore(....)
{
....
int spanArgOrder[kArgCapacity] = { -1 };
....
}
Возможно, это не является ошибкой, но разработчиков следует предупредить, что значением -1 инициализируется только первый элемент массива spanArgOrder, а все остальные будут иметь значение 0.
Этот фрагмент кода скопирован ещё в 2 места:
- V1009 Check the array initialization. Only the first element is initialized explicitly. The rest elements are initialized with zeros. EASprintfOrdered.cpp 518
- V1009 Check the array initialization. Only the first element is initialized explicitly. The rest elements are initialized with zeros. EASprintfOrdered.cpp 881
Предупреждение 5
V728 An excessive check can be simplified. The '(A && !B) || (!A && B)' expression is equivalent to the 'bool(A) != bool(B)' expression. int128.h 1242
inline void int128_t::Modulus(....) const
{
....
bool bDividendNegative = false;
bool bDivisorNegative = false;
....
if( (bDividendNegative && !bDivisorNegative)
|| (!bDividendNegative && bDivisorNegative))
{
quotient.Negate();
}
....
}
Этот фрагмент кода был отформатирован для удобства, но в оригинале это очень длинное условие, которое сложно читать. Другое дело, если мы упростим условное выражение, как советует анализатор:
if( bDividendNegative != bDivisorNegative)
{
quotient.Negate();
}
Код стал значительно короче, что сильно упростило понимание логики условного выражения.
Заключение
Как вы могли заметить, большинство подозрительных предупреждений продублированы в трех местах, причём в пределах одного и того же файла. Дублирование кода является очень плохой практикой разработки, т.к. усложняет поддержку кода. А если в такой код попадает ошибка, то стабильность программы резко снижается из-за распространения ошибок по всему коду.
Будем надеется, что опубликуют ещё что-то интересное и мы вновь вернёмся в этот репозиторий :). Ну а пока предлагаю желающим скачать PVS-Studio и попробовать проверить собственные проекты.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. Almost Perfect Libraries by Electronic Arts