>
>
>
PVS-Studio Checks OpenMW: Not All is Fi…

Andrey Karpov
Articles: 674

PVS-Studio Checks OpenMW: Not All is Fine in the Morrowind Universe

I have checked the OpenMW project by PVS-Studio and written this tiny article. Too few bugs were found, but I had been asked to write about this check, so here you are.

OpenMW

OpenMW is an attempt to reconstruct the popular RPG Morrowind, a full-blown implementation of all of the game's specifics with open source code. To run OpenMW, you will need an original Morrowind disk.

The source code can be downloaded from https://code.google.com/p/openmw/

Suspicious fragments found

Fragment No. 1

std::string getUtf8(unsigned char c,
  ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding)
{
  ....
  conv[0xa2] = 0xf3;
  conv[0xa3] = 0xbf;
  conv[0xa4] = 0x0;
  conv[0xe1] = 0x8c;
  conv[0xe1] = 0x8c;   <<<<====
  conv[0xe3] = 0x0;
  ....
}

PVS-Studio diagnostic message: V519 The 'conv[0xe1]' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 103, 104. openmw fontloader.cpp 104

I guess it is a typo. It is the 0xe2 index that should be probably used in the marked line.

Fragment No. 2

enum Flags
{
  ....
  NoDuration = 0x4,
  ....
}

bool CastSpell::cast (const ESM::Ingredient* ingredient)
{
  ....
  if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
  ....
}

PVS-Studio diagnostic message: V564 The '&' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&&' operator. openmw spellcasting.cpp 717

Here we deal with a mistake related to operation precedence. At first, the (!magicEffect->mData.mFlags) statement is executed which evaluates either to 0 or 1. Then the statement 0 & 4 or 1 & 4 is executed. But it doesn't make any sense, and the code should most likely look as follows:

if ( ! (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) )

Fragment No. 3

void Clothing::blank()
{
  mData.mType = 0;
  mData.mWeight = 0;
  mData.mValue = 0;
  mData.mEnchant = 0;
  mParts.mParts.clear();
  mName.clear();
  mModel.clear();
  mIcon.clear();
  mIcon.clear();
  mEnchant.clear();
  mScript.clear();
}

PVS-Studio diagnostic message: V586 The 'clear' function is called twice for deallocation of the same resource. Check lines: 48, 49. components loadclot.cpp 49

The 'mIcon' object is cleared twice. The second clearing is redundant or something else should have been cleared instead.

Fragment No. 4

void Storage::loadDataFromStream(
  ContainerType& container, std::istream& stream)
{
  std::string line;
  while (!stream.eof())
  {
    std::getline( stream, line );
    ....
  }
  ....
}

PVS-Studio diagnostic message: V663 Infinite loop is possible. The 'cin.eof()' condition is insufficient to break from the loop. Consider adding the 'cin.fail()' function call to the conditional expression. components translation.cpp 45

When working with the 'std::istream' class, calling the 'eof()' function to terminate the loop is not enough. If a failure occurs when reading data, the call of the 'eof()' function will return 'false' all the time. To terminate the loop in this case, you need an additional check of the value returned by 'fail()'.

Fragment No. 5

class Factory
{
  ....
  bool getReadSourceCache() { return mReadSourceCache; }
  bool getWriteSourceCache() { return mReadSourceCache; }
  ....
  bool mReadSourceCache;
  bool mWriteSourceCache;
  ....
};

PVS-Studio diagnostic message: V524 It is odd that the body of 'getWriteSourceCache' function is fully equivalent to the body of 'getReadSourceCache' function. components factory.hpp 209

I guess the getWriteSourceCache() function should look like this:

bool getWriteSourceCache() { return mWriteSourceCache; }

Fragments No. 6, 7, 8

std::string rangeTypeLabel(int idx)
{
  const char* rangeTypeLabels [] = {
    "Self",
    "Touch",
    "Target"
  };
  if (idx >= 0 && idx <= 3)
    return rangeTypeLabels[idx];
  else
    return "Invalid";
}

PVS-Studio diagnostic message: V557 Array overrun is possible. The value of 'idx' index could reach 3. esmtool labels.cpp 502

Here we see an incorrect check of an array index. If the 'idx' variable equals 3, an array overrun will occur.

The correct code:

if (idx >= 0 && idx < 3)

A similar defect was found in two other fragments:

  • V557 Array overrun is possible. The value of 'idx' index could reach 143. esmtool labels.cpp 391
  • V557 Array overrun is possible. The value of 'idx' index could reach 27. esmtool labels.cpp 475

Fragment No. 9

enum UpperBodyCharacterState
{
  UpperCharState_Nothing,
  UpperCharState_EquipingWeap,
  UpperCharState_UnEquipingWeap,
  ....
};

bool CharacterController::updateWeaponState()
{
  ....
  if((weaptype != WeapType_None ||
      UpperCharState_UnEquipingWeap) && animPlaying)
  ....
}

PVS-Studio diagnostic message: V560 A part of conditional expression is always true: UpperCharState_UnEquipingWeap. openmw character.cpp 949

This condition is very strange. In its current form, it can be reduced to "if (animPlaying)". Something is obviously wrong with it.

Fragments No. 10, 11

void World::clear()
{
  mLocalScripts.clear();
  mPlayer->clear();
  ....
  if (mPlayer)
  ....
}

PVS-Studio diagnostic message: V595 The 'mPlayer' pointer was utilized before it was verified against nullptr. Check lines: 234, 245. openmw worldimp.cpp 234

Similar defect: V595 The 'mBody' pointer was utilized before it was verified against nullptr. Check lines: 95, 99. openmw physic.cpp 95

Fragment No. 12

void ExprParser::replaceBinaryOperands()
{
  ....
  if (t1==t2)
    mOperands.push_back (t1);
  else if (t1=='f' || t2=='f')
    mOperands.push_back ('f');
  else
    std::logic_error ("failed to determine result operand type");
}

PVS-Studio diagnostic message: V596 The object was created but it is not being used. The 'throw' keyword could be missing: throw logic_error(FOO); components exprparser.cpp 101

The keyword 'throw' is missing. The fixed code should look like this:

throw std::logic_error ("failed to determine result operand type");

Conclusion

Purchase a PVS-Studio for in your team, and you will save huge amount of time usually spent on eliminating typos and diverse bugs.