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,E1is called the object expression. If the dynamic type ofE1does not contain the member to whichE2refers, 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.
Do not use a pointer-to-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 noncompliant code example, 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
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 the pointer refers, the behavior is undefined.
So, trying to use a pointer-to-member operator to access a non-existent member leads to undefined behavior and must be avoided.
Non-Compliant Code Example
In this non-compliant 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.
| Code Block | ||||
|---|---|---|---|---|
| ||||
struct B { virtual ~B() = default; }; struct D : B { virtual ~D() = default; virtual void g() { /* ... */ } }; void f() { B *b = new B; class Shape { // abstract // ... public: virtual void draw() = 0; // ... }; class Circlevoid (B: public Shape { double radius; public: Circle(double new_radius) : radius(new_radius) {} void draw() { // ... } virtual double area:*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 | ||||
|---|---|---|---|---|
| ||||
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 =void new 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 | ||||
|---|---|---|---|---|
| ||||
struct B {
virtual ~B() = default;
};
struct D : B {
virtual ~D() = default | ||||
| Code Block | ||||
| ||||
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 | ||||
|---|---|---|---|---|
| ||||
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 | Detectable |
|---|
Repairable | Priority | Level |
|---|
OOP55-CPP |
High | Probable |
No | No |
P8
L2
P6 | L2 |
Automated Detection
Tool | Version | Checker | Description | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Astrée |
| overflow_upon_dereference invalid_function_pointer | |||||||
| Axivion Bauhaus Suite |
| CertC++-OOP55 | |||||||
| CodeSonar |
| LANG.MEM.UVAR | Uninitialized Variable | ||||||
| Helix QAC |
| DF2810, DF2811, DF2812, DF2813, DF2814 | |||||||
| Klocwork |
| CERT.OOP.PTR_MEMBER.NO_MEMBER | |||||||
| Parasoft C/C++test |
| 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 |
| CERT C++: OOP55-CPP | Checks 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
...
| 2014] |
...
Subclause 5.5, "Pointer-to-Member Operators" |
...
member operators"OOP37-CPP. Constructor initializers should be ordered correctly 13. Object Oriented Programming (OOP) 14. Concurrency (CON)