Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Rewrote content

The use Referring to objects of incomplete class declarations (type, also known as "forward " declarations) , is a common . While it is possible to declare pointers and references to incomplete classes, because the class definition is not available it is not possible to access a member of the class, determine the size of the class object, and so on. However, it is possible to cast and delete a pointer to an incomplete class, but this is never a good idea.

Non-Compliant Code Example (deleting)

practice. One such common usage is with the "pimpl idiom" [Sutter 00] where an opaque pointer is used to hide implementation details from a public-facing API. However, attempting to delete a pointer to an object of incomplete class type can lead to undefined behavior. The C++ Standard, [expr.delete], paragraph 5, states [ISO/IEC 14882-2014]:

If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.

Do not attempt to delete a pointer to an object of incomplete type. While it is well-formed if the class has no nontrivial destructor and no associated deallocation function, it would become undefined behavior were a nontrivial destructor or deallocation function added later. It would be possible to check for a nontrivial destructor at compile time using a static_assert and the std::is_trivially_destructible type trait, not such type trait exists to test for the presence of a deallocation function.

Pointer downcasting to a pointer of incomplete class type has similar caveats. Pointer upcasting (casting from a more derived type to a less derived type) is a standard implicit conversion operation. C++ allows static_cast to perform the inverse operation, pointer downcasting, via [expr.static.cast], paragraph 7. However, when the pointed-to type is incomplete, the compiler is unable to make any class offset adjustments that may be required in the presence of multiple inheritance, resulting in a pointer that cannot be validly dereferenced.

reinterpret_cast of a pointer type is defined by [expr.reinterpret.cast], paragraph 7, as being static_cast<cv T *>(static_cast<cv void *>(PtrValue)), meaning that reinterpret_cast is simply a sequence of static_cast operations. C-style casts of a pointer to incomplete object type are defined as using either static_cast or reinterpret_cast (which is picked is unspecified) in [expr.cast], paragraph 5.

Do not attempt to cast through a pointer to an object of incomplete type. The cast operation itself is well-formed, but dereferencing the resulting pointer may result in undefined behavior if the downcast is unable to adjust for multiple inheritance.

Noncompliant Code Example

In this noncompliant code example, a class attempts to implement the pimpl idiom, but deletes a pointer to an incomplete class type, resulting in undefined behavior if Body has a nontrivial destructor:Consider a common application of a handle/body idiom that implements an abstract data type by a handle that contains a pointer to an implementation class.

Code Block
bgColor#FFcccc
langcpp
class Handle {
  class Body *Impl;  // Declaration incompleteof class declaration

class Handle {
  public:
    Handle();
  a pointer to an incomplete class.
public:
  ~Handle() { delete impl_Impl; } // deletionDeletion of pointer to an incomplete class.
    // ...
  private:
    Body *impl_;
};

Because impl_ is a pointer to an undefined class, its deletion in Handle's destructor results in undefined behavior if Body has a non-trivial destructor. Even in the case where Body does have a trivial destructor, this practice should be avoided. During maintenance a non-trivial destructor could be added to Body, resulting in undefined behavior in the destructor for Handle. Typical behavior in this case is that no destructor is called and the memory for Body is released via a call to the usual global operator delete. This may result in a resource leak or other bug if the destructor manages resources, or if the definition of Body defines or inherits a member operator delete.

Compliant Solution 1

Compliant Solution (delete)

In this compliant solution, the deletion of Impl is The deletion of impl_ should be moved to a part of the code where where Body is defined.:

Code Block
bgColor#ccccff
langcpp
class Handle {
  class Body { *Impl;  // Declaration of a pointer to an incomplete class.
public:
  ~Handle();
  // ...
};

 
// Elsewhere.
class Body { /* ... */ };
 
Handle::~Handle() {
  delete impl_Impl; } // correct.

}

Compliant Solution

...

(std::shared_ptr)

In this compliant solution, a std::shared_ptr is used to own the memory to Impl. Note that a std::shared_ptr is capable of referring to an incomplete type, but a std::unique_ptr is notAlternatively, an appropriate smart pointer may be used in place of a raw pointer.

Code Block
bgColor#ccccff
langcpp
#include <memory>
 
class Handle {
  std::shared_ptr<class Body> Impl;
  public:
    Handle();
    ~Handle() {} // correct.
    // ...
  private:
    std::tr1::shared_ptr<Body> impl_;
};

Note that we used a shared_ptr to refer to the Body. Other common smart pointers, including std::auto_ptr, will still produce undefined behavior.

Non-Compliant Code Example (casting)

Noncompliant Code Example

Pointer downcasting (casting a pointer to a base class into a pointer to a derived class) may require adjusting the address of the pointer Similarly, while it is possible to cast a pointer or reference to an incomplete class, it is never a good idea to do so. Casting a class address often involves an adjustment of the address by a fixed amount that can only be determined after when the layout and inheritance structure of the class inheritance structure is known, and this information is not available in the case of an incomplete class. In this noncompliant code example, f() retrieves a polymorphic pointer of complete type B from getD(). That pointer is then cast to a pointer of incomplete type, D, before being passed to g(). Casting to a pointer to the derived class may fail to properly adjust the resulting pointer, resulting in undefined behavior when the pointer is dereferenced by calling d->doSomething().

Code Block
bgColor#FFcccc
langcpp
// File1.h
class B {
protected:
  double  // ...d;
public:
  B() : d(1.0) {}
};
B *getMeSomeSortOfB(); 
// File2...
h
void g(class D *);
class B *getD(); // Returns a incompleteD declarationobject

// File1.cpp
#include "File1.h"
#include "File2.h"

void f() {
  B *bpv = getMeSomeSortOfBgetD();
D *dp = (D *)bp; // old-stlye cast: legal, but inadvisable
dp = reinterpret_cast<D *>(bp); // new-style cast: legal, but inadvisable

Both an old-style cast and a reinterpret_cast may be used to cast a pointer to an incomplete class. However, the cast may result in a bad address.

Code Block
bgColor#FFcccc
langcpp
class D : public SomeClass, public B {
    // ...
};

B *getMeSomeSortOfB() { return new D; }

In this case, it is likely that a correct cast of a B * to a D * would have to adjust the address by a fixed amount. However, at the point the cast is translated by the compiler the required information is not available and the address adjustment will not take place.

In the case of an old-style cast, the address adjustment will, however, take place if the cast is performed at a point where the structure of the class D is known. This different, context-dependent behavior of the old-style cast can result in very challenging bugs.

Compliant Solution

...

  g(reinterpret_cast<class D *>(v));
}
 
// File2.cpp
#include "File2.h"
#include "File1.h"
#include <iostream>

class Hah {
protected:
  short s;
public:
  Hah() : s(12) {}
};

class D : public Hah, public B {
  float f;
public:
  D() : Hah(), B(), f(1.2f) {}
  void doSomething() { std::cout << "f: " << f << ", d: " << d << ", s: " << s << std::endl; }
};

void g(D *d) {
  d->doSomething();
}

B *getD() {
  return new D;
}

Implementation Details

When compiled with Clang 3.5, the noncompliant code example prints:

Code Block
f: 1.89367e-40, d: 5.27183e-315, s: 0

Similarly unexpected values are printed when the example is run in Microsoft Visual Studio 2013 and GCC 4.9.0.

Compliant Solution

This compliant solution assumes that the intent is to hide implementation details by using incomplete class types. Instead of requiring a D * to be passed to g(), it expects a B * type instead:

Code Block
bgColor#ccccff
langcpp
// File1.h
class B {
protected:
  double d;
public:
  B() : d(1.0) {}
};
 
// File2.h
void g(class B *); // Accepts a B object, expects a D object
class B *getD(); // Returns a D object

// File1.cpp
#include "File1.h"
#include "File2.h"

void f() {
  B *v = getD();
  g(v);
}
 
// File2.cpp
#include "File2.h"
#include "File1.h"
#include <iostream>

class Hah {
protected:
  short s;
public:
  Hah() : s(12) {}
};

class D : public Hah, public B {
  float f;
public:
  D() : Hah(), B(), f(1.2f) {}
  void doSomething() { std::cout << "f: " << f << ", d: " << d << ", s: " << s << std::endl; }
};

void g(B *d) {
  D *t = static_cast<D *>(d);
  if (t) {
    t->doSomething();
  } else {
    // Handle error.
  }
}

B *getD() {
  return new D;
}

Risk Assessment

Casting pointers or references to incomplete classes can result in bad addresses. Deleting a pointer to an incomplete class results in undefined behavior if the class has a non-trivial destructor. This can result in program termination, a runtime signal, or resource leaks.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

EXP39-CPP

mediumMedium

unlikelyUnlikely

mediumMedium

P4

L3

Automated Detection

Tool

Version

Checker

Description

Coverity6.5DELETE_VOIDFully Implemented

Related Vulnerabilities

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

Related Guidelines

  

Bibliography

[ISO/IEC 14882-2014]

5.3.5, "Delete"
5.2.9, "Static Cast"
5.2.10, "Reinterpret Cast"
5.4, "Explicit Type Conversion (Cast Notation)
4.10, "Pointer Conversions"

[Sutter 00]"Compiler Firewalls and the Pimpl Idiom"
[Dewhurst 03]Gotcha 39

...

, "Casting Incomplete Types"