[Boost-bugs] [Boost C++ Libraries] #10899: symbol visibility: cannot catch an exception thrown by boost::throw_exception on mac OS

Subject: [Boost-bugs] [Boost C++ Libraries] #10899: symbol visibility: cannot catch an exception thrown by boost::throw_exception on mac OS
From: Boost C++ Libraries (noreply_at_[hidden])
Date: 2014-12-26 12:56:32


#10899: symbol visibility: cannot catch an exception thrown by
boost::throw_exception on mac OS
-------------------------------------------------+-------------------------
 Reporter: Sébastien Barthélémy <barthelemy@…> | Owner:
     Type: Bugs | emildotchevski
Milestone: To Be Determined | Status: new
  Version: Boost 1.55.0 | Component: exception
 Keywords: | Severity: Problem
-------------------------------------------------+-------------------------
 == The problem ==

 The problem showed up with an exception from boost::property_tree, but I
 believe it is more general.

 I have a library (named liba in the attached example) which
 * is built with hidden symbols visibility
 * uses boost::property_tree in a `deserialize()` function
 * catch `boost::property_tree::ptree_bad_path` exception if any (so this
 is not an "exception crossing dll boundary issue")

 This usually works (test_a works on all platforms) except when

 * we're on mac os
 * the `deserialize()` function is called from another library which also
 uses boost::property_tree

 In such a case, liba fails to catch the
 `boost::property_tree::ptree_bad_path` exception (test_b fails on mac os,
 but passes on linux).

 So, my problem is that on mac os, liba behaves differently depending on
 whether it's caller uses boost::property_tree or not.

 == A tentative explanation ==

 I think the problem comes from symbol visibility inconsistency:

 * because of boost::throw_exception, the exception which is thrown is not
 a `boost::property_tree::ptree_bad_path` but a subclass
 `boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>`

 * in `include/boost/exception/exception.hpp`, `error_info_injector`
 visibility is forced to be public (this change comes from #4594)
   {{{
 #if defined(__GNUC__)
 # if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4)
 # pragma GCC visibility push (default)
 # endif
 #endif
         template <class T>
         struct
         error_info_injector:
             public T,
             public exception
             {
             explicit
             error_info_injector( T const & x ):
                 T(x)
                 {
                 }

             ~error_info_injector() throw()
                 {
                 }
             };
    }}}

 * However, `boost::property_tree::ptree_bad_path` is still hidden:
   * there is no visibility #pragma in
 `include/boost/property_tree/exceptions.hpp` forcing it to be public
   * I build the library with hidden symbols by default
   * I want to catch the exception within the library, so I do not need to
 make the symbol public.


 What seems to happen on mac (see the typeid adresses) when running test_b:

 * symbol
 `boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>`
 is global, and ''the version from libb is used''.

 * symbol `boost::property_tree::ptree_bad_path` is private, each lib uses
 its own version

 * within liba,
 `boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>`
 is thrown, but liba fails to upcast it to its own version of
 `boost::property_tree::ptree_bad_path`, so it catches it as std::exception
 instead (and throw it again)

 * then within libb, the exception is caught again, but this time casting
 it to libb's version `boost::property_tree::ptree_bad_path` works.

 When calling the `serialize()` function from test_a (which does not use
 boost::property_tree), there is no symbol confusion and the exception is
 caught within liba, as expected.

 If this is right, the root cause is that one class is hidden and the other
 not. If we make both exception classes public or private, then it works as
 expected.

 I'm not sure why test_b passes on linux, it may have something to do with
 weak symbols: nm shows the public symbols as weak on linux, but not on
 mac.

 == Building and running on mac ==

 {{{
 $ make clean
 rm -f src/a.o src/liba.so src/test_a.o src/test_a src/b.o src/libb.so
 src/test_b.o src/test_b src/*.swp *.swp src/.a* src/.b*

 $ make all
 c++ -c -g -Iinclude -o src/test_a.o src/test_a.cc
 c++ -c -g -fPIC -Da_EXPORTS -Iinclude
 -I/Users/hcuche/.local/share/qi/toolchains/mac64/boost/include -o src/a.o
 src/a.cc
 c++ -shared -Wl -o src/liba.so src/a.o
 clang: warning: unknown warning option '-Wl'
 c++ -Lsrc -o src/test_a src/test_a.o -la
 c++ -c -g -Iinclude -o src/test_b.o src/test_b.cc
 c++ -c -g -fPIC -fvisibility=hidden -Db_EXPORTS -Iinclude
 -I/Users/hcuche/.local/share/qi/toolchains/mac64/boost/include -o src/b.o
 src/b.cc
 c++ -shared -Wl -o src/libb.so src/b.o src/liba.so
 clang: warning: unknown warning option '-Wl'
 c++ -Lsrc -o src/test_b src/test_b.o -lb -la

 $ DYLD_LIBRARY_PATH=./src ./src/test_a
 A::deserialize typeid(boost::property_tree::ptree_bad_path): 0x1085f2ed0
 A::deserialize
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x1085f2f00
 A::deserialize caught ptree_bad_path with typeid 0x1085f32d0
 test_a: OK got "A::deserialize caught ptree_bad_path"

 $ DYLD_LIBRARY_PATH=./src ./src/test_b
 B::load typeid(boost::property_tree::ptree_bad_path): 0x10515f3d0
 B::load
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x10515f400
 A::deserialize typeid(boost::property_tree::ptree_bad_path): 0x105197ed0
 A::deserialize
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x10515f400
 A::deserialize caught std::exception with typeid 0x1051982d0
 A::load caught ptree_bad_path with typeid 0x1051982d0
 test_b: KO got "B::load caught ptree_bad_path"

 $ c++ --version
 Apple LLVM version 4.2 (clang-425.0.28) (based on LLVM 3.2svn)
 Target: x86_64-apple-darwin12.5.0
 Thread model: posix
 }}}

 == Building and running on linux with gcc ==

 {{{
 $ make clean
 rm -f src/*.o src/*.so src/test_a src/test_b src/.a* src/.b* ._* src/._*
 include/._*

 $ make all
 g++ -c -g -Iinclude -o src/test_a.o src/test_a.cc
 g++ -c -g -fPIC -Da_EXPORTS -Iinclude
 -I/home/sbarthelemy/.local/share/qi/toolchains/linux64/boost/include -o
 src/a.o src/a.cc
 g++ -shared -Wl -o src/liba.so src/a.o
 g++ -Lsrc -o src/test_a src/test_a.o -la
 g++ -c -g -Iinclude -o src/test_b.o src/test_b.cc
 g++ -c -g -fPIC -fvisibility=hidden -Db_EXPORTS -Iinclude
 -I/home/sbarthelemy/.local/share/qi/toolchains/linux64/boost/include -o
 src/b.o src/b.cc
 g++ -shared -Wl -o src/libb.so src/b.o src/liba.so
 g++ -Lsrc -o src/test_b src/test_b.o -lb -la

 $ LD_LIBRARY_PATH=./src ./src/test_a
 A::deserialize typeid(boost::property_tree::ptree_bad_path):
 0x7f1912652c60
 A::deserialize
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x7f1912652c20
 A::deserialize caught ptree_bad_path with typeid 0x7f1912652b80
 test_a: OK got "A::deserialize caught ptree_bad_path"

 $ LD_LIBRARY_PATH=./src ./src/test_b
 B::load typeid(boost::property_tree::ptree_bad_path): 0x7fefc97dfce0
 B::load
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x7fefc97dfca0
 A::deserialize typeid(boost::property_tree::ptree_bad_path):
 0x7fefc95cdc60
 A::deserialize
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x7fefc97dfca0
 A::deserialize caught ptree_bad_path with typeid 0x7fefc95cdb80
 test_b: OK got "A::deserialize caught ptree_bad_path"

 $ g++ --version
 g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
 Copyright (C) 2011 Free Software Foundation, Inc.
 This is free software; see the source for copying conditions. There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
 PURPOSE.
 }}}

 == Building and running on linux with clang ==

 {{{
 $ make clean
 rm -f src/*.o src/*.so src/test_a src/test_b src/.a* src/.b* ._* src/._*
 include/._*

 $ CXX=clang++ make all
 clang++ -c -g -Iinclude -o src/test_a.o src/test_a.cc
 clang++ -c -g -fPIC -Da_EXPORTS -Iinclude
 -I/home/sbarthelemy/.local/share/qi/toolchains/linux64/boost/include -o
 src/a.o src/a.cc
 clang++ -shared -Wl -o src/liba.so src/a.o
 clang++ -Lsrc -o src/test_a src/test_a.o -la
 clang++ -c -g -Iinclude -o src/test_b.o src/test_b.cc
 clang++ -c -g -fPIC -fvisibility=hidden -Db_EXPORTS -Iinclude
 -I/home/sbarthelemy/.local/share/qi/toolchains/linux64/boost/include -o
 src/b.o src/b.cc
 clang++ -shared -Wl -o src/libb.so src/b.o src/liba.so
 clang++ -Lsrc -o src/test_b src/test_b.o -lb -la

 $ LD_LIBRARY_PATH=./src ./src/test_a
 A::deserialize typeid(boost::property_tree::ptree_bad_path):
 0x7f8bfa09e800
 A::deserialize
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x7f8bfa09e830
 A::deserialize caught ptree_bad_path with typeid 0x7f8bfa09ec00
 test_a: OK got "A::deserialize caught ptree_bad_path"

 $ LD_LIBRARY_PATH=./src ./src/test_b
 B::load typeid(boost::property_tree::ptree_bad_path): 0x7f1d50790920
 B::load
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x7f1d50790950
 A::deserialize typeid(boost::property_tree::ptree_bad_path):
 0x7f1d5057d800
 A::deserialize
 typeid(boost::exception_detail::error_info_injector<boost::property_tree::ptree_bad_path>):
 0x7f1d5057d830
 A::deserialize caught ptree_bad_path with typeid 0x7f1d5057dc00
 test_b: OK got "A::deserialize caught ptree_bad_path"

 $ clang++ --version
 Ubuntu clang version 3.3-5ubuntu4~precise1 (branches/release_33) (based on
 LLVM 3.3)
 Target: x86_64-pc-linux-gnu
 Thread model: posix
 }}}

-- 
Ticket URL: <https://svn.boost.org/trac/boost/ticket/10899>
Boost C++ Libraries <http://www.boost.org/>
Boost provides free peer-reviewed portable C++ source libraries.

This archive was generated by hypermail 2.1.7 : 2017-02-16 18:50:17 UTC