vendredi 2 juillet 2021

C++ (sometimes) segmentation fault when looping over nested map [duplicate]

I am parsing files that have a structure similar to JSON, where I have objects equivalent to Int, String, List (of objects) and Dictionary (keys are strings and values are objects), among others. In particular, I end up with nested lists and dictionaries.

The below code is allowing me to store the parsed data and sometimes retrieve it. I can retrieve nested lists (implemented as std::vector<Object>) without any problem, and I can retrieve dictionaries (implemented as std::map<std::string, Object>) if not nested in other dictionaries. When I have nested dictionaries, I sometimes end up with segmentation fault. On some occasion, it seems that the loop does not halt after reading the map's entries.

The below three files should allow to reproduce the problem.

I am mainly interested in understanding what is going wrong, rather than only having something that works.

  1. In the function
std::ostream &operator<<(std::ostream &Stream, const ObjDictionry &Dictionry);

iterating using for(auto Pair: Dictionry) seems to produce the problem more often than for(auto it=Dictionry.begin();it!=Dictionry.end();++it), and I would like to understand why they behave differently.

  1. In the file test.cc, Other is printed ok on its own, and Obj also printes ok if you change the code, for example, to Obj["3"]["Key"]=Value;. I would like to understand what is the issue with the current example.

  2. While trying to solve the problem, I have printer the address to map iterator it, it seems that it does not equal Dictionary.end() when it exhausts the map entries, and I would like to understand what is wrong with the code to result in this behaviour.

I would be grateful for answers and hints.

types.h

#include <cstring>
#include <iostream>
#include <map>
#include <string>
#include <vector>

struct Object;

enum Tags {
  DTInt,
  DTKey,
  DTList,
  DTDictionry,
};

typedef int ObjInt;
typedef std::string ObjKey;
typedef std::vector<Object> ObjList;
typedef std::map<ObjKey, Object> ObjDictionry;

union ObjData {
  ObjInt Int;
  ObjKey Key;
  ObjList List;
  ObjDictionry Dictionry;

  ObjData();

  ~ObjData();

  ObjData &operator=(const ObjData &Other);
};

struct Object {
  Tags Tag;
  ObjData Data;

  Object();
  Object(Tags Tag, ObjData Data) {}

  Object(const Object &Other);

  Object &operator=(const Object &Other);
  Object &operator=(const int &Other);
  Object &operator=(const std::string &Other);
  Object &operator=(const std::map<ObjKey, Object> &Other);
  Object &operator=(const std::vector<Object> &Other);

  Object &operator[](const std::string &Key);
};

extern std::ostream &operator<<(std::ostream &Stream, const Object &Obj);
extern std::ostream &operator<<(std::ostream &Stream, const ObjList &List);
extern std::ostream &operator<<(std::ostream &Stream,
                                const ObjDictionry &Dictionry);

types.cc

#include "types.h"

// Constructing the member of the biggest size
ObjData::ObjData() : Dictionry{} {};
// ObjData::ObjData() { memset(this, 0, sizeof(ObjData)); }

ObjData::~ObjData() {}

ObjData &ObjData::operator=(const ObjData &Other) {
  // FIXME: Shalow/Deep copy
  memcpy(this, &Other, sizeof(ObjData));
  return *this;
}

Object::Object() : Tag{}, Data{} {};
Object::Object(const Object &Other) {
  Tag = Other.Tag;
  Data = Other.Data;
}

Object &Object::operator=(const Object &Other) {
  Tag = Other.Tag;
  Data = Other.Data;
  return *this;
}
Object &Object::operator=(const int &Other) {
  Tag = DTInt;
  Data.Int = Other;
  return *this;
}
Object &Object::operator=(const std::string &Other) {
  Tag = DTKey;
  Data.Key = Other;
  return *this;
}
Object &Object::operator=(const std::map<ObjKey, Object> &Other) {
  Tag = DTDictionry;
  Data.Dictionry = Other;
  return *this;
}
Object &Object::operator=(const std::vector<Object> &Other) {
  Tag = DTList;
  Data.List = Other;
  return *this;
}

Object &Object::operator[](const std::string &Key) {
  try {
    if (Tag != DTDictionry)
      throw(Tag);
    return Data.Dictionry[Key];
  } catch (int T) {
    std::cerr << "Object operator[] is called with the wrong tag `" << (int)Tag
              << "`\n";
  }
  // TODO decide what to return, the below return is temporarily to suppress the
  // warning
  return *this;
};

std::ostream &operator<<(std::ostream &Stream, const Object &Obj) {
  // TODO use it to create toJSON method
  switch (Obj.Tag) {

  case DTInt:
    Stream << Obj.Data.Int;
    break;

  case DTKey:
    Stream << Obj.Data.Key;
    break;

  case DTList:
    Stream << Obj.Data.List;
    break;

  case DTDictionry:
    Stream << Obj.Data.Dictionry;
    break;

  default:
    std::cerr << "Undefined `Obj.Tag=" << (int)Obj.Tag << "`.\n";
  }
  return Stream;
};

std::ostream &operator<<(std::ostream &Stream, const ObjList &List) {
  Stream << "[\n";
  for (Object Obj : List)
    Stream << Obj << ", \n";
  Stream << "]\n";

  return Stream;
};

std::ostream &operator<<(std::ostream &Stream, const ObjDictionry &Dictionry) {
  Stream << "{\n";
  // for (std::pair<ObjKey, Object> Pair : Dictionry) {
  //   Stream << Pair.first << ": " << Pair.second << ", \n";
  // }
  for (ObjDictionry::const_iterator it = Dictionry.begin();
       it != Dictionry.end(); ++it) {
    Stream << it->first << ": " << it->second << ", \n";
  }
  Stream << "}\n";
  return Stream;
};

test.cc

#include "types.h"

int main(int argc, char const *argv[]) {

  Object Obj{};
  Obj = ObjDictionry{};
  Obj["1"] = 1;
  Obj["2"] = ObjList{};
  Object Value{};
  Value = std::string("text");
  Obj["2"].Data.List.push_back(Value);
  Obj["2"].Data.List.push_back(Value);
  Obj["3"] = ObjDictionry{};
  Object Other{};
  Other = ObjDictionry{};
  Other["1"] = 1;
  Obj["3"]["Key"] = Other;

  std::cout << Other << '\n';
  std::cout << Obj << '\n';

  return 0;
}

I usually compile using Clang++, but g++ reproduces the problem.

clang++ test.cc types.cc -o test.o && ./test.o

Aucun commentaire:

Enregistrer un commentaire