Main Content

Bytewise operations on nontrivial class object

Value representations may be improperly initialized or compared

Description

This defect occurs when you use C Standard library functions to perform bytewise operation on non-trivial or non-standard layout class type objects. For definitions of trivial and standard layout classes, see the C++ Standard (ISO/IEC 14882:2017), [class], paragraphs 6 and 7 respectively.

The checker raises a defect when:

  • You initialize or copy non-trivial class type objects using these functions:

    • std::memset

    • std::memcpy

    • std::strcpy

    • std::memmove

    To check whether a class type is trivial, use the type-traits library function std::is_trivial, for instance:

    #include <iostream>
    #include <type_traits>
    
    class trivialClass {};
    
    void checkTrivial(){
        static_assert(std::is_trivial<trivialClass>::value,
                      "Class is not trivial");
    }
    It is not sufficient to check that the object type is trivially copyable (std::is_trivially_copyable<>:value). A trivially copyable object does not guarantee the class invariants hold when you use the object later in your program.

  • You compare non-standard layout class type objects using these functions:

    • std::memcmp

    • std::strcmp

Note that an incomplete class can be potentially nontrivial.

The checker does not raise a defect if the bytewise operation is performed through an alias. For example no defect is raised in the bytewise comparison and copy operations in this code. The bytewise operations use dptr and sptr, the aliases of non-trivial or non-standard layout class objects d and s.

void func(NonTrivialNonStdLayout *d, const NonTrivialNonStdLayout *s)
{
   void* dptr = (void*)d; 
   const void* sptr = (void*)s;
   // ...
   // ...
   // ...
   if (!std::memcmp(dptr, sptr, sizeof(NonTrivialNonStdLayout))) {  
     (void)std::memcpy(dptr, sptr, sizeof(NonTrivialNonStdLayout)); 
      // ...
   }
}

Risk

Performing bytewise comparison operations by using C Standard library functions on non-trivial or non-standard layout class type object might result in unexpected values due to implementation details. The object representation depends on the implementation details, such as the order of private and public members, or the use of virtual function pointer tables to represent the object.

Performing bytewise setting operations by using C Standard library functions on non-trivial or non-standard layout class type object can change the implementation details. The operation might result in abnormal program behavior or a code execution vulnerability. For instance, if the address of a member function is overwritten, the call to this function invokes an unexpected function.

Fix

To perform bytewise operations non-trivial or non-standard layout class type object, use these C++ special member functions instead of C Standard library functions.

C Standard Library FunctionsC++ Member Functions

std::memset

Class constructor

std::memcpy

std::strcpy

std::memmove

Class copy constructor

Class move constructor

Copy assignment operator

Move assignment operator

std::memcmp

std::strcmp

operator<()

operator>()

operator==()

operator!=()

Examples

expand all

#include <cstring>
#include <iostream>
#include <utility>

class nonTrivialClass
{
    int scalingFactor;
    int otherData;
public:
    nonTrivialClass() : scalingFactor(1) {}
    void set_other_data(int i);
    int f(int i)
    {
        return i / scalingFactor;
    }
    // ...
};

void func()
{
    nonTrivialClass c;
    // ... Code that mutates c ...
    std::memset(&c, 0, sizeof(nonTrivialClass));
    std::cout << c.f(100) << std::endl;
}

In this example, func() uses std::memset to reinitialize non-trivial class object c after it is first initialized with its default constructor. This bytewise operation might not properly initialize the value representation of c.

Correction — Define Function Template That Uses std::swap

One possible correction is to define a function template clear() that uses std::swap to perform a swap operation. The call to clear()properly reinitializes object c by swapping the contents of c and default initialized object empty.

 #include <cstring>
#include <iostream>
#include <utility>

class nonTrivialClass
{
    int scalingFactor;
    int otherData;
public:
    nonTrivialClass() : scalingFactor(1) {}
    void set_other_data(int i);
    int f(int i)
    {
        return i / scalingFactor;
    }
    // ...
};

template <typename T>
T& clear(T& o)
{
    using std::swap;
    T empty;
    swap(o, empty);
    return o;
}

void func()
{
    nonTrivialClass c;
    // ... Code that mutates c ...

    clear(c);
    std::cout << c.f(100) << std::endl;
}

Result Information

Group: Object Oriented
Language: C++
Default: Off
Command-Line Syntax: MEMOP_ON_NONTRIVIAL_OBJ
Impact: Medium

Version History

Introduced in R2019b