Versions Compared

Key

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

The pointer-to-member operators .* and ->* are used to obtain an object or a function as though it were a member of an underlying object. For instance, the following are functionally equivalent ways to call the member function f() on the object o.

Code Block
struct S {
  void f() {}
};

void func() {
  S o;
  void (S::*pm)() = &S::f;
  
  o.f();
  (o.*pm)();
}

The call of the form o.f() uses class member access at compile time to look up the address of the function S::f() on the object o. The call of the form (o.*pm)() uses the pointer-to-member operator .* to call the function at the address specified by pm. In both cases, the object o is the implicit this object within the member function S::f().

The C++ Standard, [expr.mptr.oper], paragraph 4 [ISO/IEC 14882-2014], states the following:

Abbreviating pm-expression.*cast-expression as E1.*E2, E1 is called the object expression. If the dynamic type of E1 does

C++ 2003, Section 5.5 "Pointer-to-member operators", paragraph 4, says:

If the dynamic type of the object does not contain the member to which E2 refers, the pointer refers, the behavior is undefined.

A pointer-to-member expression of the form E1->*E2 is converted to its equivalent form, (*(E1)).*E2, so use of pointer-to-member expressions of either form behave equivalently in terms of undefined behavior.

Further, the C++ Standard, [expr.mptr.oper], paragraph 6, in part, states the following:

If the second operand is the null pointer to member value, the behavior is undefined.

So, trying to Do not use a pointer-to-member operator to access a non-existent member leads to undefined behavior and must be avoided.

...

member expression where the dynamic type of the first operand does not contain the member to which the second operand refers, including the use of a null pointer-to-member value as the second operand.

Noncompliant Code Example

In this non-compliant noncompliant code example there is an abstract base class Shape and a derived class Circle that contains a member function area. The last line of the code following the class definitions results in undefined behavior because there is no member function corresponding to area() in the class Shape, a pointer-to-member object is obtained from D::g but is then upcast to be a B::*. When called on an object whose dynamic type is D, the pointer-to-member call is well defined. However, the dynamic type of the underlying object is B, which results in undefined behavior.

Code Block
bgColor#FFcccc
languagecpp
classstruct ShapeB {
   // abstract
  // ...
public:
virtual ~B() = default;
};

struct D : B {
  virtual void draw~D() = 0default;
  //virtual void g() { /* ... */ }
};

class Circle : public Shape {
  double radius;
public:
  Circle(double new_radius) : radius(new_radius) {}   
  void draw() {
    // ...
  }
  virtual double areavoid f() {
  B *b = new B;
 
  // ...
 
  void (B::*gptr)() = static_cast<void(B::*)()>(&D::g);
  (b->*gptr)();
  delete b;
}

Compliant Solution

In this compliant solution, the upcast is removed, rendering the initial code ill-formed and emphasizing the underlying problem that B::g() does not exist. This compliant solution assumes that the programmer's intention was to use the correct dynamic type for the underlying object.

Code Block
bgColor#ccccff
languagecpp
struct B {
  virtual ~B() = default;
};

struct D : B {
  virtual ~D() = default;
  virtual void g() { /* ... */ }
};

void f() {
  B *b return PI*radius*radius;
  }
};

= new D; // Corrected the dynamic object type.
 
  // ...

Shape *circ = newvoid Circle(2.0);
double(Shape(D::*circ_areagptr)() = static_cast<double(Shape::*)()>(&Circle::area);
cout << "Area: " << (circ->*circ_area)() << endl;

Compliant Solution (Modifiable Base Class)

If the developer is able to change the base class when it is realized that the area() method is required in the derived class, then a pure virtual area() method should be added to the class Shape:

&D::g; // Moved static_cast to the next line.
  (static_cast<D *>(b)->*gptr)();
  delete b;
}

Noncompliant Code Example

In this noncompliant code example, a null pointer-to-member value is passed as the second operand to a pointer-to-member expression, resulting in undefined behavior.

Code Block
bgColor#FFcccc
languagecpp
struct B {
  virtual ~B() = default;
};

struct D : B {
  virtual ~D() = default
Code Block
bgColor#ccccff
class Shape {  // abstract
  // ...
public:
  virtual void draw() = 0;
  virtual void areag() = 0;
 { /* ... */ }
};
 
static void (D::*gptr)(); // Not ...
}

Compliant Solution (Non-modifiable Base Class)

explicitly initialized, defaults to nullptr.
void call_memptr(D *ptr) {
  (ptr->*gptr)();
}
 
void f() {
  D *d = new D;
  call_memptr(d);
  delete d;
}

Compliant Solution

In this compliant solution, gptr is properly initialized to a valid pointer-to-member value instead of to the default value of nullptrIn many cases, the base class is not modifiable. In this case, one must call the derived method directly.

Code Block
bgColor#ccccff
languagecpp
struct B {
  virtual ~B() = default;
};
 
struct D : B {
  virtual ~D() = default;
  virtual void g() { /* ... */ }
};
 
static void (D::*gptr)() = &D::g; // Explicitly initialized.
void call_memptr(D *ptr) {
  (ptr->*gptr)();
}
 
void f() {
  D *d = new D;
  call_memptr(d);
  delete d;
}Circle *circ = new Circle(2.0);
cout << "Area: " << (circ->area)() << endl;

Risk Assessment

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

OBJ38

OOP55-CPP

Medium

High

Probable

Medium

High

P8

L2

P6

L2

Automated Detection

Tool

Version

Checker

Description

Astrée

Include Page
Astrée_V
Astrée_V

overflow_upon_dereference
invalid_function_pointer

Axivion Bauhaus Suite

Include Page
Axivion Bauhaus Suite_V
Axivion Bauhaus Suite_V

CertC++-OOP55
CodeSonar
Include Page
CodeSonar_V
CodeSonar_V

LANG.MEM.UVAR

Uninitialized Variable

Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

DF2810, DF2811, DF2812, DF2813, DF2814


Klocwork
Include Page
Klocwork_V
Klocwork_V

CERT.OOP.PTR_MEMBER.NO_MEMBER


Parasoft C/C++test
Include Page
Parasoft_V
Parasoft_V
CERT_CPP-OOP55-a

A cast shall not convert a pointer to a function to any other pointer type, including a pointer to function type

Parasoft Insure++

Runtime detection
Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C++: OOP55-CPPChecks for pointers to member accessing non-existent class members (rule fully covered).

Related Vulnerabilities

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

Related Guidelines

This rule is a subset of EXP34-C. Do not dereference null pointers.

Bibliography

...

...

Subclause 5.5, "Pointer-to-Member Operators"


...

Image Added  Image Added Image Added member operators"OOP37-CPP. Write constructor member initializers in the canonical order      013. Object Oriented Programming (OOP)      014. Concurrency (CON)