mettle icon indicating copy to clipboard operation
mettle copied to clipboard

Static initialization order fiasco with inline all_suites on Windows + Clang

Open polter-rnd opened this issue 7 months ago • 1 comments
trafficstars

I'm continuing my experiments with mettle on different platforms by incorporating into CI.

Recently found a weird issue - on Windows with Clang, all tests are silently skipped and the output is always:

0/0 tests passed

This is caused by a static initialization order issue, due to how the variable inline suites_list all_suites is defined in all_suites.hpp:

  • Test suites in mettle are usually registered as static variables;
  • The global all_suites vector is defined as inline;
  • On some platforms (notably Clang on Windows), these static test suite variables are initialized before the constructor of all_suites runs.
  • Once the all_suites constructor runs, it clears its internal list.

The result: test suites register themselves before all_suites is constructed → later all_suites constructor wipes everything.

According to the C++ standard (basic.start.static §6.9.3.2/2):

Variables with static storage duration defined in the same translation unit shall be initialized in the order in which their definitions appear in the translation unit.

However, inline variables with external linkage may be emitted into a separate object file section, and on some toolchains like Windows+Clang, they are initialized after statics, violating the expected intra-TU order.

polter-rnd avatar Apr 22 '25 08:04 polter-rnd

From my read of the standard, this seems like a compiler bug. See §6.9.3.3 p6:

It is implementation-defined whether the dynamic initialization of a non-block inline variable with static storage duration is sequenced before the first statement of main or is deferred. If it is deferred, it strongly happens before any non-initialization odr-use of that variable. It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs.

Since the suite constructor odr-uses all_suites, I think the standard says that the latter must be dynamically-initialized before the former.

jimporter avatar Apr 27 '25 22:04 jimporter