Skip to content

Compiler optimization removes normalization for Cdr::serialize(bool) #315

@mirkomorati

Description

@mirkomorati

After way too long debugging a Fast-DDS subscriber that kept returning RETCODE_NO_DATA, I traced the problem back to assigning an uninitialized bool field to the published data type. The value had garbage in memory, and in a release build that garbage byte ends up written verbatim onto the wire.

I think that the issue is in Cdr::serialize(bool), the normalization step gets optimized away by the compiler in release builds.

Looking at the source:

Fast-CDR/src/cpp/Cdr.cpp

Lines 816 to 837 in a9bd70c

Cdr& Cdr::serialize(
const bool bool_t)
{
if (((end_ - offset_) >= sizeof(uint8_t)) || resize(sizeof(uint8_t)))
{
// Save last datasize.
last_data_size_ = sizeof(uint8_t);
if (bool_t)
{
offset_++ << static_cast<uint8_t>(1);
}
else
{
offset_++ << static_cast<uint8_t>(0);
}
return *this;
}
throw NotEnoughMemoryException(NotEnoughMemoryException::NOT_ENOUGH_MEMORY_MESSAGE_DEFAULT);
}

The release build compiles it down to a single mov %bl, (%rax).
This is the release version decompiled for reference:

/* eprosima::fastcdr::Cdr::serialize(bool) */

Cdr * __thiscall eprosima::fastcdr::Cdr::serialize(Cdr *this,bool bool_t)

{
  char cVar1;
  long lVar2;

  // Omit exception handling

  *(undefined8 *)(this + 0x88) = 1;
  *(long *)(this + 0x98) = lVar2 + 1;
  *(bool *)lVar2 = bool_t; // Here we assign directly bool_t
  return this;
}

I know this is technically UB on my side, but right now that check is effectively a no-op.

This looks like the same issue addressed in #155, is there a better way to approach it?

I thought about introducing a function like the following:

bool force_bool(uint8_t value) {
    return !!value;
}

Then the serialize method would be:

Cdr& Cdr::serialize(
        const bool bool_t)
{
    if (((end_ - offset_) >= sizeof(uint8_t)) || resize(sizeof(uint8_t)))
    {
        // Save last datasize.
        last_data_size_ = sizeof(uint8_t);

        offset_++ << force_bool(static_cast<uint8_t>(bool_t));

        return *this;
    }

    throw NotEnoughMemoryException(NotEnoughMemoryException::NOT_ENOUGH_MEMORY_MESSAGE_DEFAULT);
}

This compiles to the following (as before, I'm showing the decompilation from the library compiled in release mode):

/* eprosima::fastcdr::force_bool(unsigned char) */

bool eprosima::fastcdr::force_bool(uchar param_1)

{
  return param_1 != '\0';
}

/* eprosima::fastcdr::Cdr::serialize(bool) */

Cdr * __thiscall eprosima::fastcdr::Cdr::serialize(Cdr *this,bool bool_t)

{
  undefined1 uVar1;
  char cVar2;
  undefined1 *puVar3;
  
  // Omit exception handling

  *(undefined8 *)(this + 0x88) = 1;
  *(undefined1 **)(this + 0x98) = puVar3 + 1;
  uVar1 = force_bool(bool_t);
  *puVar3 = uVar1;
  return this;
}

Version used:

  • Fast-CDR v2.3.0
  • GCC 11.5.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions