Weakness in C++ RTTI comparison

Originator:jdgadina
Number:rdar://45821794 Date Originated:November 5 2018, 11:50 PM
Status:Open Resolved:
Product:macOS + SDK Product Version:
Classification:Serious Bug Reproducible:
 
Summary:

It looks like there's a weakness in the implementation of C++ RTTIs (run-time type information) when compiling a large C++ project, linking many static libraries, with compile-time and link-time optimization.

Issue happens within `std::type_info::operator==`.
Note that this operator is well defined by the current C++ standard, and should return true for equivalent types.

The issue happened in a project in which the operator returned false when comparing `typeid( std::string )`, issued from some compilation unit, with `typeid( std::string )` from another compilation unit.

I wasn't able to reproduce this in a test project unfortunately.
But looking at the generated assembly code, I can definitely see a weakness here.

Steps to Reproduce:

    const std::type_info & getT1( void )
    {
        return typeid( std::string );
    }

    const std::type_info & getT2( void )
    {
        return typeid( std::string );
    }

    int main( void )
    {
        const std::type_info & t1( getT1() );
        const std::type_info & t2( getT2() );
    
        if( t1 == t2 )
        {
            return 0;
        }
    
        return -1;
    }

Expected Results:

Function `main` should obviously return 0.

Actual Results:

It turns out `std::type_info::operator==` cannot always be trusted when used in different static libraries.

Generated assembly code:

     1:    push       rbp
     2:    mov        rbp, rsp
     3:    sub        rsp, 0x30
     4:    mov        dword [rbp+var_14], 0x0
     5:    call       __Z5getT1v
     6:    mov        qword [rbp+var_20], rax
     7:    call       __Z5getT2v
     8:    mov        qword [rbp+var_28], rax
     9:    mov        rax, qword [rbp+var_20]
    10:    mov        rcx, qword [rbp+var_28]
    11:    mov        qword [rbp+var_8], rax
    12:    mov        qword [rbp+var_10], rcx
    13:    mov        rax, qword [rbp+var_8]
    14:    mov        rax, qword [rax+8]
    15:    mov        rcx, qword [rbp+var_10]
    16:    cmp        rax, qword [rcx+8]

`getT1` is called at line 5 and `getT2` at line 7.
At line 16, we can see a comparison with a member at offset 8 of the two returned objects.

This member is actually the mangled name of the C++ type, like:

    NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE

Problem is the comparison uses the addresses, not the value.
If for any reason, due to optimization or linking, the same type resolves to a different address, comparison will fail.

Since the implementation is using the mangled name for comparing `std::type_info` objects, it should then compare the values, not the addresses.

I encountered this issue in a large C++ project.
Comparing two `std::type_info` returned false, although the `name` value was identical.

I found two similar issues:
 - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=23628
 - https://svn.boost.org/trac10/ticket/754

Both seem to occur with GCC, but looks like Clang has the same issue.

Comments

I should add that the issue only happened in release builds. Debug builds were working as expected. Release builds were compiled with -Os and monolithic linking.


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!