dimanche 19 juin 2022

Occasional SEGFAULT in my c++ code with flatbuffers

I'm working on my C++ project with flatbuffers. I started with google's online example and wrote a google test. However, this test sometimes failed with SEGFAULT.

Following are the code snippets.

// moster.fbs

namespace MyGame.Sample;

enum Color:byte { Red = 0, Green, Blue = 2 }

union Equipment { Weapon } // Optionally add more tables.

struct Vec3 {
  x:float;
  y:float;
  z:float;
}

table Monster {
  pos:Vec3; // Struct.
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];  // Vector of scalars.
  color:Color = Blue; // Enum.
  weapons:[Weapon];   // Vector of tables.
  equipped:Equipment; // Union.
  path:[Vec3];        // Vector of structs.
}

table Weapon {
  name:string;
  damage:short;
}

root_type Monster;
// test.cpp
#include <fstream>
#include <string>

#include "gtest/gtest.h"
#include "monster_generated.h"

using namespace MyGame::Sample;

void WriteMonsterToFile(const std::string& filename)
{
  // Build up a serialized buffer algorithmically:
  flatbuffers::FlatBufferBuilder builder;

  // First, lets serialize some weapons for the Monster: A 'sword' and an 'axe'.
  auto weapon_one_name = builder.CreateString("Sword");
  short weapon_one_damage = 3;

  auto weapon_two_name = builder.CreateString("Axe");
  short weapon_two_damage = 5;

  // Use the `CreateWeapon` shortcut to create Weapons with all fields set.
  auto sword = CreateWeapon(builder, weapon_one_name, weapon_one_damage);
  auto axe = CreateWeapon(builder, weapon_two_name, weapon_two_damage);

  // Create a FlatBuffer's `vector` from the `std::vector`.
  std::vector<flatbuffers::Offset<Weapon>> weapons_vector;
  weapons_vector.push_back(sword);
  weapons_vector.push_back(axe);
  auto weapons = builder.CreateVector(weapons_vector);

  // Second, serialize the rest of the objects needed by the Monster.
  auto position = Vec3(1.0f, 2.0f, 3.0f);

  auto name = builder.CreateString("MyMonster");

  unsigned char inv_data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  auto inventory = builder.CreateVector(inv_data, 10);

  // Shortcut for creating monster with all fields set:
  auto orc = CreateMonster(builder, &position, 150, 80, name, inventory,
                           Color_Red, weapons, Equipment_Weapon, axe.Union());

  builder.Finish(orc);  // Serialize the root of the object.

  // We now have a FlatBuffer we can store on disk or send over a network.
  std::ofstream outfile(filename, std::ios::binary);
  outfile.write((char*)builder.GetBufferPointer(), builder.GetSize());
  outfile.flush();
  outfile.close();
}

TEST(FlatbuffersTest, Monster)
{
  WriteMonsterToFile("monster.bin");

  // ** file/network code goes here :) **
  std::ifstream infile;
  infile.open("monster.bin", std::ios::binary | std::ios::in);
  infile.seekg(0, std::ios::end);
  int length = infile.tellg();
  infile.seekg(0, std::ios::beg);
  char* data = new char[length];
  infile.read(data, length);
  infile.close();

  // Get access to the root:
  auto monster = GetMonster(data);
  delete[] data;

  // Get and test some scalar types from the FlatBuffer.
  EXPECT_EQ(monster->hp(), 80);
  EXPECT_EQ(monster->mana(), 150);
  EXPECT_EQ(monster->name()->str(), "MyMonster");

  // Get and test a field of the FlatBuffer's `struct`.
  auto pos = monster->pos();
  EXPECT_EQ(pos->z(), 3.0f);

  // Get a test an element from the `inventory` FlatBuffer's `vector`.
  auto inv = monster->inventory();
  EXPECT_EQ(inv->Get(9), 9);

  // Get and test the `weapons` FlatBuffers's `vector`.
  std::string expected_weapon_names[] = {"Sword", "Axe"};
  short expected_weapon_damages[] = {3, 5};
  auto weps = monster->weapons();
  for (unsigned int i = 0; i < weps->size(); i++) {
    EXPECT_EQ(weps->Get(i)->name()->str(), expected_weapon_names[i]);
    EXPECT_EQ(weps->Get(i)->damage(), expected_weapon_damages[i]);
  }

  // Get and test the `Equipment` union (`equipped` field).
  EXPECT_EQ(monster->equipped_type(), Equipment_Weapon);
  auto equipped = static_cast<const Weapon*>(monster->equipped());
  EXPECT_EQ(equipped->name()->str(), "Axe");
  EXPECT_EQ(equipped->damage(), 5);
}

test output:

$ ctest --rerun-failed --output-on-failure
Test project /root/cpcos/build_linux64
    Start 3: FlatbuffersTest.Monster
1/1 Test #3: FlatbuffersTest.Monster ..........***Exception: SegFault  0.01 sec
Running main() from /home/conan/w/BuildSingleReference/.conan/data/gtest/1.11.0/_/_/build/7320405f83ec32d8556b524cdda87ee295bb7b84/source_subfolder/googletest/src/gtest_main.cc
Note: Google Test filter = FlatbuffersTest.Monster
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FlatbuffersTest
[ RUN      ] FlatbuffersTest.Monster
/root/cpcos/tests/flatbuffers_gtest.cpp:71: Failure
Expected equality of these values:
  monster->hp()
    Which is: 100
  80


0% tests passed, 1 tests failed out of 1

Total Test time (real) =   0.04 sec

The following tests FAILED:
          3 - FlatbuffersTest.Monster (SEGFAULT)
Errors while running CTest

I compiled the code and ran it many times, strangely, it sometimes PASS while sometimes failed with SEGFAULT.

What did I do wrong? Thanks.

Aucun commentaire:

Enregistrer un commentaire