The vulnerability as described in ISO/IEC 24772-1:2024 6.41 is applicable to C++.
In C++, inheritance is defined differently than in most other languages that support inheritance, e.g.: - There is no implicit common base class. - Defining a member with a name that is defined in a base class causes hiding the base class member in the derived class including all overloads with that name. (Full qualification via the base class name is required to access these hidden members.) - Dynamic polymorphism requires the use of references or pointers. - Private virtual member functions can be overridden in derived classes.
A vulnerability similar to namespace issues (see [BJL]) can arise if a new final overrider is defined in one of multiple parents, which can result in a silent change to the semantics of an existing program, as shown in the following example.
struct A {
virtual void foo ();
};
struct B : virtual A { };
struct C : virtual A {
void foo () override; // late addition
};
struct D : B, C
{void bar () {
// calls the late addition
foo ();
} };
The implementation of virtual member functions does not take access into account. This can lead to a potentially surprising situation where a virtual function overrides an otherwise inaccessible base class function. This is quite different than the situation for non-virtual private member functions, which cannot be accessed from outside the declaring class or any friend
s.
Inheriting from a non-polymorphic base class can be used to implement adaptor types, i.e., by limiting or extending the base’s functionality. Using private inheritance can prevent treating the derived type object as a base type object. Multiple inheritance adds an additional dimension since a class can inherit the same base class A
indirectly via different direct base classes and can result in multiple base class sub-objects of type A
, which can result in ambiguous code.
If all classes in a multiple inheritance hierarchy that directly inherit from A
use the keyword virtual
when inheriting from A
, there will be only one base class sub-object of type A
in the most derived object. Inconsistently inheriting from A
with and without virtual
is confusing, because multiple base objects of type A
exist. Even when an ambiguity does not currently exist, refering to a base class member using its qualified name reduces the chance that a future modification silently changes the program’s behaviour through accessing a different member with the same name.
In C++, a name declared in a virtual base and inherited multiple times but hidden on any virtual path is considered to be hidden on all virtual paths. Consequently, a hiding declaration introduced later will silently alter the semantics of code refering to this name. The vulnerability does not arise if a multiply-inherited name is found on at least one non-virtual inheritance path, since the use of the name is diagnosed as ambiguous.
C++ favours value-semantics for copy and move operations as well as destruction that conflicts with object-oriented polymorphic behaviour.
Virtual Destructor: Base classes that define virtual member functions will need to also define a virtual destructor and in addition need to care about the copy and move operations, otherwise deleting a dynamically-allocated derived object via a base class pointer will cause undefined behaviour [EWF].
Slicing: A common failure is to not eliminate implicitly callable copy and move operations in base classes that can lead to accidental copying of a base class sub-object via a base class reference that actually refers to a derived object. Preventing implicit copy and move operations in base classes defining virtual member functions is a common mitigation.
Incomplete Copy: When a derived class defines its own non-defaulted, non-deleted copy or move operations, care must be taken to actually copy and move all base class sub-objects as well. Omitting a base class when defining copy or move constructors means the default construction of a base class object happens. Not invoking a base class assignment in the definition of copy and move assignment operators will cause the base class retaining its previous members and not obtaining the source object’s base members. None of these omissions are a compile error and none are an issue for empty bases.
If a “using-declaration” refers to a constructor of a direct base class, all constructors of that base are candidates for the initialization of the derived class. If a base class constructor is selected to initialize the derived type, any other bases or members of the derived type will be initialized as if by an = default
default constructor. This can leave some members uninitialzed, that can result in undefined behaviour.
If a using-declaration refers to a protected member of a base class, its accessibility can be changed, which destroys the encapsulation of the member.
If a base class overloads operator new
and operator delete
, any derived classes will inherit those operations. If the base class’ operator new
and/or operator delete
assume the size of the objects being allocated are all the size of the base class and they are not all the same size, then this can result in undefined behaviour.
The mechanisms of failure from ISO/IEC 24772-1:2024 clause 6.41 can be mitigated in C++ as follows:
Execution of malicious redefinitions can be prevented by use of final
on each member function to generate compiler diagnostics when overriding is not permitted.
Accidental redefinition can be mitigated by a project mandate to use the override
or final
special identifiers when overriding a virtual member functions.
Accidental failure of redefinition can be prevented by using override
on each member function intended to be redefined to generate compiler diagnostics when overriding does not apply.
Breaking of class invariants can be avoided by proper initialization even with the default constructor and by defining data members private to ensure that the functions with the appropriate functionality are called and thus class invariants are preserved. If copy and move operations are user-defined in a derived class they must ensure to call the corresponding base class operations.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
=delete
(see clause 6.38 Deep vs. Shallow Copying [YAN]).override
whenever overriding a virtual member function.final
to prevent an unexpected overriding of that function deeper in the hierarchy.final
or override
when overriding a member function.protected
or private
whenever appropriate.