📜 ⬆️ ⬇️

Check the open-source server World of Warcraft CMaNGOS

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) // <= .... else if (roll < 6) .... else if (roll < 9) .... .... } 

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; // <= else 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: { // C++ is stupid, so add -1 with +7 m_uiMovePoint += NUM_MOVE_POINT - 1; m_uiMovePoint %= NUM_MOVE_POINT; break; } case 2: ++m_uiMovePoint %= NUM_MOVE_POINT; // <= break; } .... } 

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:


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) { .... // TODO: Strange stuff in following code // shaman family enchantments if (....) duration = 300; else if (m_spellInfo->SpellIconID == 241 && m_spellInfo->Id != 7434) duration = 3600; else if (m_spellInfo->Id == 28891 && m_spellInfo->Id == 28898) // <= duration = 3600; .... } 

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:


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); // <= break; case NPC_PRIVATE_ROCKNOT: case NPC_MISTRESS_NAGMARA: .... } } 

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 : // <= SAY_BELNISTRASZ_AGGRO_1, // <= m_creature, pAttacker); m_bAggro = true; } .... } 

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(....) { .... // If Emberstrife is already mind controled or above 10% HP: // force spell cast failure if (pEmberstrife && pEmberstrife->HasAura(SPELL_DOMINION_SOUL) || pEmberstrife->GetHealth() / pEmberstrife->GetMaxHealth() > 0.1f) // <= { .... return true; } return false; } 

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:

 // If Emberstrife is already mind controled or above 10% HP: // force spell cast failure if (pEmberstrife && pEmberstrife->HasAura(SPELL_DOMINION_SOUL) || ((float)pEmberstrife->GetHealth()) / ((float)pEmberstrife->GetMaxHealth()) > 0.1f) { .... return true; } 

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:


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) // <= item->UpdateDuration(this, time); .... } 

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) // <= return; .... } 

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:


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 // <= { if (!m_pInstance) return; if (Creature* pTwin = m_pInstance->GetSingleCreatureFromStorage( m_creature->GetEntry() == NPC_VEKLOR ? NPC_VEKNILASH : NPC_VEKLOR)) { float fHealPercent = ((float)uiHealedAmount) / ((float)m_creature->GetMaxHealth()); uint32 uiTwinHeal = (uint32)(fHealPercent * ((float)pTwin->GetMaxHealth())); uint32 uiTwinHealth = pTwin->GetHealth() + uiTwinHeal; pTwin->SetHealth(uiTwinHealth < pTwin->GetMaxHealth() ? uiTwinHealth : pTwin->GetMaxHealth()); } } 

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, // <= path, straightPath, straightPathFlags, straightPathRefs, straightPathCount, maxStraightPath, options); } stat = appendVertex(closestEndPos, 0, path[i], // <= straightPath, straightPathFlags, straightPathRefs, straightPathCount, maxStraightPath); .... } 

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) // <= { printf("%s alloc pmmerge FIALED! \r", tileString); return; } .... } 

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:


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) { .... // get heightmap data m_terrainBuilder->loadMap(mapID, tileX, tileY, meshData); // get model data m_terrainBuilder->loadVMap(mapID, tileY, tileX, // <= meshData); .... } 

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; // <= m_PlayersCount[TEAM_INDEX_HORDE] = 0; // <= m_PlayersCount[TEAM_INDEX_ALLIANCE] = 0; // <= m_PlayersCount[TEAM_INDEX_HORDE] = 0; // <= m_TeamScores[TEAM_INDEX_ALLIANCE] = 0; m_TeamScores[TEAM_INDEX_HORDE] = 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) { // Does this path exist on the real filesystem? if (exists && isDirectory) // <= { // Is this path actually a directory? if (isDirectory) // <= { .... } .... } .... } 

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) // <= { m_procAttacker = PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS; m_procVictim = PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS; } } .... } 

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:


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); // <= .... if (model) // <= { .... } .... } 

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:


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 .

Read the article and have a question?
Often our articles are asked the same questions. We collected answers to them here: Answers to questions from readers of articles about PVS-Studio, version 2015 . Please review the list.

Source: https://habr.com/ru/post/321814/


All Articles