Can ODR violation be avoided by using hidden visibility?

Viewed 204

So for example, I have a slightly complicated case of library dependency in one of my projects:


              /--------------------------------\
              |                                |
      /----> GRPC <------------------\         |
      |                              |         |
      |        (c++)                 |         |        
      \-------   A  -------------->  B         |
                 |                 (rust)      |
                 |                             |
                 \------------------> c++  <---/ 

Rust by default will prefer to use static linkage. Executable A is also built to statically link lib(std)c++. So, to my understanding, there will be two copies of STL implementation in both A and B. This is exactly the pattern that https://developer.android.com/ndk/guides/cpp-support#sr suggests avoiding.

However, looking through the dynamic linkage table (via nm -D, for example) of B; I could see no exported lib(std)c++/grpc symbol. This is because rust marks them hidden by default.

  • So, is it safe (or conforming the ODR) if all common symbols in B are hidden?
2 Answers

conforming the ODR

One-definition rule is part of C++ programming language. It's relevant to C++ language. It's irrelevant to anything else. There is no ODR "outside" of C++. C++ standard does not apply outside of C++, it's only about C++ programming language.

There is no wildly adopted portable super-standard concerning language interoperability. These are just tools, there are no definitions and rules. ODR or any other rule from C++ does not apply here.

Trying to apply C++ standard rules to unrelated contexts makes little sense. Rust is not part of C++. RPC is platform specific, outside the scope of C++ programming language.

Ergo, reasoning that some C++ rule will or will not be broken in a work chain that uses platform specific tools - a shared library, "dynamic linkage table" - and uses multiple programming language just doesn't apply here.

In the sense of C++, this all is literally "undefined behavior" - there are no rules from C++ standard that could apply here.

Can ODR violation be avoided by using hidden visibility?

Sure.

The android doc says:

In this situation, the STL, including and global data and static constructors, will be present in both libraries. The runtime behavior of this application is undefined, and in practice crashes are very common. Other possible issues include:

Memory allocated in one library, and freed in the other, causing memory leakage or heap corruption.

You mentioned those symbols are hidden. However, is it equal to, the global data is not presented twice?

If the global data can be presented twice, then "Memory allocated in one library, and freed in the other, causing memory leakage or heap corruption" may happen. For example, if some memory is allocated in Rust, and we transfer it to C++, and C++ later frees it, then we are facing this situation. Of course your code may not have this case, but if some code in the future violates, or if one day you use a third party library which violates it, then we are in trouble (and seems to be hard to debug).

Indeed, what about letting C++ to statically link the Rust code? Then you get a giant .so file containing your C++, your Rust, your dependency, etc. However, even if you can do that, we may still need to be careful: Is the giant .so file the only one in your whole Android app? In other words, are you sure you do not have, and will never have any other native libraries? If not, IMHO we may still be facing the problem again.

Anyway I am not an expert in Android/C++. I was having a similar problem a few months ago (replace "C++ code" with "the Flutter engine which is written in C++" and so on) and made some workaround. So this post is not really an answer, but rather some (can be wrong) thoughts and suggestions. Hope someone can correct me!

Related