Versions Compared

Key

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

Referring to objects of incomplete class type, also known as forward declarations, is a common practice. One such common usage is with the "pimpl idiom" [Sutter 00] where whereby 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], states the following:

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 Although 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 but no such type trait exists to test for the presence of a deallocation function.

...

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 an incomplete object type are defined as using either static_cast or reinterpret_cast (it is unspecified 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:.

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

Compliant Solution (delete)

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

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

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

Compliant Solution (std::shared_ptr)

In this compliant solution, a std::shared_ptr is used to own the memory to Implimpl. Note that a  A std::shared_ptr is capable of referring to an incomplete type, but a std::unique_ptr is not.

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

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 by a fixed amount that can only be determined only when the layout of the class inheritance structure is known. In this noncompliant code example, f() retrieves a polymorphic pointer of complete type B from getDget_d(). 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 causing undefined behavior when the pointer is dereferenced by calling d->doSomething>do_something().

Code Block
bgColor#FFcccc
langcpp
// File1.h
class B {
protected:
  double d;
public:
  B() : d(1.0) {}
};
 
// File2.h
void g(class D *);
class B *getDget_d(); // Returns a pointer to a D object

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

void f() {
  B *v = getDget_d();
  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 doSomethingdo_something() { std::cout << "f: " << f << ", d: " << d << ", s: " << s << std::endl; }
};

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

B *getDget_d() {
  return new D;
}

Implementation Details

When compiled with ClangBB. Definitions#clang3.58 and the function f() is executed, the noncompliant code example prints :the following.

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 2015 and GCC 46.91.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) {}
};
 -- contents identical.
// File2.h
void g(class B *); // Accepts a B object, expects a D object
class B *getDget_d(); // Returns a pointer to a D object

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

void f() {
  B *v = getDget_d();
  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; }
};

... all contents are identical until ...
void g(B *d) {
  D *t = staticdynamic_cast<D *>(d);
  if (t) {
    t->doSomething>do_something();
  } else {
    // Handle error.
  }
}

B *getDget_d() {
  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 nontrivial destructor. This Doing so can result in cause program termination, a runtime signal, or resource leaks.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

EXP39

EXP57-CPP

Medium

Unlikely

Medium

P4

L3

Automated Detection

Tool

Version

Checker

Description

Astrée

Include Page
Astrée_V
Astrée_V

delete-with-incomplete-type

Coverity6.5DELETE_VOIDFully
Implemented
implemented
Clang
Include Page
Clang_V
Clang_V
-Wdelete-incomplete
CodeSonar
Include Page
CodeSonar_V
CodeSonar_V

LANG.CAST.PC.INC

Conversion: pointer to incomplete
Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

C++3112
Klocwork
Include Page
Klocwork_V
Klocwork_V
CERT.EXPR.DELETE_PTR.INCOMPLETE_TYPE
LDRA tool suite
Include Page
LDRA_V
LDRA_V

169 S, 554 S

Enhanced Enforcement

Parasoft C/C++test
Include Page
Parasoft_V
Parasoft_V

CERT_CPP-EXP57-a
CERT_CPP-EXP57-b

Do not delete objects with incomplete class at the point of deletion
Conversions shall not be performed between a pointer to an incomplete type and any other type

Parasoft Insure++

Runtime detection
Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C++: EXP57-CPPChecks for conversion or deletion of incomplete class pointer
RuleChecker
Include Page
RuleChecker_V
RuleChecker_V
delete-with-incomplete-type

Related Vulnerabilities

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

Related Guidelines

  

...

Bibliography

[Dewhurst 2002]Gotcha #39, "Casting Incomplete Types"
[ISO/IEC 14882-2014]
5.3.5

Subclause 4.10, "

Delete

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


4.10, "Pointer Conversions

"

[Sutter
00
2000]"Compiler Firewalls and the Pimpl Idiom
"[Dewhurst 03]Gotcha 39,
"
Casting Incomplete Types"

...


...

Image Modified Image Modified Image Modified