Main Content

AUTOSAR C++14 Rule A3-8-1

An object shall not be accessed outside of its lifetime

Since R2020b

Description

Rule Definition

An object shall not be accessed outside of its lifetime.

Rationale

The lifetime of an object begins when it is created by its constructor. The lifetime ends when the object is deleted. Accessing a variable before its construction or after its destruction can lead to undefined behavior. Depending on the context, many operations might inadvertently access an object outside its lifetime. Examples of such operations include:

  • Noninitialized pointer: You might inadvertently access a pointer before assigning an address to it. This operation accesses an object before its lifetime and results in accessing an unpredictable memory location. The best practice is to initiate a pointer by using nullptr during its declaration.

  • Noninitialized variable: You might inadvertently read a variable before it is initialized. This operation accesses an object before its lifetime and results in reading a garbage value that is unpredictable and useless. The best practice is to initiate a variable during its declaration.

  • Use of previously deallocated pointer: You might access the dynamically allocated memory of a pointer after deallocating the memory. Trying to access this block of memory accesses an object after its lifetime and results in unpredictable behavior or even a segmentation fault. To address this issue, set the deallocated pointer to nullptr, and then to check if a pointer is nullptr before accessing it. Alternatively, use a std::unique_ptr instead of a raw pointer. Because you do not need to deallocate the allocated memory for a std::unique_ptr explicitly, you can avoid inadvertently accessing the deallocated memory.

  • Pointer or reference to stack variable leaving scope: You might assign a nonlocal pointer to a local object. For instance:

    • A nonlocal or global pointer is assigned to a variable that is local to a function.

    • A passed-by-reference function parameter, such as a pointer, is assigned to a variable that is local to a function.

    • A pointer data member of a class is assigned to a variable that is local to a function.

    Once the local variable goes out of scope, their corresponding memory blocks might hold garbage or unpredictable values. Accessing pointers to these memory locations accesses an object after its lifetime and might result in undefined or unpredictable behavior. The best practice is to not assign nonlocal pointers to local objects.

  • Modifying object with temporary lifetime: You might attempt to modify a temporary object returned by a function call. Modifying temporary objects is an undefined behavior that might lead to abnormal program termination depending on the hardware and software that you use. The best practice is to assign the temporary objects in local variables, and then modifying the local variables.

Avoid operations that might access an object outside of its lifetime.

Polyspace Implementation

Polyspace® checks for these scenarios where an object might be accessed outside of its lifetime:

  • Noninitialized pointer: Polyspace flags a pointer if it is not assigned an address before it is accessed.

  • Noninitialized variable: Polyspace flags a variable if it is not initialized before its value is read.

  • Use of previously deallocated pointer: Polyspace flags an operation where you access a block of memory after deallocating the block, for instance, by using the free() function or the delete operator.

  • Pointer or reference to stack variable leaving scope: Polyspace flags a local variable when a pointer or reference to it leaves its scope. For example, a local variable is flagged when:

    • A function returns a pointer to the local variable

    • A global pointer is pointed to the local variable

    • A pass-by-reference function parameter, such as a pointer, is pointed to the local variable

    • A pointer data member of a class is pointed to the local variable

    Polyspace assumes that the local objects within a function definition are in the same scope.

  • Accessing object with temporary lifetime: Polyspace flags an operation where you access a temporary object that is returned by a function call.

Extend Checker

You can extend the checker in the following ways:

Troubleshooting

If you expect a rule violation but Polyspace does not report it, see Diagnose Why Coding Standard Violations Do Not Appear as Expected.

Examples

expand all

This example shows how Polyspace flags accessing pointers that are not assigned to an address.

#include <cstdlib>

int* Noncompliant(int* prev)
{
	int j = 42;
	int* pi;
	if (prev == nullptr){
		pi = new int;
		if (pi == nullptr) 
		return nullptr;
	}
	*pi = j; //Noncompliant                    
	return pi;
}
int* Compliant(int* prev)
{
	int j = 42;
	int* pi;
	if (prev == nullptr){
		pi = new int;
		if (pi == nullptr)
		return nullptr;
	} 
	else 
	pi = prev;              
	*pi = j;//Compliant
	return pi;
}
int* AltCompliant(int* prev)
{
	int j = 42;
	int* pi=nullptr;
	if (prev == nullptr){
		pi = new int;
		if (pi == nullptr)
		return nullptr;
	} 
	else              
	if(pi!= nullptr) 
	*pi = j;//Compliant
	return pi;
}

Polyspace flags the pointer pi in Noncompliant() because pi is accessed before an address is assigned to it when prev is not NULL. You can address this issue in various ways. For instance:

  • Initiate pi before the statement *pi = j. Assignment to pi in Compliant() is not flagged because pi is initiated by prev before it is accessed.

  • Initiate pi by using nullptr during its declaration. Assignment to pi in AltCompliant() is not flagged because pi is initiated by nullptr during its declaration.

This example shows how Polyspace flags accessing noninitialized variables.

int Noncompliant(void)
{
	extern int getsensor(void);
	int command;
	int val;
	command = getsensor();
	if (command == 2){
		val = getsensor();
	}
	return val;//Noncompliant              
	
}
int Compliant(void)
{
	extern int getsensor(void);
	int command;
	int val=0;//Initialization
	command = getsensor();
	if (command == 2){
		val = getsensor();
	}
	return val;//Compliant              
}

Polyspace flags the statement return val in Noncompliant() because this statement accesses val before the variable is initialized when command is not equal to 2. You can address this issue in several ways. For instance, initialize the variable val to zero during its declaration, as shown in Compliant(). By initializing the variable during declaration, it is initialized in all execution paths, making the statement return val compliant with this rule.

This example shows how Polyspace flags accessing pointers that might point to already released memory blocks.

#include <memory>
int Noncompliant(double base_val, double shift){ 
	double j;
	double* pi = new double;
	if (pi == nullptr) 
	return 0;
	*pi = base_val;
	//...
	delete pi;
	//...
	j = *pi + shift;//Noncompliant
	return j;
}
int Compliant(double base_val, double shift){
	double j;
	std::unique_ptr<double>   pi(new double(3.1416));
	if (pi == nullptr) 
	return 0;
	*pi = base_val;
	j = *pi + shift;              
	return j;
}

In the function Noncompliant(), a pointer pi is declared and initialized by using the operator new. Later, the dynamically allocated memory is deallocated by using the operator delete. The deallocated pointer is then inadvertently accessed in the statement j = *pi + shift;. Polyspace flags this statement. You can address this issue in various ways. For instance, you might want to deallocate the allocated resource after performing all relevant operations. Alternatively, you can use smart pointers instead of raw pointers. In Compliant(), the pointer pi is declared as a std::unique_ptr. The acquired resources for pi are automatically deallocated at the end of Compliant() by calling its destructor. Because the memory allocated for pi is not accessed after it is deallocated, Compliant() is compliant with this rule.

This example shows how Polyspace flags operations where pointers to local variables might escape to outer scopes.

int* Noncompliant1(void) {
	int ret = 0; //Noncompliant
	return &ret ; 
}
auto Noncompliant2(int var) {
	int rhs = var; //Noncompliant
	auto adder = [&] (int lhs) {
		return (rhs + lhs);
	};
	return adder; 
}
int Compliant1(void) {
	int ret = 0; //Compliant
	return ret ; 
}
auto Compliant2(int var) {
	int rhs = var; //Compliant
	auto adder = [=] (int lhs) {
		return (rhs + lhs);
	};
	return adder; 
}
  • The function Noncompliant1() returns a pointer to the local variable ret. The local variable ret is deleted as soon as Noncompliant() finishes execution. The returned pointer points to an unpredictable value. Such operations are noncompliant with the rule. You can fix this issue by returning local variables by value, as shown in Compliant().

  • The function Noncompliant2() returns a lambda expression, which captures the local variable rhs by reference. This reference dereferences to an unpredictable value because rhs is deleted when the function Noncompliant2() finishes execution. You can fix this issue by capturing local variables by copy in the lambda expression, as shown in Compliant2().

This example shows how Polyspace flags operations that might access temporary objects that are created by a function call.

#include<vector>
struct S_Array{
	int t;
	int a[5];
};
struct S_Array Factory(void);
std::vector<int> VectorFactory(int aNumber);
int Noncompliant(void) {

	return ++(Factory().a[0]); //Noncompliant
}
int Compliant(void) {
	auto tmp = Factory();
	return ++(tmp.a[0]); //Compliant
}
int Compliant2(void) {
	return ++(VectorFactory(5)[1]); //Compliant
}

In Noncompliant(), the call to Factory() creates a temporary object. Modifying this object is noncompliant with this rule. Polyspace flags the statement return ++(Factory().a[0]). You can address this issue in various ways. For instance, you can assign the temporary object to a local variable before modifying it, as shown in Compliant(). Alternatively, use smart containers such as std::vector as shown in Compliant2(). Containers such as std::vector manage their own lifetime and have move semantics. Polyspace does not flag the statement return ++(VectorFactory(5)[1]);.

Check Information

Group: Basic concepts
Category: Required, Non-automated

Version History

Introduced in R2020b

expand all