Mixing C++17 and C++20 std::string causes segfault writing to .rodata

I’ve recently run into a problem with an executable that links together some object files built with C++17 and others built with C++20. Both the C++17 and the C++20 translation units contain an inline function declaring this variable:

[[clang::no_destroy]] static const std::string empty;

With C++17, the string() constructor isn’t constexpr. The variable symbol and a guard symbol are emitted into .bss, and the variable is initialized on its first use.

With C++20, the string() constructor is constexpr. There is no guard symbol, and the variable symbol is emitted into .rodata.

The static linker deduplicates the variable symbols, picking the one in .rodata arbitrarily, and combines it with the dynamic initialization code (inlined into a caller). When this dynamic initialization code runs, it segfaults writing to .rodata.

I wondered to what extent mixing object files of different C++ language dialects was supported, where libc++ users are concerned. In general, I believe it’s an ODR violation to mix object files where an inlined constructor is sometimes declared constexpr and sometimes not.

If [[clang::no_destroy]] were removed, the variable would require dynamic initialization to register the destructor call, avoiding the problem.

I have a workaround, https://r.android.com/3010579, which wraps the variable in a class whose constructor isn’t constexpr.

Is this the same as Static variable inside inline function emitted in a different section group than the function · Issue #72361 · llvm/llvm-project · GitHub?

Yes, that looks like the exact same issue. Thanks for the link! I can follow up there.