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] 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 [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.

...

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.

...

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 *impl;  // Declaration of a pointer to an incomplete class
public:
  ~Handle() { delete impl; } // Deletion of pointer to an incomplete class
  // ...
};

...

In this compliant solution, the deletion of impl is moved to a part of the code 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;
}

...

In this compliant solution, a std::shared_ptr is used to own the memory to impl. 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#FFcccc
langcpp
// File1.h
class B {
protected:
  double d;
public:
  B() : d(1.0) {}
};
 
// File2.h
void g(class D *);
class B *get_d(); // Returns a pointer to a D object

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

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

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

B *get_d() {
  return new D;
}

Implementation Details

When compiled with ClangBB. Definitions#clang3.8 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

...

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:.

Code Block
bgColor#ccccff
langcpp
// File1.h -- contents identical.
// File2.h
void g(class B *); // Accepts a B object, expects a D object
class B *get_d(); // Returns a pointer to a D object

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

void f() {
  B *v = get_d();
  g(v);
}
 
// File2.cpp
// ... all contents are identical until ...
void g(B *d) {
  D *t = dynamic_cast<D *>(d);
  if (t) {
    t->do_something();
  } else {
    // Handle error
  }
}

B *get_d() {
  return new D;
}

...

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 nontrivial destructor. Doing so can cause program termination, a runtime signal, or resource leaks.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

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
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
9.5PB-54, PB-55 Parasoft Insure++  Runtime detection
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.

Bibliography

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

Subclause 4.10, "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)"

[Sutter
00
2000]"Compiler Firewalls and the Pimpl Idiom"

...


...

Image Modified Image Modified Image Modified