In this article I would like to share the results of the verification with the PVS-Studio static analyzer of the open implementation of the World of Warcraft game server - CMaNGOS.
Introduction
C (ontinued) MaNGOS is an actively developing branch of the old project MaNGOS (Massive Network Game Object Server), designed to create a free alternative server for the game World of Warcraft. Most of the MaNGOS developers continue working in the CMaNGOS project.
As the developers themselves write, their goal is to create an open “well written server in C ++” for one of the best MMORPGs. I will try to help them a little with this, and check CMaNGOS with the help of the PVS-Studio static analyzer.
')
Note: The CMaNGOS-Classic server available in
the project repository on github was used for verification
.Test results
Error in the priority of operations
PVS-Studio warning : V593 Consider reviewing the expression A = B <C 'kind. The expression is calculated as the following: 'A = (B <C)'. SpellEffects.cpp 473
void Spell::EffectDummy(SpellEffectIndex eff_idx) { .... if (uint32 roll = urand(0, 99) < 3)
The author assumed that the variable
roll will be assigned a random value, and then a comparison of this value with
3 will occur. However, the priority of the comparison operation is higher than the priority of the assignment operation (see the
priority table of operations ), so in reality the random number will first be compared with
3 , and then the result of this comparison (
0 or
1 ) will be written to the
roll variable.
This error can be fixed this way:
uint32 roll = urand(0, 99); if (roll < 3) {
Same actions in if and else blocks
PVS-Studio warning : V523 The 'then' statement is equivalent to the 'else' statement. SpellAuras.cpp 1537
void Aura::HandleAuraModShapeshift(bool apply, bool Real) { switch (form) { case FORM_CAT: .... case FORM_TRAVEL: .... case FORM_AQUA: if (Player::TeamForRace(target->getRace()) == ALLIANCE) modelid = 2428;
In both blocks, the
modelid variable
is assigned the same value; most likely, this is an error, and the constant in one of the blocks needs to be replaced with some other one.
Indefinite behavior
PVS-Studio warning : V567 Undefined behavior. The m_uiMovePoint variable is variable boss_onyxia.cpp 405
void UpdateAI(const uint32 uiDiff) override { .... switch (urand(0, 2)) { case 0: .... case 1: {
In the specified line, the variable
m_uiMovePoint changes twice within one
point of the sequence , which leads to undefined program behavior. More details about this can be found in the diagnostic description
V567 .
Similar error:
- V567 Undefined behavior. The 'm_uiCrystalPosition' variable is modified while being used between sequence points. boss_ossirian.cpp 150
Error in condition
PVS-Studio warning : V547 Expression is always false. Probably the '||' operator should be used here. SpellEffects.cpp 2872
void Spell::EffectEnchantItemTmp(SpellEffectIndex eff_idx) { ....
In this condition, the equality of the variable
m_spellInfo-> Id to two different values ​​at the same time is checked. The result of such a check, of course, is always
false . Most likely, the author made a mistake and instead of the operator '||' used the operator '&&'.
It is noteworthy that the comments noted the strange behavior of this block of code and, perhaps, that it is just caused by this error.
The project found several more similar errors; here is a complete list of them:
- V547 Expression is always false. Probably the '||' operator should be used here. SpellEffects.cpp 2872
- V547 Expression is always true. Probably the '&&' operator should be used here. genrevision.cpp 261
- V547 Expression is always true. Probably the '&&' operator should be used here. vmapexport.cpp 361
- V547 Expression is always true. Probably the '&&' operator should be used here. MapTree.cpp 125
- V547 Expression is always true. Probably the '&&' operator should be used here. MapTree.cpp 234
Suspicious formatting
PVS-Studio Warning: V640 The code's operational logic does not correspond with its formatting. The statement is indented. It is possible that curly brackets are missing. instance_blackrock_depths.cpp 111
void instance_blackrock_depths::OnCreatureCreate(Creature* pCreature) { switch (pCreature->GetEntry()) { .... case NPC_HAMMERED_PATRON: .... if (m_auiEncounter[11] == DONE) pCreature->SetFactionTemporary(....); pCreature->SetStandState(UNIT_STAND_STATE_STAND);
Here, most likely, the author forgot to put curly braces after the
if statement , as a result of which the call to
pCreature-> SetStandState (UNIT_STAND_STATE_STAND) will be executed regardless of the condition in
if .
If this behavior was intended, then it is worth correcting the code alignment:
if (m_auiEncounter[11] == DONE) pCreature->SetFactionTemporary(....); pCreature->SetStandState(UNIT_STAND_STATE_STAND);
Identical Operands in the Ternary Operator
PVS-Studio warning : V583 The '?:' Operator, regardless of the conditional expression, always returns the same value: SAY_BELNISTRASZ_AGGRO_1. razorfen_downs.cpp 104
void AttackedBy(Unit* pAttacker) override { .... if (!m_bAggro) { DoScriptText(urand(0, 1) ? SAY_BELNISTRASZ_AGGRO_1 :
The second and third operands of the ternary operator are identical, most likely this is a mistake. Judging by the project code, one can assume that one of the operands should be
SAY_BELNISTRASZ_AGGRO_2 .
Integer division
PVS-Studio warning : V674 The '0.1f' literal of the 'float' type is compared to the value of the 'unsigned int' type. item_scripts.cpp 44
bool ItemUse_item_orb_of_draconic_energy(....) { ....
The
Unit :: GetHealth () method returns a value of type
uint32_t , and the
Unit :: GetMaxHealth () method also returns a value of type
uint32_t , so the result of their division is integer and it
makes no sense to compare it with
0.1f .
To correctly determine the 10% health limit, this code can be rewritten, for example, as follows:
Unconditional exit from the for loop
PVS-Studio warning : V612 An unconditional 'break' within a loop. Pet.cpp 1956
void Pet::InitPetCreateSpells() { .... for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx) { usedtrainpoints += _spell_idx->second->reqtrainpoints; break;
It is not quite clear what was meant here, but the unconditional
break statement in the body of the
for loop looks very suspicious. Even if there is no error, it is worth refactoring the code and getting rid of the unnecessary loop, because the
_spell_idx iterator takes one single value.
Similar warning:
- V612 An unconditional 'break' within a loop. Pet.cpp 895
Excess condition
PVS-Studio warning : V728 Alert check can be simplified. The '||' operator is surrounded by opposite expressions '! realtimeonly' and 'realtimeonly'. Player.cpp 10536
void Player::UpdateItemDuration(uint32 time, bool realtimeonly) { .... if ((realtimeonly && (....)) || !realtimeonly)
Type checking (
a &&
b ) || !
a can be simplified to!
a ||
b , which is clearly seen on the truth table:
Thus, the original expression is simplified to:
void Player::UpdateItemDuration(uint32 time, bool realtimeonly) { .... if (!(realtimeonly) || (....)) item->UpdateDuration(this, time); .... }
This check for null
PVS-Studio warning : V704 '! This ||! PVictim' expression should be avoided: 'this' pointer can never be NULL on newer compilers. Unit.cpp 1417
void Unit::CalculateSpellDamage(....) { .... if (!this || !pVictim)
According to the modern standard C ++, the
this pointer can never be null. Often, using
this with a null comparison can lead to unexpected errors. Read more about this in the diagnostic description of the
V704 .
Similar checks:
- V704 '! This ||! PVictim' expression should be avoided: 'this' pointer can never be NULL on newer compilers. Unit.cpp 1476
- V704 '! This ||! PVictim' expression should be avoided: 'this' pointer can never be NULL on newer compilers. Unit.cpp 1511
- V704 '! This ||! PVictim' expression should be avoided: 'this' pointer can never be NULL on newer compilers. Unit.cpp 1797
Unjustified transfer by reference
PVS-Studio warning : V669 The 'uiHealedAmount' argument is a non-constant reference. The analyzer is unable to determine where this argument is being modified. It is possible that the function contains an error. boss_twinemperors.cpp 109
void HealedBy(Unit* pHealer, uint32& uiHealedAmount) override
The variable
uiHealedAmount is passed by reference, but does not change in the function body. This can be misleading, because it seems that the function
HealedBy () is writing something to
uiHealedAmount . It is better to pass a variable by a constant reference or by value.
Reassignment
PVS-Studio warning : V519 The 'stat' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 1776, 1781. DetourNavMeshQuery.cpp 1781
dtStatus dtNavMeshQuery::findStraightPath(....) const { .... if (....) { stat = appendPortals(apexIndex, i, closestEndPos,
The analyzer detected a suspicious place where the
stat variable is assigned two different values ​​two times in a row. Definitely worth checking out this code section for errors.
Check pointer to null after new
PVS-Studio warning : V668 pm mer mer mer mer mer pointer pointer pointer pointer pointer allocated... The exception will be generated in the case of memory allocation error. MapBuilder.cpp 553
void MapBuilder::buildMoveMapTile(....) { .... rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP]; if (!pmmerge)
Checking a pointer to zero after using the
new operator is meaningless. If memory cannot be allocated, the
new operator throws an exception
std :: bad_alloc () , and does not return
nullptr . Accordingly, the program will never enter the block after the condition.
To correct this error, you can make a memory allocation in a
try {....} catch (const std :: bad_alloc &) {....} block , or use the
new (std :: nothrow) construction , which will not generate an exception in case of failure.
Similar pointer tests:
- V668 allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated The exception will be generated in the case of memory allocation error. loadlib.cpp 36
- V668 has been set using the 'new' operator. The exception will be generated in the case of memory allocation error. MapBuilder.cpp 560
- V668 using _ allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated The exception will be generated in the case of memory allocation error. WorldSocket.cpp 426
Wrong order of arguments
PVS-Studio warning : V764. Possible incorrect order of arguments passed to 'loadVMap' function: 'tileY' and 'tileX'. MapBuilder.cpp 279
void MapBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh, uint32 curTile, uint32 tileCount) { ....
The analyzer detected a suspicious transfer of arguments to a function — the
tileX and
tileY arguments were mixed up.
If you look at the prototype of the
loadVMap () function, it becomes obvious that this is really a mistake.
bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData);
Two identical code blocks
PVS-Studio warning : V760 Two blocks of text were found. The second block begins from line 213. BattleGround.cpp 210
BattleGround::BattleGround() : m_BuffChange(false), m_StartDelayTime(0), m_startMaxDist(0) { .... m_TeamStartLocO[TEAM_INDEX_ALLIANCE] = 0; m_TeamStartLocO[TEAM_INDEX_HORDE] = 0; m_BgRaids[TEAM_INDEX_ALLIANCE] = nullptr; m_BgRaids[TEAM_INDEX_HORDE] = nullptr; m_PlayersCount[TEAM_INDEX_ALLIANCE] = 0;
Here the same actions are performed twice in a row. Most likely, such code appeared as a result of Copy-Paste.
Duplicate condition
PVS-Studio warning : V571 Recurring check. The 'isDirectory' condition was already verified in line 166. FileSystem.cpp 169
FileSystem::Dir& FileSystem::getContents(const std::string& path, bool forceUpdate) {
The
isDirectory condition is checked twice. You can remove duplicate checks.
Bitwise And with a zero constant
PVS-Studio warning : V616 The 'SPELL_DAMAGE_CLASS_NONE' is not used in the bitwise operation. Spell.cpp 674
void Spell::prepareDataForTriggerSystem() { .... if (IsPositiveSpell(m_spellInfo->Id)) { if (m_spellInfo->DmgClass & SPELL_DAMAGE_CLASS_NONE)
The constant
SPELL_DAMAGE_CLASS_NONE has a zero value, and the bitwise AND of any number and zero is zero, hence the condition will always be
false , and the block following it will never be executed.
Similar error:
- V616 The 'SPELL_DAMAGE_CLASS_NONE' named after it is used in the bitwise operation. Spell.cpp 692
Potential null pointer dereference
PVS-Studio warning : V595 The 'model' pointer was used before it was verified against nullptr. Check lines: 303, 305. MapTree.cpp 303
bool StaticMapTree::InitMap(const std::string& fname, VMapManager2* vm) { .... WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name); model->setModelFlags(spawn.flags);
The
model pointer is checked for
null , i.e. its equality to zero is allowed, however, the above pointer is already used and without any checks. There is a potential null pointer dereference.
To correct this error, you should check the value of the
model pointer before calling the
model-> setModelFlags (spawn.flags) method .
Similar warnings:
- V595 Check lines: 374, 375. MapTree.cpp 374
- V595 Check lines: 272, 290. Object.cpp 272
- V595 The 'updateMask' pointer was used before it was verified against nullptr. Check lines: 351, 355. Object.cpp 351
- V595 The 'dbcEntry1' pointer was used before it was verified against nullptr. Check lines: 7123, 7128. ObjectMgr.cpp 7123
Conclusion
PVS-Studio as always found many suspicious places and errors in the code. I hope the CMaNGOS developers will correct all the shortcomings, as well as begin to constantly use static analysis in their project, since a one-time check is not so effective.
I also remind you that now anyone can take advantage of the
free use of PVS-Studio subject to the conditions described by the link.
PS You can offer to check with our analyzer any project that is interesting for you via the feedback form or using GitHub. Details on the
link .
If you want to share this article with an English-speaking audience, then please use the link to the translation: Bredikhin Egor.
Checking the World of Warcraft CMaNGOS open source server .