2014-09-24 - turtling ODR

in one of the project i use turtle mock framework. after having quite a bit of experience with google mock i was a bit of a pain to get used to its compilation times – 2x longer, for simple scenarios and gets worse with size. but this was the framework of choice in the project, so i have to live with it.

today i found another interesting thing – nice, silent way to break ODR. i spent few hours trying to figure out why my type, which has operator« defined, does not print out in the failure case of unexpected call to mock. as one might expect there is quite a lot of macros and templates inside, so it is not trivial to “parse in mind”.

while trying to create a minimum set, that would reproduce the problem i've noticed that commenting out a single mock method from a totally different file… i magically get my print back, as expected! my operator however worked smoothly, when called directly (say: cout«myInstance). after doing a bit of digging i've noticed, that the framework does this for logging content of a failed expectations:

namespace detail3
{
template< typename S, typename T >
S& operator<<( S &s, const T& ) // (2)
{
  return s << '?';
}
}
 
template< typename T >
stream& operator<<( stream& s, const T& t ) // (1)
{
  using namespace detail3;
  *s.s_ << t;
  return s;
}

T is your parameter type. at first glance it looks like a very clever thing to do: if there is an operator« defined for T, if will be used ((1) – Koening's rule) – otherwise it will fallback to outputting ? on the screen (2).

now what if, you happen to have your class Foo defined in file Foo.hpp but operator«(Foo) in FooOp.hpp? now you can have some tests including both Foo.hpp and FooOp.hpp, while some just Foo.hpp. it all compiles and links w/o any warnings or errors. but now, the ODR is broken, as the same overload (1) calls user-defined operator« in some compilation units, while overload (2) in others. since breaking ODR is an UB in C++, linker can deal with it however it pleases to. so it did.

in my case it (probably) just took the very first implementation it came across and used it everywhere, resulting in very surprised me, having ? on the screen, instead of my object's content, nicely formatted! the fix was to include FooOp.hpp as well, in every test, that used Foo. unfortunately both Foo.hpp and FooOp.hpp are generated so it is non-trivial to ensure they are always include, so the possible errors can lurk everywhere. :/

even though i'd love to just remove problematic overload (2), i can't! there is just too much code that depends on it. once it is in place, ppl use it w/o being aware of it. thanks to this we're pretty much stuck with this bug-source. on the other hand, if this overload was not there in a first place, if someone would try to use custom object with mock, it would simply not compile. no way to break ODR this way + enforced pretty-printing of objects.

lesson learned – when writing generic code, one must be extremely careful. it would be also nice to have some sort of ODR detection mechanism in place. it would cost an extra time, so it should be off by default, but if one could turn it on by a compiler/linker flag, who knows how many surprises like this could be detected? i'm sure my case was not alone…

blog/2014/09/24/turtling_odr.txt · Last modified: 2021/06/15 20:08 by 127.0.0.1
Back to top
Valid CSS Driven by DokuWiki Recent changes RSS feed Valid XHTML 1.0