Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

When manually managing the lifetime of an object, the constructor must be called to initiate the lifetime of the object. Similarly, the destructor must be called to terminate the lifetime of the object. Use of an object outside of its lifetime may result in undefined behavior. The constructor for an object may be called explicitly by using the placement new operator, or by calling the construct() function of an allocator object. The destructor for an object may be called explicitly, or by calling the destroy() function of an allocator object.

Noncompliant Code Example

In this noncompliant code example, a class with non-trivial initialization is created with a call to std::malloc(). However, the constructor for the object is never called, resulting in undefined behavior when the class is later accessed by calling s->f().

Code Block
bgColor#FFcccc
langcpp
#include <cstdlib>

struct S {
  S();
  
  void f();
};

void f() {
  S *s = static_cast<S *>(std::malloc(sizeof(S)));
 
  s->f();
 
  std::free(s);
}

Compliant Solution

In this compliant solution, the constructor and destructor are both explicitly called. Further, to reduce the possibility of the object being used outside of its lifetime, the underlying storage is a separate variable from the live object.

Code Block
bgColor#ccccff
langcpp
#include <cstdlib>
#include <new>

struct S {
  S();
  
  void f();
};

void f() {
  void *ptr = std::malloc(sizeof(S));
  S *s = new (ptr) S;

  s->f();
 
  s->~S();
  std::free(s);
}

Noncompliant Code Example

In this noncompliant code example, a custom container class uses an allocator object to obtain storage for arbitrary element types, but fails to explicitly call object constructors. If an element that has been reserved is accessed through the operator[]() function, it results in undefined behavior, depending on the type T:

Code Block
bgColor#FFcccc
langcpp
#include <memory>

template <typename T, typename Alloc = std::allocator<T>>
class Container {
  T *UnderlyingStorage;
  size_t NumElements;
  
  void CopyElements(T *From, T *To, size_t Count);
  
public:
  void reserve(size_t Count) {
    if (Count > NumElements) {
      Alloc A;
      T *P = A.allocate(Count); // Throws on failure
      try {
        CopyElements(UnderlyingStorage, P, NumElements);
      } catch (...) {
        A.deallocate(P, Count);
        throw;
      }
      UnderlyingStorage = P;
    }
    NumElements = Count;
  }
  
  T &operator[](size_t Idx) { return UnderlyingStorage[Idx]; }
  const T &operator[](size_t Idx) const { return UnderlyingStorage[Idx]; }
};

Compliant Solution

In this compliant solution, the newly-allocated memory is properly initialized by explicitly calling object constructors for T:

Code Block
bgColor#ccccff
langcpp
#include <memory>

template <typename T, typename Alloc = std::allocator<T>>
class Container {
  T *UnderlyingStorage;
  size_t NumElements;
  
  void CopyElements(T *From, T *To, size_t Count);
  
public:
  void reserve(size_t Count) {
    if (Count > NumElements) {
      Alloc A;
      T *P = A.allocate(Count); // Throws on failure
      try {
        CopyElements(UnderlyingStorage, P, NumElements);
        for (size_t i = NumElements; i < Count; ++i) {
          A.construct(&P[i]);
        }
      } catch (...) {
        A.deallocate(P, Count);
        throw;
      }
      UnderlyingStorage = P;
    }
    NumElements = Count;
  }
  
  T &operator[](size_t Idx) { return UnderlyingStorage[Idx]; }
  const T &operator[](size_t Idx) const { return UnderlyingStorage[Idx]; }
};

Exceptions

MEM33-EX1: If the object is trivially constructible, it need not have its constructor explicitly called to initiate the object's lifetime. If the object is trivially destructible, it need not have its destructor explicitly called to terminate the object's lifetime. These properties can be tested by calling std::is_trivially_constructible() and std::is_trivially_destructible() from <type_traits>. For instance, integral types such as int and long long do not require an explicit constructor or destructor call.

Risk Assessment

Failing to properly constructor or destroy an object leaves its internal state inconsistent, which can result in undefined behavior and accidental information exposure.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MEM33-CPP

High

Likely

Medium

P18

L1

Automated Detection

Tool

Version

Checker

Description

    

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

[ISO/IEC 14882-2014]3.8, "Object Lifetime"
Clause 9, "Classes"

...