{"id":381104,"date":"2024-06-29T03:28:53","date_gmt":"2024-06-29T03:28:53","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=381104"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=381104","title":{"rendered":"<span>Yo, Ho, Ho, And a Bottle of Rum \u2014 Or How We Analyzed Storm Engine&#8217;s Bugs<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>PVS-Studio is a static analysis tool that helps find errors in software source code. This time PVS-Studio looked for bugs in Storm Engine&#8217;s source code.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/8fb\/6a7\/61a\/8fb6a761ade578c1d3f14570cc970c0a.jpg\" width=\"580\" height=\"327\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8fb\/6a7\/61a\/8fb6a761ade578c1d3f14570cc970c0a.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<h3>Storm Engine<\/h3>\n<p>Storm Engine is a gaming engine that Akella has been developing since January 2000, for the Sea Dogs game series. The game engine became open-source on March 26th, 2021. The source code is available on <a href=\"https:\/\/github.com\/storm-devs\/storm-engine\">GitHub<\/a> under the GPLv3 license. Storm Engine is written in C++.<\/p>\n<p>In total, PVS-Studio issued 235 high-level warnings and 794 medium-level warnings. Many of these warnings point to bugs that may cause undefined behavior. Other warnings reveal logical errors &#8212; the program runs well, but the execution&#8217;s result may be not what&#8217;s expected.<\/p>\n<p>Examining each of the 1029 errors PVS-Studio discovered &#8212; especially those that involve the project&#8217;s architecture &#8212; would take up an entire book that is difficult to write and read. In this article, I&#8217;ll review more obvious and on-the-surface-type errors that do not require delving deep into the project&#8217;s source code.<\/p>\n<h3>Detected Errors<\/h3>\n<h4>Redundant Checks<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v547\/\">V547<\/a> Expression &#8216;nStringCode >= 0xffffff&#8217; is always false. dstring_codec. h 84<\/p>\n<pre><code>#define DHASH_SINGLESYM 255 .... uint32_t Convert(const char *pString, ....) {   uint32_t nStringCode;   ....   nStringCode = ((((unsigned char)pString[0]) &lt;&lt; 8) &amp; 0xffffff00) |                   (DHASH_SINGLESYM)   ....   if (nStringCode >= 0xffffff)   {     __debugbreak();   }   return nStringCode; }<\/code><\/pre>\n<p>Let&#8217;s evaluate the expression that the <em>nStringCode<\/em> variable contains. The <em>unsigned<\/em> <em>char<\/em> type takes values in the range of *[0,255]*. Consequently, <em>(unsigned char)pString[0]<\/em> is always less than <em>2^8<\/em>. After left shifting the result by <em>8<\/em>, we get a number that does not exceed <em>2^16<\/em>. The &#8216;&amp;&#8217; operator does not augment this value. Then we increase the expression&#8217;s value by no more than <em>255<\/em>. As a result, the <em>nStringCode<\/em> variable&#8217;s value never exceeds <em>2^16+256<\/em>, and therefore, is always less than <em>0xffffff = 2^24-1<\/em>. Thus, the check is always false and is of no use. At first glance, it would seem that we can safely remove it:<\/p>\n<pre><code>#define DHASH_SINGLESYM 255 .... uint32_t Convert(const char *pString, ....) {   uint32_t nStringCode;   ....   nStringCode = ((((unsigned char)pString[0]) &lt;&lt; 8) &amp; 0xffffff00) |                 (DHASH_SINGLESYM) ....   return nStringCode; }<\/code><\/pre>\n<p>But let&#8217;s not rush into anything. Obviously, the check is here for a reason. The developers may have expected the expression or the <em>DHASH_SINGLESYM<\/em> constant to change in the future. This example demonstrates a case when the analyzer is technically correct, but the code fragment that triggered the warning might not require fixing.<\/p>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v560\/\">V560<\/a> A part of conditional expression is always true: 0x00 &lt;= c. utf8.h 187<\/p>\n<pre><code>inline bool IsValidUtf8(....) {   int c, i, ix, n, j;   for (i = 0, ix = str.length(); i &lt; ix; i++)s   {     c = (unsigned char)str[i];     if (0x00 &lt;= c &amp;&amp; c &lt;= 0x7f)       n = 0;     ....   }   .... }<\/code><\/pre>\n<p>The <em>c<\/em> variable holds an unsigned type value and the <em>0x00 &lt;= c<\/em> check can be removed as unnecessary. The fixed code:<\/p>\n<pre><code>inline bool IsValidUtf8(....) {   int c, i, ix, n, j;   for (i = 0, ix = str.length(); i &lt; ix; i++)s   {     c = (unsigned char)str[i];     if (c &lt;= 0x7f)       n = 0;     ....   }   .... }<\/code><\/pre>\n<h4>Reaching Outside Array Bounds<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v557\/\">V557<\/a> Array overrun is possible. The value of &#8216;TempLong2 &#8212; TempLong1 + 1&#8217; index could reach 520. internal_functions.cpp 1131<\/p>\n<pre><code>DATA *COMPILER::BC_CallIntFunction(....) {   if (TempLong2 - TempLong1 >= sizeof(Message_string))   {     SetError(\"internal: buffer too small\");     pV = SStack.Push();     pV->Set(\"\");     pVResult = pV;     return pV;   }   memcpy(Message_string, pChar + TempLong1,           TempLong2 - TempLong1 + 1);   Message_string[TempLong2 - TempLong1 + 1] = 0;   pV = SStack.Push(); }<\/code><\/pre>\n<p>Here the analyzer helped find the off-by-one error.<\/p>\n<p>The function above first makes sure that the <em>TempLong2 &#8212; TempLong1<\/em> value is less than the <em>Message_string<\/em> length. Then the <em>Message_string[TempLong2 &#8212; TempLong1 + 1]<\/em> element takes the 0 value. Note that if <em>TempLong2 &#8212; TempLong1 + 1 == sizeof(Message_string)<\/em>, the check is successful and the internal error is not generated. However, the <em>Message_string[TempLong2 &#8212; TempLong1 + 1]<\/em> element is of bounds. When this element is assigned a value, the function accesses unreserved memory. This causes undefined behavior. You can fix the check as follows:<\/p>\n<pre><code>DATA *COMPILER::BC_CallIntFunction(....) {   if (TempLong2 - TempLong1 + 1 >= sizeof(Message_string))   {     SetError(\"internal: buffer too small\");     pV = SStack.Push();     pV->Set(\"\");     pVResult = pV;     return pV;   }   memcpy(Message_string, pChar + TempLong1,           TempLong2 - TempLong1 + 1);   Message_string[TempLong2 - TempLong1 + 1] = 0;   pV = SStack.Push(); }<\/code><\/pre>\n<h4>Assigning a Variable to Itself<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v570\/\">V570<\/a> The &#8216;Data_num&#8217; variable is assigned to itself. s_stack.cpp 36<\/p>\n<pre><code>uint32_t Data_num; .... DATA *S_STACK::Push(....) {   if (Data_num > 1000)   {     Data_num = Data_num;   }   .... }<\/code><\/pre>\n<p>Someone may have written this code for debugging purposes and then forgot to remove it. Instead of a new value, the <em>Data_num<\/em> variable receives its own value. It is difficult to say what the developer wanted to assign here. I suppose <em>Data_num<\/em> should have received a value from a different variable with a similar name, but the names got mixed up. Alternatively, the developer may have intended to limit the <em>Data_num<\/em> value to the 1000 constant but made a typo. In any case there&#8217;s a mistake here that needs to be fixed.<\/p>\n<h4>Dereferencing a Null Pointer<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v595\/\">V595<\/a> The &#8216;rs&#8217; pointer was utilized before it was verified against nullptr. Check lines: 163, 164. Fader.cpp 163<\/p>\n<pre><code>uint64_t Fader::ProcessMessage(....) {   ....   textureID = rs->TextureCreate(_name);   if (rs)   {     rs->SetProgressImage(_name);     .... }<\/code><\/pre>\n<p>In the code above, the <em>rs<\/em> pointer is first dereferenced, and then evaluated against <em>nullptr<\/em>. If the pointer equals <em>nullptr<\/em>, the null pointer&#8217;s dereference causes undefined behavior. If this scenario is possible, it is necessary to place the check before the first dereference:<\/p>\n<pre><code>uint64_t Fader::ProcessMessage(....) {   ....   if (rs)   {     textureID = rs->TextureCreate(_name);     rs->SetProgressImage(_name);     .... }<\/code><\/pre>\n<p>If the scenario guarantees that <em>rs != nullptr<\/em> is always true, then you can remove the unnecessary <em>if (rs)<\/em> check:<\/p>\n<pre><code>uint64_t Fader::ProcessMessage(....) {   ....   textureID = rs->TextureCreate(_name);   rs->SetProgressImage(_name);   .... }<\/code><\/pre>\n<p>There&#8217;s also a third possible scenario. Someone could have intended to check the <em>textureID<\/em> variable.<\/p>\n<p>Overall, I encountered 14 of the V595 warnings in the project.<\/p>\n<p>If you are curious, <a href=\"https:\/\/pvs-studio.com\/en\/pvs-studio\/download\/\">download and start PVS-Studio<\/a>, analyze the project and review these warnings. Here I&#8217;ll limit myself to one more example:<\/p>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v595\/\">V595<\/a> The &#8216;pACh&#8217; pointer was utilized before it was verified against nullptr. Check lines: 1214, 1215. sail.cpp 1214<\/p>\n<pre><code>void SAIL::SetAllSails(int groupNum) {   ....   SetSailTextures(groupNum, core.Event(\"GetSailTextureData\",                   \"l\", pACh->GetAttributeAsDword(\"index\",  -1)));   if (pACh != nullptr){   .... }<\/code><\/pre>\n<p>When calculating the <em>Event<\/em> method&#8217;s arguments, the author dereferences the <em>pACh<\/em> pointer. Then, in the next line, the <em>pACh<\/em> pointer is checked against <em>nullptr<\/em>. If the pointer can take the null value, the if-statement that checks <em>pACh<\/em> for <em>nullptr<\/em> must come before the <em>SetSailTextures<\/em> function call that prompts pointer dereferencing.<\/p>\n<pre><code>void SAIL::SetAllSails(int groupNum) {   ....   if (pACh != nullptr){     SetSailTextures(groupNum, core.Event(\"GetSailTextureData\",                      \"l\", pACh->GetAttributeAsDword(\"index\",  -1)));   .... }<\/code><\/pre>\n<p>If <em>pACh<\/em> can never be null, you can remove the check:<\/p>\n<pre><code>void SAIL::SetAllSails(int groupNum) {   ....   SetSailTextures(groupNum, core.Event(\"GetSailTextureData\",                    \"l\", pACh->GetAttributeAsDword(\"index\",  -1)));   .... }<\/code><\/pre>\n<h4>new[] \u2013 delete Error<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v611\/\">V611<\/a> The memory was allocated using &#8216;new T[]&#8217; operator but was released using the &#8216;delete&#8217; operator. Consider inspecting this code. It&#8217;s probably better to use &#8216;delete [] pVSea;&#8217;. Check lines: 169, 191. SEA.cpp 169<\/p>\n<pre><code>struct CVECTOR {   public:     union {       struct       {         float x, y, z;       };       float v[3];   }; }; .... struct SeaVertex {   CVECTOR vPos;   CVECTOR vNormal;   float tu, tv; }; .... #define STORM_DELETE (x) { delete x; x = 0; }  void SEA::SFLB_CreateBuffers() {     ....     pVSea = new SeaVertex[NUM_VERTEXS]; } SEA::~SEA() { .... STORM_DELETE(pVSea); .... }<\/code><\/pre>\n<p>Using macros requires special care and experience. In this case a macro causes an error: the incorrect <em>delete<\/em> operator &#8212; instead of the correct <em>delete[]<\/em> operator &#8212; releases the memory that the <em>new[]<\/em> operator allocated. As a result, the code won&#8217;t call destructors for the <em>pVSea<\/em> array elements. In some cases, this won&#8217;t matter &#8212; for example, when all destructors of both array elements and their fields are trivial.<\/p>\n<p>However, if the error does not show up at runtime &#8212; it does not mean there isn&#8217;t one. The key here is how the new[] operator is defined. In some cases calling the <em>new[]<\/em> operator will allocate memory for the array, and will also write the memory section&#8217;s size and the number of elements at the beginning of the memory slot. If the developer then uses the <em>delete<\/em> operator that is incompatible with <em>new[]<\/em>, the delete operator is likely to misinterpret the information at the beginning of the memory block, and the result of such operation will be undefined. There is another possible scenario: memory for arrays and single elements is allocated from different memory pools. In that case, attempting to return memory allocated for arrays back to the pool that was intended for scalars will result in a crash.<\/p>\n<p>This error is dangerous, because it may not manifest itself for a long time, and then shoot you in the foot when you least expect it. The analyzer found a total of 15 errors of this type. Here are some of them:<\/p>\n<ul>\n<li>\n<p>V611 The memory was allocated using &#8216;new T[]&#8217; operator but was released using the &#8216;delete&#8217; operator. Consider inspecting this code. It&#8217;s probably better to use &#8216;delete [] m_pShowPlaces;&#8217;. Check lines: 421, 196. ActivePerkShower.cpp 421<\/p>\n<\/li>\n<li>\n<p>V611 The memory was allocated using &#8216;new T[]&#8217; operator but was released using the &#8216;delete&#8217; operator. Consider inspecting this code. It&#8217;s probably better to use &#8216;delete [] pTable;&#8217;. Check lines: 371, 372. AIFlowGraph.h 371<\/p>\n<\/li>\n<li>\n<p>V611 The memory was allocated using &#8216;new T[]&#8217; operator but was released using the &#8216;delete&#8217; operator. Consider inspecting this code. It&#8217;s probably better to use &#8216;delete [] vrt;&#8217;. Check lines: 33, 27. OctTree.cpp 33<\/p>\n<\/li>\n<li>\n<p>V611 The memory was allocated using &#8216;new T[]&#8217; operator but was released using the &#8216;delete&#8217; operator. Consider inspecting this code. It&#8217;s probably better to use &#8216;delete [] flist;&#8217;. Flag.cpp 738<\/p>\n<\/li>\n<li>\n<p>V611 The memory was allocated using &#8216;new T[]&#8217; operator but was released using the &#8216;delete&#8217; operator. Consider inspecting this code. It&#8217;s probably better to use &#8216;delete [] rlist;&#8217;. Rope.cpp 660<\/p>\n<\/li>\n<\/ul>\n<p>Analysis showed that many of the cases above involve the <em>STORM_DELETE<\/em> macro. However a simple change from <em>delete<\/em> to <em>delete[]<\/em> will lead to new errors, because the macro is also intended free the memory that the <em>new<\/em> operator allocated. To fix this code, add a new macro &#8212; <em>STORM_DELETE_ARRAY<\/em> &#8212; that uses the correct operator, *delete[]*.<\/p>\n<pre><code>struct CVECTOR .... struct SeaVertex {   CVECTOR vPos;   CVECTOR vNormal;   float tu, tv; }; .... #define STORM_DELETE (x) { delete x; x = 0; }  #define STORM_DELETE_ARRAY (x) { delete[] x; x = 0; }  void SEA::SFLB_CreateBuffers() {     ....     pVSea = new SeaVertex[NUM_VERTEXS]; } SEA::~SEA() { .... STORM_DELETE_ARRAY(pVSea); .... }<\/code><\/pre>\n<h4>A Double Assignment<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v519\/\">V519<\/a> The &#8216;h&#8217; variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 385, 389. Sharks.cpp 389<\/p>\n<pre><code>inline void Sharks::Shark::IslandCollision(....) {   if (h &lt; 1.0f)   {     h -= 100.0f \/ 150.0f;     if (h > 0.0f)     {       h *= 150.0f \/ 50.0f;     }     else       h = 0.0f;     h = 0.0f;     vx -= x * (1.0f - h);     vz -= z * (1.0f - h); }<\/code><\/pre>\n<p>Take a look at the <em>h &lt; 1.0f<\/em> expression in the code above. First, the developer calculates the <em>h<\/em> variable, and then sets it to <em>0<\/em>. As a result, the <em>h<\/em> variable is always <em>0<\/em>, which is an error. To fix the code, remove the <em>h<\/em> variable&#8217;s second assignment:<\/p>\n<pre><code>inline void Sharks::Shark::IslandCollision(....) {   if (h &lt; 1.0f)   {     h -= 100.0f \/ 150.0f;     if (h > 0.0f)     {       h *= 150.0f \/ 50.0f;     }     else       h = 0.0f;     vx -= x * (1.0f - h);     vz -= z * (1.0f - h); }<\/code><\/pre>\n<h4>Dereferencing a Pointer from realloc or malloc Function<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v522\/\">V522<\/a> There might be dereferencing of a potential null pointer &#8216;pTable&#8217;. Check lines: 36, 35. s_postevents.h 36<\/p>\n<pre><code>void Add(....) {   ....   pTable = (S_EVENTMSG **)realloc(                          pTable, nClassesNum * sizeof(S_EVENTMSG *));   pTable[n] = pClass;   .... };<\/code><\/pre>\n<p>When there&#8217;s a lack of memory, the <em>realloc<\/em> function fails to extend a memory block to the required size and returns <em>NULL<\/em>. Then the <em>pTable[n]<\/em> expression attempts to dereference this null pointer and causes undefined behavior. Moreover, the <em>pTable<\/em> pointer is rewritten, which is why the address of the original memory block may be lost. To fix this error, add a check and use an additional pointer:<\/p>\n<pre><code>void Add(....) {   ....   S_EVENTMSG ** newpTable      = (S_EVENTMSG **)realloc(pTable,                               nClassesNum * sizeof(S_EVENTMSG *));   if(newpTable)    {     pTable = newpTable;     pTable[n] = pClass;     ....   }   else   {   \/\/ Handle the scenario of realloc failing to reallocate memory   } };<\/code><\/pre>\n<p>PVS-Studio found similar errors in scenarios that involve the <em>malloc<\/em> function:<\/p>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v522\/\">V522<\/a> There might be dereferencing of a potential null pointer &#8216;label&#8217;. Check lines: 116, 113. geom_static.cpp 116<\/p>\n<pre><code>GEOM::GEOM(....) : srv(_srv) {   ....   label = static_cast&lt;LABEL *>(srv.malloc(sizeof(LABEL) *                                rhead.nlabels));   for (long lb = 0; lb &lt; rhead.nlabels; lb++)   {     label[lb].flags = lab[lb].flags;     label[lb].name = &amp;globname[lab[lb].name];     label[lb].group_name = &amp;globname[lab[lb].group_name];     memcpy(&amp;label[lb].m[0][0], &amp;lab[lb].m[0][0],             sizeof(lab[lb].m));     memcpy(&amp;label[lb].bones[0], &amp;lab[lb].bones[0],            sizeof(lab[lb].bones));     memcpy(&amp;label[lb].weight[0], &amp;lab[lb].weight[0],             sizeof(lab[lb].weight));   } }<\/code><\/pre>\n<p>This code needs an additional check:<\/p>\n<pre><code>GEOM::GEOM(....) : srv(_srv) {   ....   label = static_cast&lt;LABEL *>(srv.malloc(sizeof(LABEL) *                                rhead.nlabels));   for (long lb = 0; lb &lt; rhead.nlabels; lb++)   {     if(label)     {       label[lb].flags = lab[lb].flags;       label[lb].name = &amp;globname[lab[lb].name];       label[lb].group_name = &amp;globname[lab[lb].group_name];       memcpy(&amp;label[lb].m[0][0], &amp;lab[lb].m[0][0],                sizeof(lab[lb].m));       memcpy(&amp;label[lb].bones[0], &amp;lab[lb].bones[0],              sizeof(lab[lb].bones));       memcpy(&amp;label[lb].weight[0], &amp;lab[lb].weight[0],               sizeof(lab[lb].weight));     }   ....   } }<\/code><\/pre>\n<p>Overall, the analyzer found 18 errors of this type.<\/p>\n<p>Wondering what these errors can lead to and why you should avoid them? See <a href=\"https:\/\/pvs-studio.com\/en\/blog\/posts\/cpp\/0558\/\">this article<\/a> for answers. <\/p>\n<h4>Modulo 1 Remainder<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v1063\/\">V1063<\/a> The modulo by 1 operation is meaningless. The result will always be zero. WdmSea.cpp 205<\/p>\n<pre><code>void WdmSea::Update(float dltTime) {   long whiteHorses[1];   ....   wh[i].textureIndex = rand() % (sizeof(whiteHorses) \/ sizeof(long)); }<\/code><\/pre>\n<p>In the code above, the developer calculated the <em>whiteHorses<\/em> array&#8217;s size and applied the modulo operation to the size value. Since the array size <em>equals<\/em> 1, the result of this modulo operation is always <em>0<\/em>. Therefore, the operation does not make sense. The author may have made a mistake when declaring the <em>whiteHorses<\/em> variable &#8212; the array&#8217;s size needed to be different. There is also a chance that there&#8217;s no mistake here and the *rand() % (sizeof(whiteHorses) \/ sizeof(long)) *expression accommodates some future scenario. This code also makes sense if the <em>whiteHorses<\/em> array size is expected to change in the future and there will be a need to generate a random element&#8217;s index. Whether the developer wrote this code on purpose or by accident, it&#8217;s a good idea to take a look and recheck &#8212; and that&#8217;s exactly what the analyzer calls for.<\/p>\n<h4>std::vector vs std::deque<\/h4>\n<p>Aside from detecting obvious errors and inaccuracies in code, the PVS-Studio analyzer helps optimize code.<\/p>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v826\/\">V826<\/a> Consider replacing the &#8216;aLightsSort&#8217; std::vector with std::deque. Overall efficiency of operations will increase. Lights.cpp 471<\/p>\n<pre><code>void Lights::SetCharacterLights(....) {   std::vector&lt;long> aLightsSort;   for (i = 0; i &lt; numLights; i++)     aLightsSort.push_back(i);   for (i = 0; i &lt; aMovingLight.size(); i++)   {     const auto it = std::find(aLightsSort.begin(),aLightsSort.end(),                                aMovingLight[i].light);     aLightsSort.insert(aLightsSort.begin(), aMovingLight[i].light);   } }<\/code><\/pre>\n<p>The code above initializes <em>std::vector<\/em> <em>aLightsSort<\/em>, and then inserts elements at its beginning.<\/p>\n<p>Why is it a bad idea to insert many elements at the beginning of <em>std::vector<\/em>? Because each insertion causes the vector&#8217;s buffer reallocation. Each time a new buffer is allocated, the program fills in the inserted value and copies the values from the old buffer. Why don&#8217;t we just simply write a new value before the old buffer&#8217;s zeroth element? Because <em>std::vector<\/em> does not know how to do this.<\/p>\n<p>However, <em>std::deque<\/em> does. This container&#8217;s buffer is implemented as a circular buffer. This allows you  to add and remove elements at the beginning or at the end without the need to copy the elements. We can insert elements into <em>std::deque<\/em> exactly how we want &#8212; just add a new value before the zero element.<\/p>\n<p>This is why this code requires replacing *std::vector *with <em>std::deque<\/em>:<\/p>\n<pre><code>void Lights::SetCharacterLights(....) {   std::deque&lt;long> aLightsSort;   for (i = 0; i &lt; numLights; i++)     aLightsSort.push_back(i);   for (i = 0; i &lt; aMovingLight.size(); i++)   {     const auto it = std::find(aLightsSort.begin(),aLightsSort.end(),                                aMovingLight[i].light);     aLightsSort.push_front(aMovingLight[i].light);   } }<\/code><\/pre>\n<h3>Conclusion<\/h3>\n<p>PVS-Studio found that the Storm Engine source code contains many errors and code fragments that need revision. Many warnings pointed to code the developers had already tagged as needing revision. These errors may have been detected by static analysis tools or during code review. Other warnings pointed to errors not marked with comments. This means, the developers hadn&#8217;t suspected anything wrong there. All errors I&#8217;ve examined earlier in the article were from this list. If Storm Engine and its errors intrigued you, you can undertake my journey by yourself. I also invite you to take a look at <a href=\"http:\/\/www.pvs-studio.com\/en\/inspections\/\">these select articles about projects whose source code we checked<\/a> &#8212; there my colleagues discuss the analysis results and errors.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/564698\/\"> https:\/\/habr.com\/ru\/articles\/564698\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>PVS-Studio is a static analysis tool that helps find errors in software source code. This time PVS-Studio looked for bugs in Storm Engine&#8217;s source code.<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h3>Storm Engine<\/h3>\n<p>Storm Engine is a gaming engine that Akella has been developing since January 2000, for the Sea Dogs game series. The game engine became open-source on March 26th, 2021. The source code is available on <a href=\"https:\/\/github.com\/storm-devs\/storm-engine\">GitHub<\/a> under the GPLv3 license. Storm Engine is written in C++.<\/p>\n<p>In total, PVS-Studio issued 235 high-level warnings and 794 medium-level warnings. Many of these warnings point to bugs that may cause undefined behavior. Other warnings reveal logical errors &#8212; the program runs well, but the execution&#8217;s result may be not what&#8217;s expected.<\/p>\n<p>Examining each of the 1029 errors PVS-Studio discovered &#8212; especially those that involve the project&#8217;s architecture &#8212; would take up an entire book that is difficult to write and read. In this article, I&#8217;ll review more obvious and on-the-surface-type errors that do not require delving deep into the project&#8217;s source code.<\/p>\n<h3>Detected Errors<\/h3>\n<h4>Redundant Checks<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v547\/\">V547<\/a> Expression &#8216;nStringCode >= 0xffffff&#8217; is always false. dstring_codec. h 84<\/p>\n<pre><code>#define DHASH_SINGLESYM 255 .... uint32_t Convert(const char *pString, ....) {   uint32_t nStringCode;   ....   nStringCode = ((((unsigned char)pString[0]) &lt;&lt; 8) &amp; 0xffffff00) |                   (DHASH_SINGLESYM)   ....   if (nStringCode >= 0xffffff)   {     __debugbreak();   }   return nStringCode; }<\/code><\/pre>\n<p>Let&#8217;s evaluate the expression that the <em>nStringCode<\/em> variable contains. The <em>unsigned<\/em> <em>char<\/em> type takes values in the range of *[0,255]*. Consequently, <em>(unsigned char)pString[0]<\/em> is always less than <em>2^8<\/em>. After left shifting the result by <em>8<\/em>, we get a number that does not exceed <em>2^16<\/em>. The &#8216;&amp;&#8217; operator does not augment this value. Then we increase the expression&#8217;s value by no more than <em>255<\/em>. As a result, the <em>nStringCode<\/em> variable&#8217;s value never exceeds <em>2^16+256<\/em>, and therefore, is always less than <em>0xffffff = 2^24-1<\/em>. Thus, the check is always false and is of no use. At first glance, it would seem that we can safely remove it:<\/p>\n<pre><code>#define DHASH_SINGLESYM 255 .... uint32_t Convert(const char *pString, ....) {   uint32_t nStringCode;   ....   nStringCode = ((((unsigned char)pString[0]) &lt;&lt; 8) &amp; 0xffffff00) |                 (DHASH_SINGLESYM) ....   return nStringCode; }<\/code><\/pre>\n<p>But let&#8217;s not rush into anything. Obviously, the check is here for a reason. The developers may have expected the expression or the <em>DHASH_SINGLESYM<\/em> constant to change in the future. This example demonstrates a case when the analyzer is technically correct, but the code fragment that triggered the warning might not require fixing.<\/p>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v560\/\">V560<\/a> A part of conditional expression is always true: 0x00 &lt;= c. utf8.h 187<\/p>\n<pre><code>inline bool IsValidUtf8(....) {   int c, i, ix, n, j;   for (i = 0, ix = str.length(); i &lt; ix; i++)s   {     c = (unsigned char)str[i];     if (0x00 &lt;= c &amp;&amp; c &lt;= 0x7f)       n = 0;     ....   }   .... }<\/code><\/pre>\n<p>The <em>c<\/em> variable holds an unsigned type value and the <em>0x00 &lt;= c<\/em> check can be removed as unnecessary. The fixed code:<\/p>\n<pre><code>inline bool IsValidUtf8(....) {   int c, i, ix, n, j;   for (i = 0, ix = str.length(); i &lt; ix; i++)s   {     c = (unsigned char)str[i];     if (c &lt;= 0x7f)       n = 0;     ....   }   .... }<\/code><\/pre>\n<h4>Reaching Outside Array Bounds<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v557\/\">V557<\/a> Array overrun is possible. The value of &#8216;TempLong2 &#8212; TempLong1 + 1&#8217; index could reach 520. internal_functions.cpp 1131<\/p>\n<pre><code>DATA *COMPILER::BC_CallIntFunction(....) {   if (TempLong2 - TempLong1 >= sizeof(Message_string))   {     SetError(\"internal: buffer too small\");     pV = SStack.Push();     pV->Set(\"\");     pVResult = pV;     return pV;   }   memcpy(Message_string, pChar + TempLong1,           TempLong2 - TempLong1 + 1);   Message_string[TempLong2 - TempLong1 + 1] = 0;   pV = SStack.Push(); }<\/code><\/pre>\n<p>Here the analyzer helped find the off-by-one error.<\/p>\n<p>The function above first makes sure that the <em>TempLong2 &#8212; TempLong1<\/em> value is less than the <em>Message_string<\/em> length. Then the <em>Message_string[TempLong2 &#8212; TempLong1 + 1]<\/em> element takes the 0 value. Note that if <em>TempLong2 &#8212; TempLong1 + 1 == sizeof(Message_string)<\/em>, the check is successful and the internal error is not generated. However, the <em>Message_string[TempLong2 &#8212; TempLong1 + 1]<\/em> element is of bounds. When this element is assigned a value, the function accesses unreserved memory. This causes undefined behavior. You can fix the check as follows:<\/p>\n<pre><code>DATA *COMPILER::BC_CallIntFunction(....) {   if (TempLong2 - TempLong1 + 1 >= sizeof(Message_string))   {     SetError(\"internal: buffer too small\");     pV = SStack.Push();     pV->Set(\"\");     pVResult = pV;     return pV;   }   memcpy(Message_string, pChar + TempLong1,           TempLong2 - TempLong1 + 1);   Message_string[TempLong2 - TempLong1 + 1] = 0;   pV = SStack.Push(); }<\/code><\/pre>\n<h4>Assigning a Variable to Itself<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v570\/\">V570<\/a> The &#8216;Data_num&#8217; variable is assigned to itself. s_stack.cpp 36<\/p>\n<pre><code>uint32_t Data_num; .... DATA *S_STACK::Push(....) {   if (Data_num > 1000)   {     Data_num = Data_num;   }   .... }<\/code><\/pre>\n<p>Someone may have written this code for debugging purposes and then forgot to remove it. Instead of a new value, the <em>Data_num<\/em> variable receives its own value. It is difficult to say what the developer wanted to assign here. I suppose <em>Data_num<\/em> should have received a value from a different variable with a similar name, but the names got mixed up. Alternatively, the developer may have intended to limit the <em>Data_num<\/em> value to the 1000 constant but made a typo. In any case there&#8217;s a mistake here that needs to be fixed.<\/p>\n<h4>Dereferencing a Null Pointer<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v595\/\">V595<\/a> The &#8216;rs&#8217; pointer was utilized before it was verified against nullptr. Check lines: 163, 164. Fader.cpp 163<\/p>\n<pre><code>uint64_t Fader::ProcessMessage(....) {   ....   textureID = rs->TextureCreate(_name);   if (rs)   {     rs->SetProgressImage(_name);     .... }<\/code><\/pre>\n<p>In the code above, the <em>rs<\/em> pointer is first dereferenced, and then evaluated against <em>nullptr<\/em>. If the pointer equals <em>nullptr<\/em>, the null pointer&#8217;s dereference causes undefined behavior. If this scenario is possible, it is necessary to place the check before the first dereference:<\/p>\n<pre><code>uint64_t Fader::ProcessMessage(....) {   ....   if (rs)   {     textureID = rs->TextureCreate(_name);     rs->SetProgressImage(_name);     .... }<\/code><\/pre>\n<p>If the scenario guarantees that <em>rs != nullptr<\/em> is always true, then you can remove the unnecessary <em>if (rs)<\/em> check:<\/p>\n<pre><code>uint64_t Fader::ProcessMessage(....) {   ....   textureID = rs->TextureCreate(_name);   rs->SetProgressImage(_name);   .... }<\/code><\/pre>\n<p>There&#8217;s also a third possible scenario. Someone could have intended to check the <em>textureID<\/em> variable.<\/p>\n<p>Overall, I encountered 14 of the V595 warnings in the project.<\/p>\n<p>If you are curious, <a href=\"https:\/\/pvs-studio.com\/en\/pvs-studio\/download\/\">download and start PVS-Studio<\/a>, analyze the project and review these warnings. Here I&#8217;ll limit myself to one more example:<\/p>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v595\/\">V595<\/a> The &#8216;pACh&#8217; pointer was utilized before it was verified against nullptr. Check lines: 1214, 1215. sail.cpp 1214<\/p>\n<pre><code>void SAIL::SetAllSails(int groupNum) {   ....   SetSailTextures(groupNum, core.Event(\"GetSailTextureData\",                   \"l\", pACh->GetAttributeAsDword(\"index\",  -1)));   if (pACh != nullptr){   .... }<\/code><\/pre>\n<p>When calculating the <em>Event<\/em> method&#8217;s arguments, the author dereferences the <em>pACh<\/em> pointer. Then, in the next line, the <em>pACh<\/em> pointer is checked against <em>nullptr<\/em>. If the pointer can take the null value, the if-statement that checks <em>pACh<\/em> for <em>nullptr<\/em> must come before the <em>SetSailTextures<\/em> function call that prompts pointer dereferencing.<\/p>\n<pre><code>void SAIL::SetAllSails(int groupNum) {   ....   if (pACh != nullptr){     SetSailTextures(groupNum, core.Event(\"GetSailTextureData\",                      \"l\", pACh->GetAttributeAsDword(\"index\",  -1)));   .... }<\/code><\/pre>\n<p>If <em>pACh<\/em> can never be null, you can remove the check:<\/p>\n<pre><code>void SAIL::SetAllSails(int groupNum) {   ....   SetSailTextures(groupNum, core.Event(\"GetSailTextureData\",                    \"l\", pACh->GetAttributeAsDword(\"index\",  -1)));   .... }<\/code><\/pre>\n<h4>new[] \u2013 delete Error<\/h4>\n<p>PVS-Studio warns: <a href=\"https:\/\/pvs-studio.com\/en\/w\/v611\/\">V611<\/a> The memory was allocated using &#8216;new T[]&#8217; operator but was released using the &#8216;delete&#8217; operator. Consider inspecting this code. It&#8217;s probably better to use &#8216;delete [] pVSea;&#8217;. Check lines: 169, 191. SEA.cpp 169<\/p>\n<pre><code>struct CVECTOR {   public:     union {       struct       {         float x, y, z;       };       float v[3];   }; }; .... struct SeaVertex {   CVECTOR vPos;   CVECTOR vNormal;   float tu, tv; }; .... #define STORM_DELETE (x) { delete x; x = 0; }  void SEA::SFLB_CreateBuffers() {     ....     pVSea = new SeaVertex[NUM_VERTEXS]; } SEA::~SEA() { .... STORM_DELETE(pVSea); .... }<\/code><\/pre>\n<p>Using macros requires special care and experience. In this case a macro causes an error: the incorrect <em>delete<\/em> operator &#8212; instead of the correct <em>delete[]<\/em> operator &#8212; releases the memory that the <em>new[]<\/em> operator allocated. As a result, the code won&#8217;t call destructors for the <em>pVSea<\/em> array elements. In some cases, this won&#8217;t matter &#8212; for example, when all destructors of both array elements and their fields are trivial.<\/p>\n<p>However, if the error does not show up at runtime &#8212; it does not mean there isn&#8217;t one. The key here is how the new[] operator is defined. In some cases calling the <em>new[]<\/em> operator will allocate memory for the array, and will also write the memory section&#8217;s size and the number of elements at the beginning of the memory slot. If the developer then uses the <em>delete<\/em> operator that is incompatible with <em>new[]<\/em>, the delete operator is likely to misinterpret the information at the beginning of the memory block, and the result of such operation will be undefined. There is another possible scenario: memory for arrays and single elements is allocated from different memory pools. In that case, attempting to return memory allocated for arrays back to the pool that was intended for scalars will result in a crash.<\/p>\n<p>This error is dangerous, because it may not manifest itself for a long time, and then shoot you in the foot when you least expect it. The analyzer found a total of 15 errors of this type. Here are some of them:<\/p>\n<ul>\n<li>\n<p>V611 The memory was allocated using<\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-381104","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/381104","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=381104"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/381104\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=381104"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=381104"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=381104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}