电子艺术几乎完美的图书馆

GitHub上的Electronic Arts存储库最近吸引了我们的注意力。 它很小,在那里可用的23个项目中,只有几个C ++库似乎很有趣:EASTL,EAStdC,EABase,EAThread,EATest,EAMain和EAAssert。 项目本身也很小(每个约10个文件),因此仅在“最大”的20个文件项目中发现了错误:D但是我们确实找到了它们,而且它们看起来确实很有趣! 在撰写本文时,我们还对EA游戏和公司政策进行了热烈的讨论:D

图片1


引言


电子艺界(EA)是一家美国视频游戏公司。 它在GitHub上有一个小型存储库 ,还有一些C ++项目,即C ++库:EASTL,EAStdC,EABase,EAThread,EATest,EAMain和EAAssert。 它们很小, PVS-Studio分析仪仅在“最大”项目EAStdC (20个文件)中设法找到了所有错误。 使用这样的大小,您将无法可靠地判断整体代码质量,因此,请查看以下五个警告并自行决定。

警告1


V524 “ >>”功能的主体完全等同于“ <<”功能的主体,这很奇怪。 第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;} .... } 

当重载移位运算符时,程序员通过编写<<而不是>>来对其中之一进行错别字。 这看起来很像是复制粘贴错误。

警告2


V557阵列可能超限。 索引“ nFormatLength”的值可以达到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函数具有尽可能高的索引(即15) ,则最终nFormatLength的索引增加到 16 。 之后,在switch语句中将发生数组越界错误。

此片段又被复制了两次:

  • V557阵列可能超限。 “ nFormatLength”索引的值可以达到16。EASprintfOrdered.cpp 614
  • V557阵列可能超限。 索引“ nFormatLength”的值可以达到16。EASprintfOrdered.cpp 977

警告3


V560条件表达式的一部分始终为true :(结果> = 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条件始终为true。 该代码看起来并不正确,其中肯定有一些错误。

此片段又被复制了两次:

  • V560条件表达式的一部分始终为true :(结果> = 0)。 EASprintfOrdered.cpp 852
  • V560条件表达式的一部分始终为true :(结果> = 0)。 EASprintfOrdered.cpp 1215

警告4


V1009检查阵列初始化。 仅第一个元素被显式初始化。 其余元素用零初始化。 EASprintfOrdered.cpp 151

 static int OVprintfCore(....) { .... int spanArgOrder[kArgCapacity] = { -1 }; .... } 

这不一定是一个错误,但应警告作者,只有spanArgOrder数组的第一个元素被初始化为-1 ,而其余​​所有元素都将被设置为0。

此片段又被复制了两次:

  • V1009检查阵列初始化。 仅第一个元素被显式初始化。 其余元素用零初始化。 EASprintfOrdered.cpp 518
  • V1009检查阵列初始化。 仅第一个元素被显式初始化。 其余元素用零初始化。 EASprintfOrdered.cpp 881

警告5


V728过度检查可以简化。 '((A &&!B)|| (!A && B)'表达式等同于'bool(A)!= Bool(B)'表达式。 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(); } 

现在的代码要短得多,这使条件的逻辑更容易掌握。

结论


您可能已经注意到,大多数警告都有另外两个重复项,所有这些重复项都在同一文件中找到。 代码重复是非常糟糕的反模式,因为它使程序维护变得非常复杂。 当错误蔓延到此类代码中时,程序的稳定性会急剧下降,因为它们会散布在整个代码中。

希望EA将上传其他一些有趣的项目,我们将再次访问它们的资源库:)。 同时,欢迎下载PVS-Studio并在您自己的项目中尝试。

Source: https://habr.com/ru/post/zh-CN461677/


All Articles