You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 40 Next »

Self-assignment can occur in situations of varying complexity, but all boil down to some variation of:

#include <utility>
 
struct S { /* ... */ }
 
void f() {
  S s;
  s = s; // Self-copy-assignment
  s = std::move(s); // Self-move-assignment
}

User-provided copy and move assignment operators must properly handle self-assignment.

Copy Assignment

The post-conditions required for copy assignment are specified by the C++ Standard, [utility.arg.requirements], Table 23 [ISO/IEC 14882-2014], which states that for x = y, the value of y is unchanged. When &x == &y, this post-condition translates into the values of both x and y remaining unchanged. A naive implementation of copy assignment will destroy object-local resources in the process of copying resources from the given parameter. If the given parameter is the same object as the local object, the act of destroying object-local resources will invalidate them. The subsequent copy of those resources will be left in an indeterminate state, which violates the post-condition.

A user-provided copy assignment operator must prevent self-copy-assignment from leaving the object in an indeterminate state. This can be accomplished by self-assignment tests, copy-and-swap, or other idiomatic design patterns.

Move Assignment

The post-conditions required for a move assignment are specified by the C++ Standard, [utility.arg.requirements], Table 22, which states that for x = std::move(y), the value of y is left in a valid, but unspecified, state. When &x == &y, this post-condition translates into the values of both x and y remaining in a valid, but unspecified state. Leaving the values in an unspecified state may result in vulnerabilities leading to exploitable code.

A user-provided move assignment operator must prevent self-move-assignment from leaving the object in a valid, but unspecified, state. Akin to copy assignment, this can be accomplished by self-assignment tests, move-and-swap, or other idiomatic design patterns.

Noncompliant Code Example

In this noncompliant code example, the copy and move assignment operators do not protect against self-assignment. If self-copy-assignment occurs, this->S1 is deleted, which results in RHS.S1 also being deleted. The invalidated memory for RHS.S1 is then passed into the copy constructor for S, which can result in dereferencing an invalid pointer. If self-move-assignment occurs, it is dependent on the implementation of std::swap as to whether S1 is left in a valid, but unspecified, state.

#include <new>
#include <utility>
 
struct S { /* ... */ }; // Has nonthrowing copy constructor
 
class T {
  int N;
  S *S1;
 
public:
  T(const T &RHS) : N(RHS.N), S1(RHS.S1 ? new S1(*RHS.S1) : nullptr) {}
  T(T &&RHS) : N(RHS.N), S1(RHS.S1) noexcept { RHS.S1 = nullptr; }
  ~T() { delete S1; }
 
  // ...
 
  T& operator=(const T &RHS) {
    N = RHS.N;
    delete S1;
    S1 = new S(*RHS.S1);
    return *this;
  }
 
  T& operator==(T &&RHS) noexcept {
    N = RHS.N;
    std::swap(S1, RHS.S1);
    return *this;
  }
};

Compliant Solution (Self Test)

This compliant solution guards against self-assignment by testing whether the given parameter is the same as this. If self-assignment occurs, the result of operator= is a noop, otherwise the copy and move proceeds as in the original example.

#include <new>
#include <utility>
 
struct S { /* ... */ }; // Has nonthrowing copy constructor
 
class T {
  int N;
  S *S1;
 
public:
  T(const T &RHS) : N(RHS.N), S1(RHS.S1 ? new S1(*RHS.S1) : nullptr) {}
  T(T &&RHS) : N(RHS.N), S1(RHS.S1) noexcept { RHS.S1 = nullptr; }
  ~T() { delete S1; }

  // ...
 
  T& operator=(const T &RHS) {
    if (this != &RHS) {
      N = RHS.N;
      delete S1;
      try {
        S1 = new S(*RHS.S1);
      } catch (std::bad_alloc &) {
        S1 = nullptr; // For basic exception guarantees
        throw;
      }
    }
    return *this;
  }
 
  T& operator==(T &&RHS) noexcept {
    if (this != &RHS) {
      N = RHS.N;
      std::swap(S1, RHS.S1);
    }
    return *this;
  }
};

Note that this solution does not provide a strong exception guarantee for the copy assignment. Specifically, if an exception is called when evaluating the new expression, this has already been modified. However, this solution does provide a basic exception guarantee because no resources are leaked and all data members contain valid values.

Compliant Solution (Copy and Swap)

This compliant solution avoids self-copy-assignment by accepting the operator= parameter by value instead of by reference, which results in the copy constructor being called prior to entering the assignment operator. The assignment operator is then allowed to simply swap the contents of RHS and *this. This compliant solution is able to provide a strong exception guarantee because the assignment operator will never be called if resource allocation results in an exception being thrown. It avoids self-move-assignment by testing for self-assignment as in the previous compliant solution.

#include <new>
#include <utility>
 
struct S { /* ... */ }; // Has nonthrowing copy constructor
 
class T {
  int N;
  S *S1;
 
public:
  T(const T &RHS) : N(RHS.N), S1(RHS.S1 ? new S1(*RHS.S1) : nullptr) {}
  T(T &&RHS) : N(RHS.N), S1(RHS.S1) noexcept { RHS.S1 = nullptr; }
  ~T() { delete S1; }

  // ...
 
  friend void swap(T &LHS, T &RHS) noexcept {
    using std::swap;
    swap(LHS.N, RHS.N);
    swap(LHS.S1, RHS.S1);
  }
 
  T& operator=(T RHS) noexcept {
    swap(*this, RHS);
    return *this;
  }
 
  T& operator==(T &&RHS) noexcept {
    if (this != &RHS) {
      N = RHS.N;
      std::swap(S1, RHS.S1);
    }
    return *this;
  }
};

Risk Assessment

Allowing a copy assignment operator to corrupt an object could lead to undefined behavior. Allowing a move assignment operator to leave an object in a valid but unspecified state could lead to unexpected program execution.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

OBJ38-CPP

Low

Probable

High

P2

L3

Automated Detection

Tool

Version

Checker

Description

 PRQA QA-C++

 4.4

4072,4073,4075,4076

 

Related Vulnerabilities

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

Related Guidelines

 

 

Bibliography

[ISO/IEC 14882-2014]17.6.3.1, "Template Argument Requirements"
[Henricson 97]Rule 5.12, "Copy assignment operators should be protected from doing destructive actions if an object is assigned to itself"
[Meyers 05]Item 11, "Handle assignment to self in operator=

 


  • No labels