Reclaiming resources when exceptions are thrown is important. An exception being thrown may result in cleanup code being bypassed or an object being left in a partially initialized state. Such a partially initialized object would violate basic exception safety, as described in ERR56-CPP. Guarantee exception safety. It is preferable that resources be reclaimed automatically, using the RAII design pattern [Stroustrup 2001], when objects go out of scope. This technique avoids the need to write complex cleanup code when allocating resources.

However, constructors do not offer the same protection. Because a constructor is involved in allocating resources, it does not automatically free any resources it allocates if it terminates prematurely. The C++ Standard, [except.ctor], paragraph 2 [ISO/IEC 14882-2014], states the following:

An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.

It is generally recommended that constructors that cannot complete their job should throw exceptions rather than exit normally and leave their object in an incomplete state [Cline 2009].

Resources must not be leaked as a result of throwing an exception, including during the construction of an object.

This rule is a subset of MEM51-CPP. Properly deallocate dynamically allocated resources, as all failures to deallocate resources violate that rule.

Noncompliant Code Example

In this noncompliant code example, pst is not properly released when process_item throws an exception, causing a resource leak.

#include <new>
 
struct SomeType {
  SomeType() noexcept; // Performs nontrivial initialization.
  ~SomeType(); // Performs nontrivial finalization.
  void process_item() noexcept(false);
};
 
void f() {
  SomeType *pst = new (std::nothrow) SomeType();
  if (!pst) {
    // Handle error
    return;
  }
 
  try {
    pst->process_item();
  } catch (...) {
    // Process error, but do not recover from it; rethrow.
    throw;
  }
  delete pst;
}

Compliant Solution (delete)

In this compliant solution, the exception handler frees pst by calling delete.

#include <new>

struct SomeType {
  SomeType() noexcept; // Performs nontrivial initialization.
  ~SomeType(); // Performs nontrivial finalization.

  void process_item() noexcept(false);
};

void f() {
  SomeType *pst = new (std::nothrow) SomeType();
  if (!pst) {
    // Handle error
    return;
  }
  try {
    pst->process_item();
  } catch (...) {
    // Process error, but do not recover from it; rethrow.
    delete pst;
    throw;
  }
  delete pst;
}

While this compliant solution properly releases its resources using catch clauses, this approach can have some disadvantages:

  • Each distinct cleanup requires its own try and catch blocks.
  • The cleanup operation must not throw any exceptions.

Compliant Solution (RAII Design Pattern)

A better approach is to employ RAII. This pattern forces every object to clean up after itself in the face of abnormal behavior, preventing the programmer from having to do so. Another benefit of this approach is that it does not require statements to handle resource allocation errors, in conformance with MEM52-CPP. Detect and handle memory allocation errors.

struct SomeType {
  SomeType() noexcept; // Performs nontrivial initialization.
  ~SomeType(); // Performs nontrivial finalization.

  void process_item() noexcept(false);
};

void f() {
  SomeType st;
  try {
    st.process_item();
  } catch (...) {
    // Process error, but do not recover from it; rethrow.
    throw;
  } // After re-throwing the exception, the destructor is run for st.
} // If f() exits without throwing an exception, the destructor is run for st.

Noncompliant Code Example

In this noncompliant code example, the C::C() constructor might fail to allocate memory for a, might fail to allocate memory for b, or might throw an exception in the init() method. If init() throws an exception, neither a nor b will be released. Likewise, if the allocation for b fails, a will not be released.

struct A {/* ... */};
struct B {/* ... */};

class C {
  A *a;
  B *b;
protected:
  void init() noexcept(false);
public:
  C() : a(new A()), b(new B()) {
    init();
  }
};

Compliant Solution (try/catch)

This compliant solution mitigates the potential failures by releasing a and b if an exception is thrown during their allocation or during init().

struct A {/* ... */};
struct B {/* ... */};
 
class C {
  A *a;
  B *b;
protected:
  void init() noexcept(false);
public:
  C() : a(nullptr), b(nullptr) {
    try {
      a = new A();
      b = new B();
      init();
    } catch (...) {
      delete a;
      delete b;
      throw;
    }
  }
};

Compliant Solution (std::unique_ptr)

This compliant solution uses std::unique_ptr to create objects that clean up after themselves should anything go wrong in the C::C() constructor. The std::unique_ptr applies the principles of RAII to pointers.

#include <memory>
 
struct A {/* ... */};
struct B {/* ... */};

class C {
  std::unique_ptr<A> a;
  std::unique_ptr<B> b;
protected:
  void init() noexcept(false);
public:
  C() : a(new A()), b(new B()) {
    init();
  }
};

Risk Assessment

Memory and other resource leaks will eventually cause a program to crash. If an attacker can provoke repeated resource leaks by forcing an exception to be thrown through the submission of suitably crafted data, then the attacker can mount a denial-of-service attack.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

ERR57-CPP

Low

Probable

High

P2

L3

Automated Detection

Tool

Version

Checker

Description

CodeSonar
8.1p0

ALLOC.LEAK

Leak
Helix QAC

2024.1

DF4756, DF4757, DF4758


Klocwork
2024.1

CL.MLK
MLK.MIGHT
MLK.MUST
MLK.RET.MIGHT
MLK.RET.MUST
RH.LEAK


LDRA tool suite
9.7.1

 

50 D

Partially implemented

Parasoft C/C++test
2023.1

CERT_CPP-ERR57-a

Ensure resources are freed

Polyspace Bug Finder

R2023b

CERT C++: ERR57-CPP

Checks for:

  • Resource leak caused by exception
  • Object left in partially initialized state
  • Bad allocation in constructor

Related Vulnerabilities

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

Related Guidelines

Bibliography

[Cline 2009]

Question 17.2, I'm still not convinced: A 4-line code snippet shows that return-codes aren't any worse than exceptions;
why should I therefore use exceptions on an application that is orders of magnitude larger?

[ISO/IEC 14882-2014]Subclause 15.2, "Constructors and Destructors"
[Meyers 1996]Item 9, "Use Destructors to Prevent Resource Leaks"
[Stroustrup 2001]"Exception-Safe Implementation Techniques"



5 Comments

  1. I'm quite disturbed by all those compliant solutions that use catch.

    First, thanks to RAII we don't need to use catch except where we know what to do with the error. Thus the first current first RAII solution sounds strange to me as it does two things : 1- release the resource(s), 2- handle the error. In typical RAII code, (1) appears a lot while (2) seldom appears in comparison. It reminds me this entry from the new C++ FAQ: https://isocpp.org/wiki/faq/exceptions#too-many-trycatch-blocks A typical code will more likely look like the one in https://isocpp.org/wiki/faq/exceptions#exceptions-avoid-spreading-out-error-logic

    The second thing is while all these solutions using null-initialized pointers are correct for the current organisation of the code, they don't scale, and this is an important limitation. The more resources there are, the more complex the code becomes. I really like this the demonstration done in this post. While we can find a try-catch solution without RAII that works at one point in time, this solution may no longer work if we reorganize the execution flow or if we introduce a new resource. BTW, not all resources are pointers, and knowing the current situation may require more code (e.g. (depending on the mutex type) unlocking an unlocked POSIX mutex is sometimes an undefined behaviour – and we cannot test whether the mutex is null).


    TL;DR IMO, we should annotate non-RAII solutions as solutions that don't scale. Thing which is contrary to maintenance objective.

    1. I generally agree with your points. Specifically, we should be pointing people to writing secure code that is also clean and maintainable whenever possible. However, one of the goals of the secure coding standards is to show people how to correct existing code bases with minimal modifications, which doesn't always result in clean or maintainable code, despite improving the security. I like the idea of using some form of annotation to clarify when a CS still has some undesirable properties, but only if that doesn't also turn the secure coding standard into a style guide. I'm not certain of what that annotation would look like, but the next time we get a round of funding to work on the rules, that might be an idea worth exploring further.

      Thus the first current first RAII solution sounds strange to me as it does two things ...

      You spotted a think-o in the first RAII solution, which I'll correct. It should not be re-throwing the exception because the exception is being handled (according to the comment). Thank you for pointing that out!

      1. Regarding the annotation, the first compliant solution has list of disadvantages. I was thinking of adding something like "beware, while the solution complies to the rule on the current code, adding resources and failures paths will make it hard to maintain".

         

        I understand the minimal modifications objective. Regarding memory leaks, I start to have the impression that I waste less time in refactoring to encapsulate raw resources in RAII capsules than juggling with try-catches.

        1. > Regarding the annotation, the first compliant solution has list of disadvantages. I was thinking of adding something like "beware, while the solution complies to the rule on the current code, adding resources and failures paths will make it hard to maintain".

          I was hoping to find something that doesn't add a lot of length to the content, because I suspect we will want to use a similar annotation in many places. Something more obvious than a third background color for the code blocks but less obvious than additional sentences. If I could find a way to get Confluence to add a text box on the right-hand side of a code block (like a two-column format for the code block), that would be ideal – we waste a lot of horizontal space with code examples, and it might be nice to have an area for call-out text coupled with a third background color that stands for "conforming to the rule, but read the call-out text for more information."

          > I understand the minimal modifications objective. Regarding memory leaks, I start to have the impression that I waste less time in refactoring to encapsulate raw resources in RAII capsules than juggling with try-catches.

          I can sympathize with that perspective. Writing C++ code that is correct in the face of exceptions is nontrivial, but RAII certainly helps to greatly reduce the pain.

      2. The rethrow was important to demonstrating the issues, but the previous "handle error" comments made it sound like rethrowing was the wrong thing to do. I solved it by changing the comments to not imply the error has been handled.