Skip to content

Commit bc792a2

Browse files
authored
[libc++] Encode additional ODR-affecting properties in the ABI tag (#69669)
As explained in `__config`, we have an ABI tag that we use to ensure that we don't run into ODR issues when mixing different versions of libc++ in multiple TUs. However, the reasoning behind that extends not only to different versions of libc++, but also to different configurations of the same version of libc++. In fact, we've been aware of this for a while but never really bothered to make the change because ODR issues are often thought to be benign. Well, it turns out that I just spent over an hour banging my head against an issue that boils down to our lack of encoding of some ODR properties in the ABI tag, so here's the patch we should have done a long time ago. For now, the ODR properties we encode in the ABI tag are: - library version - exceptions vs no-exceptions - hardening mode Those are all things that we support different values for on a per-TU basis and they definitely affect ODR in a meaningful way. We can add more properties later as we see fit.
1 parent 560bad0 commit bc792a2

File tree

3 files changed

+161
-15
lines changed

3 files changed

+161
-15
lines changed

libcxx/include/__config

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,6 @@
5656
# define _LIBCPP_CONCAT_IMPL(_X, _Y) _X##_Y
5757
# define _LIBCPP_CONCAT(_X, _Y) _LIBCPP_CONCAT_IMPL(_X, _Y)
5858

59-
// Valid C++ identifier that revs with every libc++ version. This can be used to
60-
// generate identifiers that must be unique for every released libc++ version.
61-
# define _LIBCPP_VERSIONED_IDENTIFIER _LIBCPP_CONCAT(v, _LIBCPP_VERSION)
62-
6359
# if __STDC_HOSTED__ == 0
6460
# define _LIBCPP_FREESTANDING
6561
# endif
@@ -734,22 +730,54 @@ typedef __char32_t char32_t;
734730
# define _LIBCPP_EXCLUDE_FROM_EXPLICIT_INSTANTIATION _LIBCPP_ALWAYS_INLINE
735731
# endif
736732

733+
# if _LIBCPP_ENABLE_HARDENED_MODE
734+
# define _LIBCPP_HARDENING_SIG h
735+
# elif _LIBCPP_ENABLE_SAFE_MODE
736+
# define _LIBCPP_HARDENING_SIG s
737+
# elif _LIBCPP_ENABLE_DEBUG_MODE
738+
# define _LIBCPP_HARDENING_SIG d
739+
# else
740+
# define _LIBCPP_HARDENING_SIG u // for unchecked
741+
# endif
742+
743+
# ifdef _LIBCPP_HAS_NO_EXCEPTIONS
744+
# define _LIBCPP_EXCEPTIONS_SIG n
745+
# else
746+
# define _LIBCPP_EXCEPTIONS_SIG e
747+
# endif
748+
749+
# define _LIBCPP_ODR_SIGNATURE \
750+
_LIBCPP_CONCAT(_LIBCPP_CONCAT(_LIBCPP_HARDENING_SIG, _LIBCPP_EXCEPTIONS_SIG), _LIBCPP_VERSION)
751+
737752
// This macro marks a symbol as being hidden from libc++'s ABI. This is achieved
738753
// on two levels:
739754
// 1. The symbol is given hidden visibility, which ensures that users won't start exporting
740755
// symbols from their dynamic library by means of using the libc++ headers. This ensures
741756
// that those symbols stay private to the dynamic library in which it is defined.
742757
//
743-
// 2. The symbol is given an ABI tag that changes with each version of libc++. This ensures
744-
// that no ODR violation can arise from mixing two TUs compiled with different versions
745-
// of libc++ where we would have changed the definition of a symbol. If the symbols shared
746-
// the same name, the ODR would require that their definitions be token-by-token equivalent,
747-
// which basically prevents us from being able to make any change to any function in our
748-
// headers. Using this ABI tag ensures that the symbol name is "bumped" artificially at
749-
// each release, which lets us change the definition of these symbols at our leisure.
750-
// Note that historically, this has been achieved in various ways, including force-inlining
751-
// all functions or giving internal linkage to all functions. Both these (previous) solutions
752-
// suffer from drawbacks that lead notably to code bloat.
758+
// 2. The symbol is given an ABI tag that encodes the ODR-relevant properties of the library.
759+
// This ensures that no ODR violation can arise from mixing two TUs compiled with different
760+
// versions or configurations of libc++ (such as exceptions vs no-exceptions). Indeed, if the
761+
// program contains two definitions of a function, the ODR requires them to be token-by-token
762+
// equivalent, and the linker is allowed to pick either definition and discard the other one.
763+
//
764+
// For example, if a program contains a copy of `vector::at()` compiled with exceptions enabled
765+
// *and* a copy of `vector::at()` compiled with exceptions disabled (by means of having two TUs
766+
// compiled with different settings), the two definitions are both visible by the linker and they
767+
// have the same name, but they have a meaningfully different implementation (one throws an exception
768+
// and the other aborts the program). This violates the ODR and makes the program ill-formed, and in
769+
// practice what will happen is that the linker will pick one of the definitions at random and will
770+
// discard the other one. This can quite clearly lead to incorrect program behavior.
771+
//
772+
// A similar reasoning holds for many other properties that are ODR-affecting. Essentially any
773+
// property that causes the code of a function to differ from the code in another configuration
774+
// can be considered ODR-affecting. In practice, we don't encode all such properties in the ABI
775+
// tag, but we encode the ones that we think are most important: library version, exceptions, and
776+
// hardening mode.
777+
//
778+
// Note that historically, solving this problem has been achieved in various ways, including
779+
// force-inlining all functions or giving internal linkage to all functions. Both these previous
780+
// solutions suffer from drawbacks that lead notably to code bloat.
753781
//
754782
// Note that we use _LIBCPP_EXCLUDE_FROM_EXPLICIT_INSTANTIATION to ensure that we don't depend
755783
// on _LIBCPP_HIDE_FROM_ABI methods of classes explicitly instantiated in the dynamic library.
@@ -769,7 +797,7 @@ typedef __char32_t char32_t;
769797
# ifndef _LIBCPP_NO_ABI_TAG
770798
# define _LIBCPP_HIDE_FROM_ABI \
771799
_LIBCPP_HIDDEN _LIBCPP_EXCLUDE_FROM_EXPLICIT_INSTANTIATION \
772-
__attribute__((__abi_tag__(_LIBCPP_TOSTRING(_LIBCPP_VERSIONED_IDENTIFIER))))
800+
__attribute__((__abi_tag__(_LIBCPP_TOSTRING(_LIBCPP_ODR_SIGNATURE))))
773801
# else
774802
# define _LIBCPP_HIDE_FROM_ABI _LIBCPP_HIDDEN _LIBCPP_EXCLUDE_FROM_EXPLICIT_INSTANTIATION
775803
# endif
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// TODO: Investigate
10+
// XFAIL: msvc
11+
12+
// Test that we encode whether exceptions are supported in an ABI tag to avoid
13+
// ODR violations when linking TUs that have different values for it.
14+
15+
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -DTU1 -fno-exceptions -o %t.tu1.o
16+
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -DTU2 -fexceptions -o %t.tu2.o
17+
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -DMAIN -o %t.main.o
18+
// RUN: %{cxx} %t.tu1.o %t.tu2.o %t.main.o %{flags} %{link_flags} -o %t.exe
19+
// RUN: %{exec} %t.exe
20+
21+
// -fno-exceptions
22+
#ifdef TU1
23+
# include <__config>
24+
_LIBCPP_HIDE_FROM_ABI inline int f() { return 1; }
25+
int tu1() { return f(); }
26+
#endif // TU1
27+
28+
// -fexceptions
29+
#ifdef TU2
30+
# include <__config>
31+
_LIBCPP_HIDE_FROM_ABI inline int f() { return 2; }
32+
int tu2() { return f(); }
33+
#endif // TU2
34+
35+
#ifdef MAIN
36+
# include <cassert>
37+
38+
int tu1();
39+
int tu2();
40+
41+
int main(int, char**) {
42+
assert(tu1() == 1);
43+
assert(tu2() == 2);
44+
return 0;
45+
}
46+
#endif // MAIN
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// TODO: Remove these UNSUPPORTED lines once we change how hardening is enabled to avoid
10+
// mutually exclusive modes being enabled at the same time.
11+
// UNSUPPORTED: libcpp-hardening-mode=hardened
12+
// UNSUPPORTED: libcpp-hardening-mode=safe
13+
// UNSUPPORTED: libcpp-hardening-mode=debug
14+
15+
// TODO: Investigate
16+
// XFAIL: msvc
17+
18+
// Test that we encode the hardening mode in an ABI tag to avoid ODR violations
19+
// when linking TUs that have different values for it.
20+
21+
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -DTU1 -D_LIBCPP_ENABLE_HARDENED_MODE -o %t.tu1.o
22+
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -DTU2 -D_LIBCPP_ENABLE_SAFE_MODE -o %t.tu2.o
23+
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -DTU3 -D_LIBCPP_ENABLE_DEBUG_MODE -o %t.tu3.o
24+
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -DTU4 -o %t.tu4.o
25+
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -DMAIN -o %t.main.o
26+
// RUN: %{cxx} %t.tu1.o %t.tu2.o %t.tu3.o %t.tu4.o %t.main.o %{flags} %{link_flags} -o %t.exe
27+
// RUN: %{exec} %t.exe
28+
29+
// hardened mode
30+
#ifdef TU1
31+
# include <__config>
32+
_LIBCPP_HIDE_FROM_ABI inline int f() { return 1; }
33+
int tu1() { return f(); }
34+
#endif // TU1
35+
36+
// safe mode
37+
#ifdef TU2
38+
# include <__config>
39+
_LIBCPP_HIDE_FROM_ABI inline int f() { return 2; }
40+
int tu2() { return f(); }
41+
#endif // TU2
42+
43+
// debug mode
44+
#ifdef TU3
45+
# include <__config>
46+
_LIBCPP_HIDE_FROM_ABI inline int f() { return 3; }
47+
int tu3() { return f(); }
48+
#endif // TU3
49+
50+
// unchecked mode
51+
#ifdef TU4
52+
# include <__config>
53+
_LIBCPP_HIDE_FROM_ABI inline int f() { return 4; }
54+
int tu4() { return f(); }
55+
#endif // TU4
56+
57+
#ifdef MAIN
58+
# include <cassert>
59+
60+
int tu1();
61+
int tu2();
62+
int tu3();
63+
int tu4();
64+
65+
int main(int, char**) {
66+
assert(tu1() == 1);
67+
assert(tu2() == 2);
68+
assert(tu3() == 3);
69+
assert(tu4() == 4);
70+
return 0;
71+
}
72+
#endif // MAIN

0 commit comments

Comments
 (0)