Main Content

CWE Rule 460

Improper Cleanup on Thrown Exception

Since R2023a

Description

Rule Description

The product does not clean up its state or incorrectly cleans up its state when an exception is thrown, leading to unexpected state or control flow.

Polyspace Implementation

The rule checker checks for Exception violating class invariant.

Examples

expand all

Issue

This issue occurs when a non-noexcept member function attempts to raise an exception after modifying any of the fields of the class. For instance, Polyspace® flags a throw() statement or a new statement if they are used after modifying the internal state of a class.

Risk

To facilitate recovery from an exception while preserving the invariant of the relevant objects, design programs that have exception safety. The C++ standard allows these levels of exception safety:

  • Basic Exception Safety: This level of exception safety requires that after raising an exception, the basic invariants of all objects are maintained in a valid state and no memory is leaked. If an operation has basic exception safety, then you can destroy or assign to an object after the operations, even if an exception has been raised. The operations in the standard library offer at least basic exception safety.

  • Strong Exception Safety: This level of exception safety requires that after raising an exception, the state of the program remains as it was before the exception. Operations with strong exception safety either succeed or exit with an exception and have no effects on the program.

  • nothrow: This level of safety requires that an exception cannot occur in an operation.

Without at least the basic exception safety, exceptions might corrupt the state of a program, leave the program in an invalid state, or create memory leaks. Code that does not provide at least the basic exception safety guarantee is unsafe and defective.

Fix

To provide at least the basic exception safety in a function, modify the class invariant only when there can be no exceptions. When performing actions that might raise exceptions, use temporary objects that do not modify the original objects invariant.

Example
#include <cstdint>
#include <cstring>
template<typename T=int>
class myArray
{
public:
    myArray() {/*...*/}
    myArray(const myArray& rhs)
    {
        DeepCopyNC(rhs);
    }
    ~myArray()
    {
        delete[] array;
    }
    void DeepCopyNC(const myArray& rhs) // Noncompliant
    {
        if (this != &rhs)
            {
                delete[] array;
                array = nullptr;
                len = rhs.len;

                if (len > 0)
                    {

                        array = new T[len];
                        std::memcpy(array, rhs.array, len * sizeof(T));
                    }
            }
    }

private:
    T* array;
    std::size_t len;
};
extern 	myArray<> c1{};
void foo()
{

    myArray<> c2{c1};

}

This example shows a generic class template myArray that manages a raw array. The copy constructor of this class shows two implementation of deep copy. In the function DeepCopyNC(), the memory operations are performed after modifying the this->array and this->len fields. If the memory operation exits with an exception, the invariant of the original class is violated. Because the class invariant is violated by an exception, the function DeepCopyNC() cannot guarantee basic exception safety. Polyspace flags it.

Correction

To guarantee basic exception safety, modify the class invariant only when the memory operation succeeds with no exceptions, as shown in DeepCopy.

#include <cstdint>
#include <cstring>
template<typename T=int>
class myArray
{
public:
    myArray() {/*...*/}
    myArray(const myArray& rhs)
    {
        DeepCopy(rhs);
    }
    ~myArray()
    {
        delete[] array;
    }
    void DeepCopy(const myArray& rhs) // Compliant
    {
        T* eTmp = nullptr;

        if (rhs.len > 0)
            {
                eTmp = new T[rhs.len];
                std::memcpy(eTmp, rhs.array, rhs.len * sizeof(T));
            }

        delete[] array;
        array = eTmp;
        len = rhs.len;
    }

private:
    T* array;
    std::size_t len;
};
extern 	myArray<> c1{};
void foo()
{

    myArray<> c2{c1};

}

Check Information

Category: Initialization and Cleanup Errors

Version History

Introduced in R2023a