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.
Do not attempt to delete a pointer to an object of incomplete type. 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, 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.
...
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 | ||||
|---|---|---|---|---|
| ||||
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 | ||||
|---|---|---|---|---|
| ||||
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 | ||||
|---|---|---|---|---|
| ||||
// 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 | ||||
|---|---|---|---|---|
| ||||
// 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 |
| delete-with-incomplete-type | |||||||
| Coverity | 6.5 | DELETE_VOID | Fully implemented | ||||||
| Clang |
| -Wdelete-incomplete |
| CodeSonar |
| LANG.CAST.PC.INC | Conversion: pointer to incomplete | ||||||
| Helix QAC |
| C++3112 | |||||||
| Klocwork |
| CERT.EXPR.DELETE_PTR.INCOMPLETE_TYPE | |||||||
| LDRA tool suite |
| 169 S, 554 S | Enhanced Enforcement | ||||||
| Parasoft C/C++test |
| CERT_CPP-EXP57-a | Do not delete objects with incomplete class at the point of deletion | |||||||
| Parasoft Insure++ | Runtime detection | ||||||||
| Polyspace Bug Finder |
| CERT C++: EXP57-CPP | Checks for conversion or deletion of incomplete class pointer | ||||||
| RuleChecker |
| delete-with-incomplete-type |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Bibliography
| [Dewhurst |
| 2002] | Gotcha #39, "Casting Incomplete Types" |
| [ISO/IEC 14882-2014] | Subclause 4.10, "Pointer Conversions" |
| [Sutter |
| 2000] | "Compiler Firewalls and the Pimpl Idiom" |
...
...