7

I'm trying to use clang-tidy to enforce the C++ Core Guidelines. While it does have a lot of valid points, there is one thing I cannot really work around: dlsym returns a void* which I need to turn into a proper function pointer somehow. To do that I use reinterpret_cast. Since the guidelines forbid it, I have warnings about it. Of course I can put //NOLINT comments everywhere, but I'm looking for a solution that doesn't use reinterpret_cast so the warnings go away.

Are there any workarounds for this problem?

Calmarius
  • 18,570
  • 18
  • 110
  • 157
  • No way. It's a well known problem. – n. m. could be an AI Mar 26 '19 at 17:23
  • 1
    Just casting from `void*` to a function pointer type is sketchy from a language perspective. Object pointers and function pointers are not compatible types and aren't guaranteed to even have the same size. Though any compiler for platforms that provides `dlsym` have to contend with that in some way. The fact that a function that returns function pointers does so through `void*` is just overall problematic. – François Andrieux Mar 26 '19 at 17:25
  • @FrançoisAndrieux casting from object pointers to function pointers and vice versa is *conditionally supported*. If a platform offers `dlopen`, it probably suports such casting. – n. m. could be an AI Mar 26 '19 at 17:30
  • 1
    Create a wrapper for `dlsym`, so you'll only need to use `//NOLINT` once in the wrapper. – geza Mar 26 '19 at 17:42
  • 1
    @FrançoisAndrieux the standard requires that *if* an implementation supports a conditionally-supported feature *then* it must obey certain rules about it. Yes "shall yield the original pointer value" is such a rule. – n. m. could be an AI Mar 26 '19 at 17:48
  • You *could* use C-style cast... but I expect that also violates guidelines and is a worse solution than simply not enforcing the guidelines. They are only guidelines rather than language rules for a reason. – eerorika Mar 26 '19 at 17:48
  • Interestingly, it seems that GCC does not document the behavior of `reinterpret_cast` in this case. At least there seems to be nothing [here](https://gcc.gnu.org/onlinedocs/gcc/Conditionally-supported-behavior.html#Conditionally-supported-behavior)? Does anyone know of any such documentation? Because, technically, a lack of such documentation would make GCC a non-conforming implementation!? – Michael Kenzel Mar 26 '19 at 18:00
  • @KonradRudolph Unfortunately, that won't help here as `static_cast` cannot perfom the cast required… – Michael Kenzel Mar 26 '19 at 18:13
  • @MichaelKenzel: you can consider GCC's source code as documentation :) At least, I don't see that the standard specifies what a documentation is. – geza Mar 26 '19 at 18:14
  • @geza good point! I was wondering where to look, especially since this is something that will potentially vary depending on target architecture… – Michael Kenzel Mar 26 '19 at 18:15
  • @MichaelKenzel Bloody function pointers. – Konrad Rudolph Mar 26 '19 at 18:16
  • @KonradRudolph indeed – Michael Kenzel Mar 26 '19 at 18:16
  • With two type-casts: `(function *)(intptr_t)dlsym(...)` – Lorinczy Zsigmond Mar 27 '19 at 03:31

1 Answers1

8

There is no other way in the language to cast a function pointer type to an object pointer type except for reinterpret_cast. Doing so is implementation-defined behavior [expr.reinterpret.cast]/8:

Converting a function pointer to an object pointer type or vice versa is conditionally-supported. The meaning of such a conversion is implementation-defined, except that if an implementation supports conversions in both directions, converting a prvalue of one type to the other type and back, possibly with different cv-qualification, shall yield the original pointer value.

That means that a conforming C++ compiler must document if it does not support this feature. And, if it does support it, it must document how exactly it behaves. You can rely on it working (or not being available) in the documented way on that compiler.

Concerning the Core Guidelines linting: If you would have to put //NOLINT "everywhere", then that would seem to imply that you're calling naked dlsym() in many places. Consider wrapping it, for example

template <typename T>
inline T* lookupSymbol(void* module, const char* name)
{
    auto symbol = reinterpret_cast<T*>(dlsym(module, name));  // NOLINT

    if (!symbol)
        throw std::runtime_error("failed to find symbol '"s + name + '\'');

    return symbol;
}
Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
  • @KonradRudolph It seems that the paragraph quoted above was introduced in C++11, so it would seem to me that at least these kinds of contortions should not be required anymore… – Michael Kenzel Mar 26 '19 at 18:30
  • I added `using pT = T*;` in the function and then I can use function style cast like `pT(dlsym(module, name))` and clang-tidy doesn't bite for that. – Calmarius Jul 30 '19 at 12:01
  • @Calmarius Well, that's just a very roundabout way of doing the same thing. Your function-style cast in this case is defined to be equivalent to a C-style cast [\[expr.type.conv/2\]](http://eel.is/c++draft/expr.type.conv#2), which is defined to be equivalent to a `reinterpret_cast` in this case [\[expr.cast/4\]](http://eel.is/c++draft/expr.cast#4)… – Michael Kenzel Sep 28 '19 at 00:59