ut
ut copied to clipboard
reporter_junit causes segfault with clang 16
Expected Behavior
Clang 16 seems to be in the set of supported compilers and UT should work there generally.
Actual Behavior
When testing with clang 16 I experienced segfaults for all UT tests.
Small reproducer on compiler explorer: https://godbolt.org/z/dvxezoTsP (exit code 139 is the container way of reporting segfault), locally I get a segfault.
The debugger points to the redirection of the output stream buffers as a possible cause, but I didn't manage to fix it and it seems to me that all buffers get reconfigured correctly. Maybe someone else sees what the problem is.
Workaround
Falling back to the more basic reporter resolves the issue (but of course looses a lot of nice features).
#if defined(__clang__) && __clang_major__ >= 16
// clang 16 does not like ut's default reporter_junit due to some issues with stream buffers and output redirection
template <>
auto boost::ut::cfg<boost::ut::override> = boost::ut::runner<boost::ut::reporter<>>{};
#endif
Specifications
- Version: master
- Platform: linux + clang 16.0.1
- Subsystem: reporter
I noticed this on Clang 17 and Visual C++ 2022 as well. I think it has to do with the passing of argc and argv.
I made the above draft PR, which is not a real solution, but it does work and shows where the problem is.
What I noticed in terms of the issue:
struct cfg {
using value_ref = std::variant<std::monostate, std::reference_wrapper<bool>,
std::reference_wrapper<std::size_t>,
std::reference_wrapper<std::string>>;
using option = std::tuple<std::string, std::string, value_ref, std::string>;
static inline reflection::source_location location{};
static inline bool wip{};
#if defined(_MSC_VER)
static inline int largc = __argc;
static inline const char** largv = const_cast<const char**>(__argv);
#else
static inline int largc = 0;
static inline const char** largv = nullptr;
#endif
The above snippet sets largc and largv based on semi-supported compiler-dependent methods (__argv, __argc). Note that just removing the MSC_VER part (and always setting to null/nullptr) actually does not solve the issue.
Then there is this part which instantiates reporter_junit runner by default:
template <class = override, class...>
//[[maybe_unused]] inline auto cfg = runner<reporter<printer>>{};// alt reporter
[[maybe_unused]] inline auto cfg = runner<reporter_junit<printer>>{};
And in the reporter_junit constructor, the following happens:
reporter_junit() : lcout_(std::cout.rdbuf()) {
::boost::ut::detail::cfg::parse(detail::cfg::largc, detail::cfg::largv);
So it takes the (statically declared above) largc and largv and passes them to parse, all during globals initialization.
And parse:
static inline void parse(int argc, const char* argv[]) {
const std::size_t n_args = 0; // static_cast<std::size_t>(argc);
if (n_args > 0 && argv != nullptr) {
cfg::largc = argc;
cfg::largv = argv;
executable_name = argv[0];
}
Takes these and stores them back into largc and largv. The argv[0] access is where the segfault seems to be.
I don't know if I understood this correctly, but it seems a bit strange. Likely the issue itself comes as a result of unexpected order of initialization of statics/globals and/or the __argc and __argv compiler-specific features.
Then
I'm running into this issue on clang-cl 17 on x86-64 Windows. I cannot use the library because of it.
For me, without any arguments passed, detail::cfg::largc is set to -1 which causes the std::size_t cast to convert it into a very large number, so it passes the > 0 check. argv is left uninitialized (in my case, always an invalid pointer, but not 0) and passes the nullptr check, causing me to crash.
In my testing, it seems like reading __argc and __argv inside the parse function itself, does give me the correct values. But when they're global static variables, they don't get filled in correctly. This would make executable_name fill in the correct value.
It does indeed seem like something weird is going on with the CRT or these function aren't intended to be used this way, but I'm unsure what's going on here and it will require me to debug further.
EDIT: Right, I'm just blind and didn't see this: https://github.com/boost-ext/ut/commit/3cfd9937e1d4c9375f7d7904a5ba6a1926f7161f I will give it a try.
This seems to be fixed by either clang 17 and/or the commit mentioned in the previous commit, see compiler explorer. I still have some issues where enabling the junit reporter clashes with some other code which also works with some static initializers, but the fundamental issues seems to be resolved, so I'll close this for now.