CertC++-OOP55¶
Do not use pointer-to-member operators to access nonexistent members
Required inputs: IR, StaticSemanticAnalysis
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.
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.
struct B {
virtual ~B() = default;
};
struct D : B {
virtual ~D() = default;
virtual void g() { /* ... */ }
};
void 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.
struct B {
virtual ~B() = default;
};
struct D : B {
virtual ~D() = default;
virtual void g() { /* ... */ }
};
void f() {
B *b = new D; // Corrected the dynamic object type.
// ...
void (D::*gptr)() = &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.
struct B {
virtual ~B() = default;
};
struct D : B {
virtual ~D() = default;
virtual void g() { /* ... */ }
};
static void (D::*gptr)(); // Not 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
nullptr.
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;
}
Risk Assessment
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| OOP55-CPP | High | Probable | High | P6 | L2 |
Related Guidelines
This rule is a subset of EXP34-C. Do not dereference null pointers.
Bibliography
| [ ISO/IEC 14882-2014] | Subclause 5.5, "Pointer-to-Member Operators" |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
non-existing |
Calling a non-existing member via a pointer-to-member call. |
None |
False |
uninitialized |
Uninitialized pointer-to-member called. |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
pointer_target_limit¶
pointer_target_limit : int | None = 5