parts/6.20.IdentifierNameReuse-YOW.md

6.20 Identifier Name Reuse [YOW]

6.20.1 Applicability to language

The vulnerability as described in ISO/IEC TR 24772-1:2019 clause 6.20 exists in C++, except for the second issue of limited identifier length. In C++ all characters in an identifier are significant.

C++ provides the scope resolution operator ::{.cpp} to access identifiers from non-local scopes.

Overloading and specialization of functions is a cornerstone of C++ generic programming. In this context, the reuse of function names is essential. See clause 6.41 for inheritance issues associated with name reuse.

Overloaded function names and operators considered in an expression are not restricted to a simple scope hierarchy, because of argument-dependent lookup (ADL). In generic code the unqualified function or operator selected can come from a scope based on the type of the arguments and not from the current scope hierarchy. The rules for which namespaces are eligible for lookup of unqualified functions and operators are intricate, but required to make overloaded operators work.

In addition, if implicit conversions can happen on arguments, the overload selected by ADL can be different from programmer expectation even in non-generic code, especially when an argument is of a type that can be implicitly converted to another type where a corresponding overload is defined. Visibility on a namespace-level of such an operator overload may make it eligible, even if neither argument matches the parameter types directly. In the best case this leads to a compile error due to ambiguities, but it can also result in perfectly compiling code executing an unexcepted overload.

The following example demonstrates part of the problem:

#include <iostream>
#include <typeinfo>

namespace Y {
template <typename T>
void print(T i){
    std::cout << typeid(T).name()<< ":" << i ;
}
template <typename T>
void println(T x){
    print(x); // expects to call Y::print
    std::cout<<'\n';
}
} 
namespace X {
   struct A{
        A(double){}
        friend // make this a hidden friend
        std::ostream & operator << (std::ostream & out, A const &a){
            return out << "An A as expected\n";
        } 
    };
    void print(A a){ // not expected to be called by println
        std::cout << "Surprise happens!";
    }
}
int main(){
    X::A a{3.14};
    Y::println(42); // i:42 - calls Y::print
    std::cout << a; // An A as expected - calls X::operator<<
    Y::println(a);  // Surprise happens! - calls X::print
    Y::println(42u);// u:42 - calls Y::print
}

The above code calls the overload print(A) from println since it is pulled in by ADL. On the other hand, ADL is required to work to allow the output operator for type X::A to work.

The consideration of implicit conversions together with ADL can be suppressed by defining operator overloads as class members or as hidden friends. The latter is achieved by declaring all corresponding overloads as friend functions in the class that take the class’ objects as arguments. Generic base classes can provide mix-in facilities for hidden friends by taking the argument type that is the derived class as template parameter.

6.20.2 Avoidance mechanismsfor language users

To avoid the vulnerability or mitigate its ill effects, C++ software developers can: