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
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!
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.