Main Content

CERT C++: EXP54-CPP

Do not access an object outside of its lifetime

Description

Rule Definition

Do not access an object outside of its lifetime.1

Polyspace Implementation

The rule checker checks for these issues:

  • Non-initialized pointer.

  • Non-initialized variable.

  • Use of previously freed pointer.

  • Pointer or reference to stack variable leaving scope.

  • Accessing object with temporary lifetime.

Extend Checker

You can extend the checker in the following ways:

Examples

expand all

Issue

Non-initialized pointer occurs when a pointer is not assigned an address before dereference.

Risk

Unless a pointer is explicitly assigned an address, it points to an unpredictable location.

Fix

The fix depends on the root cause of the defect. For instance, you assigned an address to the pointer but the assignment is unreachable.

Often the result details show a sequence of events that led to the defect. You can implement the fix on any event in the sequence. If the result details do not show the event history, you can trace back using right-click options in the source code and see previous related events. See also Interpret Bug Finder Results in Polyspace Desktop User Interface.

See examples of fixes below. It is a good practice to initialize a pointer to NULL when declaring the pointer.

If you do not want to fix the issue, add comments to your result or code to avoid another review. See:

Example - Non-initialized pointer error
#include <stdlib.h>

int* assign_pointer(int* prev)
{
    int j = 42;
    int* pi;

    if (prev == nullptr) 
      {
        pi = new int;
        if (pi == nullptr) return NULL;
      }
    *pi = j;  //Noncompliant

    return pi;
}

If prev is not nullptr, the pointer pi is not assigned an address. However, pi is dereferenced on every execution paths, irrespective of whether prev is nullptr or not.

Correction — Initialize Pointer on Every Execution Path

One possible correction is to assign an address to pi when prev is not nullptr. Alternatively, initialize pi as a nullptr during its declaration.

#include <cstdlib>

int* assign_pointer(int* prev)
{
    int j = 42;
/*Fix: Initialize pointers by using nullptr during declaration*/
    int* pi = nullptr;

    if (prev == NULL) 
       {
        pi = new int;
        if (pi == nullptr) return NULL;
       } 
    /* Fix: Initialize pi in branches of if statement  */
    else 
        pi = prev;              
    *pi = j;
    return pi;
}
Issue

Non-initialized variable occurs when a variable is not initialized before its value is read.

Risk

Unless a variable is explicitly initialized, the variable value is unpredictable. You cannot rely on the variable having a specific value.

Fix

The fix depends on the root cause of the defect. For instance, you assigned a value to the variable but the assignment is unreachable or you assigned a value to the variable in one of two branches of a conditional statement. Fix the unreachable code or missing assignment.

Often the result details show a sequence of events that led to the defect. You can implement the fix on any event in the sequence. If the result details do not show the event history, you can trace back using right-click options in the source code and see previous related events. See also Interpret Bug Finder Results in Polyspace Desktop User Interface.

See examples of fixes below. It is a good practice to initialize a variable at declaration.

If you do not want to fix the issue, add comments to your result or code to avoid another review. See:

Example - Non-initialized variable error
int get_sensor_value(void)
{
    extern int getsensor(void);
    int command;
    int val;

    command = getsensor();
    if (command == 2) 
      {
        val = getsensor();
      }

    return val; //Noncompliant
}

If command is not 2, the variable val is unassigned. In this case, the return value of function get_sensor_value is undetermined.

Correction — Initialize During Declaration

One possible correction is to initialize val during declaration so that the initialization is not bypassed on some execution paths.

int get_sensor_value(void)
{
    extern int getsensor(void);
    int command;
    /* Fix: Initialize val */
    int val=0;

    command = getsensor();
    if (command == 2) 
      {
        val = getsensor();
      }

    return val;              
 }

val is assigned an initial value of 0. When command is not equal to 2, the function get_sensor_value returns this value.

Issue

Use of previously freed pointer occurs when you access a block of memory after deallocating the block, for instance, by using the free function or the delete operator.

Risk

When a pointer is allocated dynamic memory by using the functions malloc, calloc, realloc or the operator new, it points to a memory location on the heap. When you use the free function or the delete operator on this pointer, the associated block of memory is deallocated. Trying to access this block of memory can result in unpredictable behavior or even a segmentation fault.

Fix

The fix depends on the root cause of the defect. See if you intended to deallocate the memory later or allocate another memory block to the pointer before access.

As a good practice, after you deallocate a memory block, assign the corresponding pointer to nullptr. Before dereferencing pointers, check if they are nullptr and handle the error. In this way, you are protected against accessing a deallocated block.

Example - Use of Previously Freed Pointer Error
#include <cstdlib>
int increment_content_of_address(int base_val, int shift)
   { 
    int j;
    int* pi = new int;
    if (pi == NULL) return 0;

    *pi = base_val;
    delete pi;

    j = *pi + shift; //Noncompliant
 
    return j;
   }

The delete operator deallocates the block of memory that pi refers to. Therefore, dereferencingpi after the delete pi; statement is not valid.

Correction — Deallocate Pointer After Use

One possible correction is to deallocate the pointer pi only after the last instance where it is accessed.

#include <cstdlib>

int increment_content_of_address(int base_val, int shift)
{
    int j;
    int* pi = new int;
    if (pi == NULL) return 0;

    *pi = base_val;

    j = *pi + shift;
    *pi = 0;

    /* Fix: The pointer is deallocated after its last use */
    delete pi;               
    return j;
}
Correction — Use std::unique_ptr

Another possible correction is to use a std::unique_ptr instead of a raw pointer. Smart pointers such as std::unique_ptr manages their own resources. because you don't have to deallocate smart pointers explicitly, they are not inadvertently accessed after deallocation.

#include <cstdlib>
#include <memory>

int increment_content_of_address(int base_val, int shift)
{
    int j;
    /* Fix: A smart pointer is used*/
    std::unique_ptr<int>   pi(new int(3));
    if (pi == nullptr) return 0;

    *pi = base_val;

    j = *pi + shift;
    *pi = 0;
    return j;
}
Issue

Pointer or reference to stack variable leaving scope occurs when a pointer or reference to a local variable leaves the scope of the variable. For instance:

  • A function returns a pointer to a local variable.

  • A function performs the assignment globPtr = &locVar. globPtr is a global pointer variable and locVar is a local variable.

  • A function performs the assignment *paramPtr = &locVar. paramPtr is a function parameter that is, for instance, an int** pointer and locVar is a local int variable.

  • A C++ method performs the assignment memPtr = &locVar. memPtr is a pointer data member of the class the method belongs to. locVar is a variable local to the method.

The defect also applies to memory allocated using the alloca function. The defect does not apply to static, local variables. Polyspace assumes that the local objects within a function definition are in the same scope.

Risk

Local variables are allocated an address on the stack. Once the scope of a local variable ends, this address is available for reuse. Using this address to access the local variable value outside the variable scope can cause unexpected behavior.

If a pointer to a local variable leaves the scope of the variable, Polyspace Bug Finder™ highlights the defect. The defect appears even if you do not use the address stored in the pointer. For maintainable code, it is a good practice to not allow the pointer to leave the variable scope. Even if you do not use the address in the pointer now, someone else using your function can use the address, causing undefined behavior.

Fix

Do not allow a pointer or reference to a local variable to leave the variable scope.

Example - Pointer to Local Variable Returned from Function
void func2(int *ptr) {
    *ptr = 0;
}

int* func1(void) {
    int ret = 0; //Noncompliant
    return &ret ;
}
void main(void) {
    int* ptr = func1() ;
    func2(ptr) ;
}

In this example, func1 returns a pointer to local variable ret.

In main, ptr points to the address of the local variable. When ptr is accessed in func2, the access is illegal because the scope of ret is limited to func1,

Example - Pointer to Local Variable Escapes Through Lambda Expression
auto createAdder(int amountToAdd) {
  int addThis = amountToAdd; //Noncompliant
  auto adder = [&] (int initialAmount) {
      return (initialAmount + addThis);
  };
  return adder;
}
 
void func() {
  auto AddByTwo = createAdder(2);
  int res = AddByTwo(10);
}

In this example, the createAdder function defines a lambda expression adder that captures the local variable addThis by reference. The scope of addThis is limited to the createAdder function. When the object returned by createAdder is called, a reference to the variable addThis is accessed outside its scope. When accessed in this way, the value of addThis is undefined.

Correction – Capture Local Variables by Copy in Lambda Expression Instead of Reference

If a function returns a lambda expression object, avoid capturing local variables by reference in the lambda object. Capture the variables by copy instead.

Variables captured by copy have the same lifetime as the lambda object, but variables captured by reference often have a smaller lifetime than the lambda object itself. When the lambda object is used, these variables accessed outside scope have undefined values.

auto createAdder(int amountToAdd) {
  int addThis = amountToAdd;
  auto adder = [=] (int initialAmount) {
      return (initialAmount + addThis);
  };
  return adder;
}
 
void func() {
  auto AddByTwo = createAdder(2);
  int res = AddByTwo(10);
}
Issue

Accessing object with temporary lifetime occurs when you attempt to read from or write to an object with temporary lifetime that is returned by a function call. In a structure or union returned by a function, and containing an array, the array members are temporary objects. The lifetime of temporary objects ends:

  • When the full expression or full declarator containing the call ends, as defined in the C11 Standard.

  • After the next sequence point, as defined in the C90 and C99 Standards. A sequence point is a point in the execution of a program where all previous evaluations are complete and no subsequent evaluation has started yet.

For C++ code, Accessing object with temporary lifetime raises a defect only when you write to an object with a temporary lifetime.

If the temporary lifetime object is returned by address, no defect is raised.

Risk

Modifying objects with temporary lifetime is undefined behavior and can cause abnormal program termination and portability issues.

Fix

Assign the object returned from the function call to a local variable. The content of the temporary lifetime object is copied to the variable. You can now modify it safely.

Example - Modifying Temporary Lifetime Object Returned by Function Call
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define SIZE6 6

struct S_Array
{
    int t;
    int a[SIZE6];
};

struct S_Array func_temp(void);

/* func_temp() returns a struct value containing
* an array with a temporary lifetime.
*/
int func(void) {
 
/*Writing to temporary lifetime object is
 undefined behavior
 */
    return ++(func_temp().a[0]); //Noncompliant
}

void main(void) {
    (void)func();
}

In this example, func_temp() returns by value a structure with an array member a. This member has temporary lifetime. Incrementing it is undefined behavior.

Correction — Assign Returned Value to Local Variable Before Writing

One possible correction is to assign the return of the call to func_temp() to a local variable. The content of the temporary object a is copied to the variable, which you can safely increment.

 #include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define SIZE6 6

struct S_Array
{
    int t;
    int a[SIZE6];
};

struct S_Array func_temp(void);

int func(void) {

/* Assign object returned by function call to 
 *local variable
 */
    struct S_Array s = func_temp(); 

/* Local variable can safely be
 *incremented
 */
    ++(s.a[0]);                                           
    return s.a[0];
}

void main(void) {
    (void)func();
}

Check Information

Group: 02. Expressions (EXP)

Version History

Introduced in R2019a

expand all


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.