Nested protobuf structure with repeated field leads to debug assertion failure

Viewed 1318

See update 2 for minimal example.

I'm trying to send data from one process to another using protobuf and TCP/IP. For this I created the following proto file:

syntax = "proto3";
option cc_enable_arenas = false;

message TCPMessage {
    enum Type {
        SETUP = 0;
        DATA = 1;
        START = 2;
        STOP = 3;
    }
    Type messageType = 1;
    oneof message {
        SetupMessage setupMessage = 2;
        DataMessage dataMessage = 3;
        StartMessage startMessage = 4;
        StopMessage stopMessage = 5;
    }
    uint64 timestamp = 6;
}

message StartMessage{
    bool diagnosticMode = 1;
}

message StopMessage{

}

message SetupMessage {
    repeated string entities = 1;
    repeated string objects = 2;
    repeated string commands = 3;
    repeated VariableDescription commandDescriptions = 4;
    repeated ProtoVariable initialState = 5;
}

message DataMessage {
    repeated ProtoVariable variables = 1;
    uint64 timeSpan = 2;
}


message VariableDescription {

    enum DataType {
        DOUBLE = 0;
        // FLOAT = 1;
        // INT32 = 2;
        INT64 = 3;
        // UINT32 = 4;
        // UINT64 = 5;
        // Reserved if ever needed
        // SINT32 = 6;
        // SINT64 = 7;
        // FIXED32 = 8;
        // FIXED64 = 9;
        // SFIXED32 = 10;
        // SFIXED64 = 11;
        BOOL = 12;
        STRING = 13;
        BYTES = 14;
    }

    string entity = 1;
    string name = 2;
    DataType dataType = 3;
    repeated uint64 dimensions = 4;
}

message ProtoVariable
{
    VariableDescription metaData = 1;
    bytes data = 2;
}

As you can see I'm using a nested message structure with repeated fields to send information of certain variables from one process to another. On the python side (receiving end) I have no problem with the code. All information is received as expected and everything works fine. On the C++ side, however, I'm running into Debug Assertion Problems caused by the deletion of a TCPMessage object.

When sending a TCPMessage filled with a StartMessage in the message field I have no problems, everything works as expected, however, when sending a DataMessage in the message field I run into the described problems.

First here is the code, how I create a StartMessage:

start_msg.set_messagetype(TCPMessage_Type_START);
StartMessage* tmp = new StartMessage();
start_msg.set_allocated_startmessage(tmp);
sendMessage(std::make_unique<TCPMessage>(start_msg))

As I said, this works. After reading online about the mutable_foo() method I created the following code for creating a DataMessage:

std::unique_ptr<TCPMessage> msg = std::make_unique<TCPMessage>();
msg->set_messagetype(TCPMessage::DATA);
DataMessage* data_msg = msg->mutable_datamessage();

auto var = data_msg->add_variables();
VariableDescription* meta_data = var->mutable_metadata();

meta_data->set_entity(entity);
meta_data->set_name(cmd_identifier);
meta_data->set_datatype(stored_meta_data.getType());
for (uint64_t i = 0; i < stored_meta_data.getDimensions().size(); i++) {
  meta_data->add_dimensions(stored_meta_data.getDimensions()[i]);
}

double val = 12345.6789;
char const* d = reinterpret_cast<char const*>(&val);

std::string* data_str = var->mutable_data();
for (int i = 0; i < 8; ++i) {
  data_str->operator+=(d[i]);
}
sendMessage(std::move(msg));

I know there is some weird code (see data_str->operator+=(d[i]) for example) this has to do with me trying to get this working in any way possible.

Lastly here is the code of the sendMessage(std::unique_ptr<TCPMessage> msg) method:

int TCPConnection::sendMessage(std::unique_ptr<TCPMessage>& msg)
{
  // bool a = msg->messagetype() == TCPMessage::DATA; // This was used for debugging (see further down)
  std::string out = "";
  msg->SerializeToString(&out);
  std::string message_len = "";
  for (int i = 0; i < 8; ++i) {
    message_len += char((int)(((uint64_t)out.size() >> (i * 8)) & 0xFF));
  }
  std::string out_buffer = "";
  size_t i = 0;
  for (; i < 8; ++i) {
    out_buffer += message_len[i];
  }
  size_t j = 0;
  for (; j < out.size(); ++j) {
    out_buffer += out[j];
  }
  int i_send_result = ::send(tcp_client_socket_, &out_buffer[0], out.size() + 8, 0);
  if (i_send_result == SOCKET_ERROR) {
    std::cout << "send failed with error: " << WSAGetLastError() << std::endl;
    closesocket(tcp_client_socket_);
    WSACleanup();
    return i_send_result;
  }
//  if (a) {
//    int o = 1;
//    msg->~TCPMessage(); // Here I was figuring out, that the debug assertion happens in ~DataMessage()
                          // in the RepeatedPtrField<Element>::~RepeatedPtrField() destructor
//  }

  return i_send_result;
}

If anybody can point me to where I made a mistake in the memory allocation or anything else I would be very happy! I tried to use the set_allocated_datamessage() function or the set_data() function or more or less all I can think of...

Further things that might be of interest:

  • The message creation and the sending of messages happen in two different threads. I use a thread-safe (I hope) queue to get the unique_ptr from one thread to the other.
  • I tried creating the message in an arena, I can call arena.Reset() on the arena without getting errors, so I suppose the destruction of the message works in general(?)
  • The error vanishes if I remove the message creation and sending, so I suppose the heap allocation problem is really in there.
  • I can send a different message without a problem, in the beginning a StartMessage is sent without throwing debug assertion errors or similar.

The breakpoint which triggers when run in debug mode in VisualStudio 2019 points to the following code in the file delete_scalar.cpp inside the MSVC folder of VisualStudio (if that helps):

_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
    #ifdef _DEBUG
    _free_dbg(block, _UNKNOWN_BLOCK); //<-- Breakpoint
    #else
    free(block);
    #endif
}

And this is the Call-Stack:call-stack

Update:

I managed to find out, that apparently this is due to the string fields of the VariableDescription Message name and entity. To find out what is wrong I changed the code for generating the TCP message to the following:

TCPMessage* msg = google::protobuf::Arena::CreateMessage<TCPMessage>(proto_arena_);
msg->set_messagetype(TCPMessage::DATA);

DataMessage* data_msg = google::protobuf::Arena::CreateMessage<DataMessage>(proto_arena_);

auto v = data_msg->add_variables();
auto md = v->mutable_metadata();
md->set_datatype(VariableDescription_DataType_DOUBLE);
auto dim = md->mutable_dimensions();
dim->Add(1);
string test = "test";
md->set_allocated_name(&test); // I tried this
// md->set_name("test")        // And this
// auto n = md->mutable_name();// And those two lines
// n->assign("test");

msg->set_allocated_datamessage(data_msg);

auto msg_string = msg->SerializeAsString(); // If I remove this it runs through?!
proto_arena_->Reset();

All of those tries to set the name did not work. However, if I use

auto n = md->mutable_name();
n->push_back('a');

it works! However, if I iterate over the string and push_back each character one by one it doesn't work...

Update 2:

I just tried it out in the simplest matter I could think of and I still have the same problem. I changed some stuff in the proto file, so here a complete example:

main.cpp:

#include <iostream>
#include "tcp_data_message.pb.h"

int main()
{

  auto msg_t = std::make_unique<TCPMessage>();
  msg_t->set_messagetype(TCPMessage_Type_DATA);
  DataMessage* data_msg = msg_t->mutable_datamessage();

  auto v = data_msg->add_variables();
  auto md = v->mutable_metadata();
  md->set_datatype(VariableDescription_DataType_DOUBLE);
  auto dim = md->mutable_dimensions();
  dim->Add(1);
  md->set_entityid(1);
  md->set_id(2345678);
}

Proto file:

syntax = "proto3";
option cc_enable_arenas = true;
option optimize_for = LITE_RUNTIME;

message TCPMessage {
    enum Type {
        SETUP = 0;
        DATA = 1;
        START = 2;
        STOP = 3;
        // DATATEST = 4;
    }
    Type messageType = 1;
    oneof message {
        SetupMessage setupMessage = 2;
        DataMessage dataMessage = 3;
        StartMessage startMessage = 4;
        StopMessage stopMessage = 5;
        // DataMessageTest dataMessagetest = 7;
    }
    uint64 timestamp = 6;
}

message StartMessage{
    bool diagnosticMode = 1;
}

message StopMessage{

}

message SetupMessage {
    map<string, int32> entities = 1;
    map<string, int32> objects = 2;
    map<string, int32> commands = 3;
    repeated CommandDescription commandDescriptions = 4;
}

message CommandDescription {
    VariableDescription description = 1;
    string name = 2;
}

message DataMessage {
    repeated ProtoVariable variables = 1;
    uint64 timeSpan = 2;
}

message VariableDescription {

    enum DataType {
        DOUBLE = 0;
        // FLOAT = 1;
        // INT32 = 2;
        INT64 = 3;
        // UINT32 = 4;
        // UINT64 = 5;
        // Reserved if ever needed
        // SINT32 = 6;
        // SINT64 = 7;
        // FIXED32 = 8;
        // FIXED64 = 9;
        // SFIXED32 = 10;
        // SFIXED64 = 11;
        BOOL = 12;
        STRING = 13;
        BYTES = 14;
    }

    int32 entityID = 1;
    int32 ID = 2;
    DataType dataType = 3;
    repeated uint64 dimensions = 4;
}

message ProtoVariable
{
    VariableDescription metaData = 1;
    bytes data = 2;
}

message VariableDescriptionOld {

    enum DataType {
        DOUBLE = 0;
        // FLOAT = 1;
        // INT32 = 2;
        INT64 = 3;
        // UINT32 = 4;
        // UINT64 = 5;
        // Reserved if ever needed
        // SINT32 = 6;
        // SINT64 = 7;
        // FIXED32 = 8;
        // FIXED64 = 9;
        // SFIXED32 = 10;
        // SFIXED64 = 11;
        BOOL = 12;
        STRING = 13;
        BYTES = 14;
    }

    string entity = 1;
    string name = 2;
    DataType dataType = 3;
    repeated uint64 dimensions = 4;
}

message ProtoVariableOld
{
    VariableDescriptionOld metaData = 1;
    bytes data = 2;
}

And I still get the same error...

However, if I create a SetupMessage it works:

#include <iostream>
#include "tcp_data_message.pb.h"

int main()
{

  auto msg_t = std::make_unique<TCPMessage>();
  msg_t->set_messagetype(TCPMessage_Type_SETUP);
  auto setup_msg = msg_t->mutable_setupmessage();
  auto entities = setup_msg->mutable_entities();
  (*entities)["test"] = 123;


  /*
  DataMessage* data_msg = msg_t->mutable_datamessage();

  auto v = data_msg->add_variables();
  auto md = v->mutable_metadata();
  md->set_datatype(VariableDescription_DataType_DOUBLE);
  auto dim = md->mutable_dimensions();
  dim->Add(1);
  md->set_entityid(1);
  md->set_id(2345678);
  */
}

Update 3:

Apparently it was a problem with libprotobuf-lite.dll and libprotobuf-lited.dll, I didn't use the debug dll for my code. I managed to get my minimal example running now, however I ran into another problem. When Parsing the message from string to message I get a read access violation error. However, in my minimal example it works...

Minimal example:


#include <iostream>
#include "tcp_data_message.pb.h"

int main()
{
  std::string in = "18 97 10 12 10 4 98 97 108 108 16 -1 -1 -1 -1 7 18 14 10 10 112 111 115 105 116 105 111 110 95 121 16 1 18 14 10 10 118 101 108 111 99 105 116 121 95 121 16 2 26 18 10 14 115 101 116 95 118 101 108 111 99 105 116 121 95 121 16 3 34 29 10 11 8 -1 -1 -1 -1 7 16 3 34 1 1 18 14 115 101 116 95 118 101 108 111 99 105 116 121 95 121 0";
//^this is the chars I'm trying to parse
  std::string s = "";
  size_t pos = 0;
  std::string delimiter = " ";
  while ((pos = in.find(delimiter)) != std::string::npos) {
    std::string token = in.substr(0, pos);
    char c = static_cast<char>(std::stoi(token));
    s += c;
    in.erase(0, pos + delimiter.length());
  }
  std::cout << s << std::endl;
  {
    auto msg_t = std::make_unique<TCPMessage>();
    if (msg_t->ParseFromString(s)) {
      std::cout << "Wuhu!" << std::endl;
    }
    msg_t->set_messagetype(TCPMessage_Type_SETUP);
    auto setup_msg = msg_t->mutable_setupmessage();
    auto entities = setup_msg->mutable_entities();
    (*entities)["test"] = 123;

    DataMessage* data_msg = msg_t->mutable_datamessage();

    auto v = data_msg->add_variables();
    auto md = v->mutable_metadata();
    md->set_datatype(VariableDescription_DataType_DOUBLE);
    auto dim = md->mutable_dimensions();
    dim->Add(1);
    md->set_entityid(1);
    md->set_id(2345678);
  }
  std::cout << "Test" << std::endl;
}

If I copy paste the whole string and creation of it and so on I get a read access violation in my "proper" code. I believe this must be a problem of the dll or something like that.

"Real" code:

//...

std::string in = "18 97 10 12 10 4 98 97 108 108 16 -1 -1 -1 -1 7 18 14 10 10 112 111 115 105 116 105 111 110 95 121 16 1 18 14 10 10 118 101 108 111 99 105 116 121 95 121 16 2 26 18 10 14 115 101 116 95 118 101 108 111 99 105 116 121 95 121 16 3 34 29 10 11 8 -1 -1 -1 -1 7 16 3 34 1 1 18 14 115 101 116 95 118 101 108 111 99 105 116 121 95 121 0";
  std::string s = "";
  size_t pos = 0;
  std::string delimiter = " ";
  while ((pos = in.find(delimiter)) != std::string::npos) {
    std::string token = in.substr(0, pos);
    char c = static_cast<char>(std::stoi(token));
    s += c;
    in.erase(0, pos + delimiter.length());
  }
  //std::cout << "Received Message: " << buf_string << std::endl;
  TCPMessage* msg = new TCPMessage();
  if (!msg->ParseFromString(s)) {
    std::cout << "ERROR: Parsing Message from String failed" << std::endl;
    return NULL;
  }

When I debug the code through VisualStudio I can find out, that in the ParseFromString(std::string data) the data object already shows "Unable to read memory". The exception thrown is:

Exception thrown at 0x01133B87 (vcruntime140d.dll) in program.exe: 0xC0000005: Access violation reading location 0xCCCCCCCC.

And it shows me this part of memcpy.asm: enter image description here

Any ideas where I could be going wrong.

Here is what the debugger shows me right before calling ´msg->ParseFromString(s)´:
enter image description here

And here is what it shows me inside the ParseFromString(ConstStringParam data) method:
enter image description here

1 Answers

I finally figured out what was the problem, so for everybody who has a similar problem:

1 For the first described heap problems make sure you use the correct libraries for the appropriate configuration (debug/ release). I used libprotobuf.lib/dll instead of libprotobufd.lib/dll.

2 For the problem with the string passed to the ParseFromString() method: I had _ITERATOR_DEBUG_LEVEL=0 set in the program's Preprocessor Definitions. This was incompatible with the dll generated by vcpkg. To make it compatible you have to include _ITERATOR_DEBUG_LEVEL=0 to the compilation of the protobuflib. You can do this by adding

set(VCPKG_C_FLAGS_DEBUG "/D_ITERATOR_DEBUG_LEVEL=0")
set(VCPKG_CXX_FLAGS_DEBUG "/D_ITERATOR_DEBUG_LEVEL=0")

to the cmake file used for compiling the proto library in vcpkg. E.g. C:\Program Files\vcpkg\triplets\x86-windows.cmake if invoking vcpkg install protobuf protobuf:x86-windows (best to create a new cmake file and invoke it after the colon in the install command). Then use the newly created lib/dll files in your project.

Related