Bugs From the 90's: The Code of the Command & Conquer Game

Written by pvs-studio | Published 2020/10/12
Tech Story Tags: cpp | cpp-vulnerabilities | gamedev | game-development | c++ | gaming | gaming-industry | bugs

TLDR Electronic Arts released source code of Command & Conquer: Tiberian Dawn and Red Alert games. PVS-Studio analyzer was used to find errors in the code. The tool is designed to detect errors and potential vulnerabilities in programs written in C, C++, C#, and Java. All error examples will be given in a series of two articles to highlight errors in code. Error examples include copy-paste, array overrun, copy-possible errors and copy-overflow errors.via the TL;DR App

The American company Electronic Arts Inc (EA) has made the source code of the games Command & Conquer: Tiberian Dawn and Command & Conquer: Red Alert publicly available. This code should help the game community to develop mods and maps, create custom units, and customize the gameplay logic. We all now have a unique opportunity to plunge into the history of development, which is very different from the modern one.
Back then, there was no StackOverflow site, convenient code editors, or powerful compilers. Moreover, at that time, there were no static analyzers, and the first thing the community will face is hundreds of errors in the code. This is what the PVS-Studio team will help you with by pointing out the erroneous places.

Introduction

Command & Conquer is a series of computer games in the real-time strategy genre. The first game in the series was released in 1995. The Electronic Arts company acquired this game's development studio only in 1998.
Since then, several games and many mods have been released. The source code of the games was posted together with the release of the Command & Conquer Remastered collection.
The PVS-Studio analyzer was used to find errors in the code. The tool is designed to detect errors and potential vulnerabilities in the source code of programs, written in C, C++, C#, and Java.
Due to the large amount of problems found in the code, all error examples will be given in a series of two articles.

Typos and copy-paste

V501 There are identical sub-expressions to the left and to the right of the '||' operator: dest == 0 || dest == 0 CONQUER.CPP 5576
I'd like to start the review with the never ending copy-paste. The author hasn't checked the pointer for the source and checked the destination pointer twice, because they had copied the dest == NULLcheck and had forgotten to change the variable name.
V584 The 'Current' value is present on both sides of the '!=' operator. The expression is incorrect or it can be simplified. CREDITS.CPP 173
The analyzer found a meaningless comparison. I suppose there must have been something as follows:
but inattention has won.
The exact same code fragment was copied to another function:
• V584 The 'Current' value is present on both sides of the '!=' operator. The expression is incorrect or it can be simplified. CREDITS.CPP 246
V524 It is odd that the body of 'Mono_Y' function is fully equivalent to the body of 'Mono_X' function. MONOC.CPP 753
A larger piece of code that was copied with the consequences. You have to admit, that except using the analyzer, you won't be able notice that the Get_X function instead of Get_Ywas called from the Mono_Y function. The MonoClass class does have 2 functions that differ by one symbol. Most likely, we found a real error.
I found the identical piece of code below:
• V524 It is odd that the body of 'Mono_Y' function is equivalent to the body of 'Mono_X' function. MONOC.CPP 1083

Errors with arrays

V557 Array overrun is possible. The '9' index is pointing beyond array bound. FOOT.CPP 232
It seems that this is a debugging method, but the history doesn't record to what extent it could be detrimental for the developer's mental health. Here, the Path array consists of 9 elements, and all 13 of them are printed.
In total, 4 memory accesses outside the array boundary:
• V557 Array overrun is possible. The '9' index is pointing beyond array bound. FOOT.CPP 232
• V557 Array overrun is possible. The '10' index is pointing beyond array bound. FOOT.CPP 233
• V557 Array overrun is possible. The '11' index is pointing beyond array bound. FOOT.CPP 234
• V557 Array overrun is possible. The '12' index is pointing beyond array bound. FOOT.CPP 235
V557 Array underrun is possible. The value of '_SpillTable[index]' index could reach -1. COORD.CPP 149
At first glance, the example is complex, but it is easy to puzzle it out after a brief analysis.
The two-dimensional _MoveSpillage array is accessed by an index that is taken from the _SpillTable array. The array happens to contain negative values. Perhaps, access to data is organized according to a special formula and this is what the developer intended. Nonetheless, I'm not sure about that.
V512 A call of the 'sprintf' function will lead to overflow of the buffer '(char *) ptr'. SOUNDDLG.CPP 250
An attentive reader will wonder - why such a long string is saved in a buffer of 4 bytes? This is because the programmer thought that sizeof(100) would return something more (at least 100). However, the sizeof operator returns the size of the type and even never evaluates any expressions. The author should have just written the constant 100, or better yet, used named constants, or a different type for strings or a pointer.
V512 A call of the 'memset' function will lead to underflow of the buffer 'Buffer'. KEYBOARD.CPP 96
A buffer is cleared by 256 bytes, although the full size of the original buffer is 256*sizeof(unsigned short). Oops... goofed this one.
It can be also fixed as follows:
V557 Array overrun is possible. The 'QuantityB' function processes value '[0..86]'. Inspect the first argument. Check lines: 'HOUSE.H:928', 'CELL.CPP:2337'. HOUSE.H 928
There are a lot of global variables in the code and it is obvious that they are easy to get confused. The analyzer's warning about an array index out of bounds is issued at the point of accessing the BQuantity array by index. The array size is 84 elements. Algorithms for analyzing the data flow in the analyzer helped to find out that the index value comes from another function – Goodie_Check. There, a loop is executed with a final value of 86. Therefore, 12 bytes of "someone's" memory (3 int elements) are constantly being read in this place.
V575 The 'memset' function processes '0' elements. Inspect the third argument. DLLInterface.cpp 1103
In my opinion, I have repeatedly seen this error in modern projects. Programmers still confuse the 2nd and 3rd arguments of the memset function.
One more similar fragment:
V575 The 'memset' function processes '0' elements. Inspect the third argument. DLLInterface.cpp 1404

About null pointers

V522 Dereferencing of the null pointer 'list' might take place. DISPLAY.CPP 1062
An explicit access to a null pointer looks very strange. This place looks like the one with a typo and there are a few other places worth checking out:
• V522 Dereferencing of the null pointer 'list' might take place. DISPLAY.CPP 951
• V522 Dereferencing of the null pointer 'unitsptr' might take place. QUEUE.CPP 2362
• V522 Dereferencing of the null pointer 'unitsptr' might take place. QUEUE.CPP 2699
V595 The 'enemy' pointer was utilized before it was verified against nullptr. Check lines: 3689, 3695. TECHNO.CPP 3689
The enemy pointer is dereferenced, and then checked to make sure that it is nonnull. It is still a vital problem, dare I say it, for every open source project. I'm sure that in projects with closed code the situation is about the same, unless, of course, PVS-Studio is used ;-)

Incorrect casts

V551 The code under this 'case' label is unreachable. The '4109' value of the 'char' type is not in the range [-128; 127]. WINDOWS.CPP 547
This function handles the characters you enter. As you know, a 1-byte value is placed in the char type, and the number 4109 will never be there. So, this switch statement just contains an unreachable code branch.
Several such places were found:
• V551 The code under this 'case' label is unreachable. The '4105' value of the 'char' type is not in the range [-128; 127]. WINDOWS.CPP 584
• V551 The code under this 'case' label is unreachable. The '4123' value of the 'char' type is not in the range [-128; 127]. WINDOWS.CPP 628
V552 A bool type variable is being incremented: printedtext ++. Perhaps another variable should be incremented instead. ENDING.CPP 170
In this code fragment, the analyzer found the application of the increment operation to a variable of the bool type. This is correct code from the point of view of the language, but it looks very strange now. This operation is also marked as deprecated, starting from the C++17 standard.
In total, 2 such places were detected:
• V552 A bool type variable is being incremented: done ++. Perhaps another variable should be incremented instead. ENDING.CPP 187
V556 The values of different enum types are compared. Types: ImpactType, ResultType. AIRCRAFT.CPP 742
The programmer tied some logic to comparing the values of different enumerations. Technically, this works because numerical representations are compared. But such code often leads to logical errors. It is worth tweaking the code (of course, if this project is to be supported).
The entire list of warnings for this diagnostic looks like this:
• V556 The values of different enum types are compared: SoundEffectName[voc].Where == IN_JUV. DLLInterface.cpp 402
• V556 The values of different enum types are compared: SoundEffectName[voc].Where == IN_VAR. DLLInterface.cpp 405
• V556 The values of different enum types are compared: Map.Theater == CNC_THEATER_DESERT. Types: TheaterType, CnCTheaterType. DLLInterface.cpp 2805
• V556 The values of different enum types are compared. Types: ImpactType, ResultType. AIRCRAFT.CPP 4269
• V556 The values of different enum types are compared: SoundEffectName[voc].Where == IN_VAR. DLLInterface.cpp 429
V716 Suspicious type conversion in assign expression: 'HRESULT = BOOL'. GBUFFER.H 780.
This is a very old problem that is still relevant today. There are special macros for working with the HRESULT type. Casting to BOOL and visa versa isn't used for this type. These two data types are extremely similar to each other from the point of view of the language, but logically they are still incompatible. The implicit type casting operation that exists in the code doesn't make sense.
This and a few other places would be worth refactoring:
• V716 Suspicious type conversion in assign expression: 'HRESULT = BOOL'. GBUFFER.H 817
• V716 Suspicious type conversion in assign expression: 'HRESULT = BOOL'. GBUFFER.H 857
• V716 Suspicious type conversion in assign expression: 'HRESULT = BOOL'. GBUFFER.H 773
• V716 Suspicious type conversion in assign expression: 'HRESULT = BOOL'. GBUFFER.H 810
• V716 Suspicious type conversion in assign expression: 'HRESULT = BOOL'. GBUFFER.H 850
V610 Undefined behavior. Check the shift operator '<<'. The left operand '(~0)' is negative. MP.CPP 2410
Here, the negative number is shifted to the left, which is undefined behavior. A negative number is obtained from zero when using the inversion operator. Since the result of the operation is placed in the int type, the compiler uses it to store the value, whereas it is of the signed type.
In 2020, the compiler already finds this error as well:
Warning C26453: Arithmetic overflow: Left shift of a negative signed number is undefined behavior.
But compilers are not full-fledged static analyzers, because they solve other problems. So here is another example of undefined behavior that is only detected by PVS-Studio:
V610 Undefined behavior. Check the shift operator '<<'. The right operand ('(32 - bits_to_shift)' = [1..32]) is greater than or equal to the length in bits of the promoted left operand. MP.CPP 659.
The analyzer has found an unusual situation. The 32-bit number can be potentially shifted to the right for the number of bits, exceeding the available number. Here's how it works:
The UNITIZE constant has the value 32:
Thus, the value of the bits_to_shift variable will be zero for all bits values that are multiples of 32.
Therefore, in this code fragment:
32 digits will be shifted if 0 is subtracted from the constant 32.
List of all PVS-Studio warnings about shifts with undefined behavior:
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(~0)' is negative. TARGET.H 66
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 24) * 256) / 24)' is negative. ANIM.CPP 160
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 12) * 256) / 24)' is negative. BUILDING.CPP 4037
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 21) * 256) / 24)' is negative. DRIVE.CPP 2160
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 21) * 256) / 24)' is negative. DRIVE.CPP 2161
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 20) * 256) / 24)' is negative. DRIVE.CPP 2162
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 20) * 256) / 24)' is negative. DRIVE.CPP 2163
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 18) * 256) / 24)' is negative. DRIVE.CPP 2164
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 18) * 256) / 24)' is negative. DRIVE.CPP 2165
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 17) * 256) / 24)' is negative. DRIVE.CPP 2166
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 16) * 256) / 24)' is negative. DRIVE.CPP 2167
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 15) * 256) / 24)' is negative. DRIVE.CPP 2168
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 14) * 256) / 24)' is negative. DRIVE.CPP 2169
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 13) * 256) / 24)' is negative. DRIVE.CPP 2170
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 12) * 256) / 24)' is negative. DRIVE.CPP 2171
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 11) * 256) / 24)' is negative. DRIVE.CPP 2172
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 10) * 256) / 24)' is negative. DRIVE.CPP 2173
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 9) * 256) / 24)' is negative. DRIVE.CPP 2174
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 8) * 256) / 24)' is negative. DRIVE.CPP 2175
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 7) * 256) / 24)' is negative. DRIVE.CPP 2176
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 6) * 256) / 24)' is negative. DRIVE.CPP 2177
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 5) * 256) / 24)' is negative. DRIVE.CPP 2178
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 4) * 256) / 24)' is negative. DRIVE.CPP 2179
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 3) * 256) / 24)' is negative. DRIVE.CPP 2180
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 2) * 256) / 24)' is negative. DRIVE.CPP 2181
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 1) * 256) / 24)' is negative. DRIVE.CPP 2182
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(((- 5) * 256) / 24)' is negative. INFANTRY.CPP 2730
• V610 Undefined behavior. Check the shift operator '>>'. The right operand ('(32 - bits_to_shift)' = [1..32]) is greater than or equal to the length in bits of the promoted left operand. MP.CPP 743
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(~0)' is negative. RANDOM.CPP 102
• V610 Undefined behavior. Check the shift operator '<<'. The left operand '(~0L)' is negative. RANDOM.CPP 164

Conclusion

Let's hope that modern projects of Electronic Arts are of better quality. If not, we invite you to our site to download and try PVS-Studio on all projects.
Someone may object that cool successful games used to be made with this quality, and we can partially agree with this. On the other hand, we mustn't forget that competition in the development of programs and games has grown many times over the years. Expenses on development, support, and advertising have also increased. Consequently, fixing errors at later stages of development can lead to significant financial and reputational losses.
Follow our blog and don't miss the 2nd part of the review of this games series.

Written by pvs-studio | Search for bugs in C, C++, C#, Java on Windows, Linux, macOS. https://pvs-studio.com/
Published by HackerNoon on 2020/10/12