Versions Compared

Key

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

...

Do not use an object outside of its lifetime, except in the ways described above as being well-defined.

Noncompliant Code Example

In this noncompliant code example, a pointer to an object is used, prior to its lifetime starting, to call a non-static member function of the object, resulting in undefined behavior:

Code Block
bgColor#FFCCCC
langcpp
struct S {
  void mem_fn();
};
 
void f() {
  S *s;
  s->mem_fn();
}

Compliant Solution

In this compliant solution, storage is obtained for the pointer prior to calling S::mem_fn():

...

An improved compliant solution would not dynamically allocate memory directly, but would instead use an automatic local variable to obtain the storage and perform initialization. If a pointer was required, use of a smart pointer like std::unique_ptr would be a marked improvement. However, these suggested compliant solutions would distract from the lifetime demonstration of this compliant solution, and are consequently not shown.

Noncompliant Code Example

In this noncompliant code example, a pointer to an object is implicitly converted to a virtual base class after the object's lifetime has ended, resulting in undefined behavior:

...

Despite the fact that f() never makes use of the object, the fact that it is passed as an argument to f() is sufficient to trigger undefined behavior.

Compliant Solution

In this compliant solution, the lifetime of s is extended to cover the call to f():

Code Block
bgColor#ccccff
langcpp
struct B {};
 
struct D1 : virtual B {};
struct D2 : virtual B {};
 
struct S : D1, D2 {};
 
void f(const B *b) {}
 
void g() {
  S *s = new S;
  // Use s
  f(s);
 
  delete s;
}

Noncompliant Code Example

In this noncompliant code example, the address of a local variable is returned from f(). When the resulting pointer is passed to h(), the lvalue-to-rvalue conversion applied to i results in undefined behavior:

...

Some compilers generate a diagnostic message when a pointer to an object with automatic storage duration is returned from a function, as in this example.

Compliant Solution

In this compliant solution, the local variable returned from g() has static storage duration instead of automatic storage duration, extending its lifetime sufficiently for use within f():

Code Block
bgColor#ccccff
langcpp
int *g() {
  static int i = 12;
  return &i;
}
 
void h(int *i);
 
void f() {
  int *i = g();
  h(i);
}

Noncompliant Code Example

In this noncompliant code example, the function g() returns a lambda which captures the automatic local variable i by reference. When that lambda is returned from the call, the reference it captured will refer to a variable whose lifetime has ended. As a result, when the lambda is executed in f(), the use of the dangling reference in the lambda results in undefined behavior. As a general rule of thumb, functions returning lambdas should not capture by reference. 

Code Block
bgColor#FFcccc
langc
auto g() {
  int i = 12;
  return [&] {
    i = 100;
    return i;
  };
}

void f() {
  int i = g()();
}

Compliant Solution

In this compliant solution, the lambda does not capture i by reference, but instead captures it by copy. Consequently, the lambda contains an implicit data member named i whose lifetime is that of the lambda.

Code Block
bgColor#ccccff
langc
auto g() {
  int i = 12;
  return [=] () mutable {
    i = 100;
    return i;
  };
}

void f() {
  int i = g()();
}

Noncompliant Code Example

std::initializer_list<> object is constructed from an initializer list as if the implementation allocated a temporary array and passed it to the std::initializer_list<> constructor. This temporary array has the same lifetime as other temporary objects, except that initializing a std::initializer_list<> object from the array extends the lifetime of the array exactly like binding a reference to a temporary [ISO/IEC 14882-2014]. In this noncompliant code example, a member variable of type std::initializer_list<int> is list initialized within the constructor's ctor-initializier. Under these circumstances, the conceptual temporary array's lifetime ends once the constructor exits, and so accessing any elements of the std::initializer_list<int> member variable results in undefined behavior.

Code Block
bgColor#FFcccc
langc
#include <initializer_list>
#include <iostream>

class C {
  std::initializer_list<int> L;
  
public:
  C() : L{1, 2, 3} {}
  
  int first() const { return *L.begin(); }
};

void f() {
  C c;
  std::cout << c.first();
}

Compliant Solution

In this compliant solution, the std::initializer_list<int> member variable is replaced with a std::vector<int>, which copies the elements of the initializer list to the container instead of relying on a dangling reference to the temporary array.

Code Block
bgColor#ccccff
langc
#include <initializer_list>
#include <iostream>
#include <vector>
 
class C {
  std::vector<int> L;
  
public:
  C() : L{1, 2, 3} {}
  
  int first() const { return *L.begin(); }
};
 
void f() {
  C c;
  std::cout << c.first();
}

Risk Assessment

Referencing an object outside of its lifetime can result in an attacker being able to run arbitrary code.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

EXP34-CPP

High

Probable

High

P6

L2

Automated Detection

Tool

Version

Checker

Description

Clang
Include Page
Clang_V
Clang_V

-Wdangling-initializer-list

Catches some lifetime issues related to incorrect use of std::initializer_list<>

Related Vulnerabilities

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

Related Guidelines

Bibliography

[ISO/IEC 14882-2014]3.8, "Object Lifetime"
8.5.4, "List-Initialization" 
[Coverity 2007] 

...