libtool/tests/exceptions.at
2024-01-12 01:35:32 -05:00

402 lines
10 KiB
Plaintext

# exception.at -- test C++ exception handling with libtool -*- Autotest -*-
#
# Copyright (C) 2010-2019, 2021-2024 Free Software Foundation, Inc.
#
# This file is part of GNU Libtool.
#
# GNU Libtool is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# GNU Libtool is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Libtool; see the file COPYING. If not, a copy
# can be downloaded from http://www.gnu.org/licenses/gpl.html,
# or obtained by writing to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
####
AT_SETUP([C++ exception handling])
AT_KEYWORDS([libtool])
AT_KEYWORDS([libltdl])
: ${LTDLINCL="-I$abs_top_srcdir/libltdl"}
: ${LIBLTDL="$abs_builddir/../libltdl/libltdlc.la"}
# Skip this test when called from:
# make distcheck DISTCHECK_CONFIGURE_FLAGS=--disable-ltdl-install
AT_CHECK([case $LIBLTDL in #(
*/_inst/lib/*) test -f "$LIBLTDL" || (exit 77) ;;
esac], [], [ignore])
CPPFLAGS="$LTDLINCL $CPPFLAGS"
# Win32 (and cygwin) notes
# ------------------------
# When using C++ and Win32 DLLs, data types used in the DLL's interface
# that are other-than-POD, must have vtables, typeinfo, and other
# elements resolved when the client is linked. This includes exception
# classes. Therefore, the exception class "modexc" thrown by the
# dynamically-loaded module must be defined in a separate DLL, to which
# both that module and main must be directly linked; hence, the 'common'
# library. Not using a 'common' library in this manner represents an
# ODR violation, unless the platform's runtime loader is capable of
# rationalizing vague linkage items such as vtables, typeinfo, and
# typename elements) at runtime. The Win32 loader is not capable of
# this, but some ELF loaders appear to be.
#
# Similar treatment is not necessary for liba (e.g. the libexc
# exception class), because that library is not dynamically loaded. As a
# consequence, vague linkage items for the class libexc are resolved at
# link time using the vague linkage rules, for both Win32 and other
# (e.g. ELF) platforms.
#
# Also, when linking a C++ DLL with another C++ DLL, some versions of
# the GNU toolchain on Win32 (or cygwin) mistakenly re-export symbols
# that were imported from the other DLL, when the client DLL is linked
# using -export-all-symbols. Similar issues MAY also arise with those
# versions of the GNU toolchain if using the libtool link flags
# -export-symbols LIST or -export-symbols-regex REGEX, if any symbols
# from the dependency, rather than client, library are listed (or match
# the regex). However, in this test, none of these situations apply,
# so we don't directly address it. Otherwise, the correct mechanism
# would be to avoid all of those flags, and instead explicitly decorate
# all symbols with appropriate __declspec (dllexport) or
# __declspec (dllimport) flags when building the DLLs and the clients.
#
# For more information, see these two threads:
# http://lists.gnu.org/archive/html/bug-libtool/2010-06/msg00069.html
# http://cygwin.com/ml/cygwin/2010-06/msg00392.html
# To sum up: C++ is complicated.
AT_DATA([common.h],
[[#ifndef LIBTOOL_TEST_COMMON_HEADER
#define LIBTOOL_TEST_COMMON_HEADER
#include <exception>
#include <string>
#if defined __CYGWIN__ || defined _WIN32
# if defined DLL_EXPORT || defined USING_COMMON_DLL
# if defined LIBTOOL_TEST_IN_COMMON
# define COMMON_IMPEXP __declspec (dllexport)
# else
# define COMMON_IMPEXP __declspec (dllimport)
# endif
# else
# define COMMON_IMPEXP
# endif
#else
# define COMMON_IMPEXP
#endif
class COMMON_IMPEXP modexc : public std::exception {
public:
modexc (std::string str) : message (str) { }
~modexc () throw () { }
virtual const char *what () const throw ()
{
return message.c_str ();
}
private:
std::string message;
};
extern "C" int COMMON_IMPEXP common_dummy (void);
#endif
]])
AT_DATA([common.cpp],
[[#define LIBTOOL_TEST_IN_COMMON
#include "common.h"
extern "C"
int common_dummy (void)
{
return 0;
}
]])
AT_DATA([module.h],
[[#include "common.h"
#if defined __CYGWIN__ || defined _WIN32
# if defined DLL_EXPORT || defined USING_MODULE_DLL
# if defined LIBTOOL_TEST_IN_MODULE
# define MODULE_IMPEXP __declspec (dllexport)
# else
# define MODULE_IMPEXP __declspec (dllimport)
# endif
# else
# define MODULE_IMPEXP
# endif
#else
# define MODULE_IMPEXP
#endif
extern "C" int MODULE_IMPEXP modfoo () throw (modexc);
]])
AT_DATA([module.cpp],
[[#include <iostream>
#define LIBTOOL_TEST_IN_MODULE
#include "module.h"
int modbar (void) throw (modexc)
{
throw modexc ("exception in module");
}
extern "C"
int modfoo (void) throw (modexc)
{
try {
modbar ();
}
catch (modexc e) {
std::cerr << "caught inside module: " << e.what () << '\n';
throw modexc ("exception from module");
}
return 0;
}
]])
AT_DATA([lib.h],
[[#include <exception>
#include <string>
#if defined __CYGWIN__ || defined _WIN32
# if defined DLL_EXPORT || defined USING_LIB_DLL
# if defined LIBTOOL_TEST_IN_LIB
# define LIB_IMPEXP __declspec (dllexport)
# else
# define LIB_IMPEXP __declspec (dllimport)
# endif
# else
# define LIB_IMPEXP
# endif
#else
# define LIB_IMPEXP
#endif
class LIB_IMPEXP libexc : public std::exception {
public:
libexc (std::string str) : message (str) { }
~libexc () throw () { }
virtual const char *what () const throw ()
{
return message.c_str ();
}
private:
std::string message;
};
int LIB_IMPEXP libfoo () throw (libexc);
]])
AT_DATA([lib.cpp],
[[#include <iostream>
#define LIBTOOL_TEST_IN_LIB
#include "lib.h"
int libbar (void) throw (libexc)
{
throw libexc ("exception in library");
}
int libfoo (void) throw (libexc)
{
try {
libbar ();
}
catch (libexc e) {
std::cerr << "caught inside lib: " << e.what () << '\n';
throw libexc ("exception from library");
}
return 0;
}
]])
AT_DATA([main.cpp],
[[#include <ltdl.h>
#include <cstdlib>
#include <iostream>
#include <exception>
#include <string>
#include "common.h"
#include "lib.h"
#include "module.h"
class exc : public std::exception {
public:
exc (std::string str) : message (str) { }
~exc () throw () { }
virtual const char *what () const throw ()
{
return message.c_str ();
}
private:
std::string message;
};
int foo (void) throw (exc)
{
throw exc ("exception in program");
return 0;
}
int exceptions_in_prog (void)
{
std::cerr << "exceptions_in_prog\n";
try {
foo ();
}
catch (exc e) {
std::cerr << "caught: " << e.what () << '\n';
return 0;
}
return 1;
}
int exceptions_in_lib (void)
{
std::cerr << "exceptions_in_lib\n";
try {
libfoo ();
}
catch (libexc e) {
std::cerr << "caught: " << e.what () << '\n';
return 0;
}
return 1;
}
int exceptions_in_module (void)
{
std::cerr << "exceptions_in_module\n";
if (lt_dlinit ())
{
std::cerr << "init error: " << lt_dlerror () << '\n';
return 1;
}
// Some systems need RTLD_GLOBAL for exceptions to work in modules.
lt_dladvise advise;
if (lt_dladvise_init (&advise) || lt_dladvise_global (&advise))
{
std::cerr << "error setting advise global\n";
return 1;
}
lt_dlhandle handle = lt_dlopenadvise ("module.la", advise);
if (handle == NULL)
{
std::cerr << "dlopen failed: " << lt_dlerror () << '\n';
return 1;
}
lt_dladvise_destroy (&advise);
typedef int (*pfun) (void);
pfun pf = (pfun) lt_dlsym (handle, "modfoo");
if (pf == NULL)
{
std::cerr << "dlsym failed: " << lt_dlerror () << '\n';
return 1;
}
bool exception_caught = false;
try {
(*pf) ();
}
catch (modexc e) {
std::cerr << "caught: " << e.what () << '\n';
exception_caught = true;
}
/* Only close the module after all of its objects have gone out of scope. */
if (exception_caught)
{
if (lt_dlclose (handle))
{
std::cerr << "dlclose failed: " << lt_dlerror () << '\n';
return 1;
}
if (lt_dlexit ())
{
std::cerr << "lt_dlexit failed: " << lt_dlerror () << '\n';
return 1;
}
return 0;
}
return 1;
}
int main (void)
{
LTDL_SET_PRELOADED_SYMBOLS();
if (exceptions_in_prog ())
return 1;
if (exceptions_in_lib ())
return 1;
if (exceptions_in_module ())
return 1;
return 0;
}
]])
inst=`pwd`/inst
libdir=$inst/lib
bindir=$inst/bin
moddir=$inst/mod
mkdir l m $inst $libdir $bindir $moddir
# If the C++ compiler isn't capable, don't bother.
AT_CHECK([$CXX $CPPFLAGS $CXXFLAGS -DUSING_COMMON_DLL -DUSING_MODULE_DLL -DUSING_LIB_DLL -c main.cpp || exit 77], [], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=compile --tag=CXX $CXX $CPPFLAGS $CXXFLAGS -c common.cpp],
[], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=compile --tag=CXX $CXX $CPPFLAGS $CXXFLAGS -c lib.cpp],
[], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=compile --tag=CXX $CXX $CPPFLAGS $CXXFLAGS -DUSING_COMMON_DLL -c module.cpp],
[], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=link --tag=CXX $CXX $CXXFLAGS $LDFLAGS -o l/libcommon.la ]dnl
[common.lo -no-undefined -version-info 1:0:0 -rpath $libdir],
[], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=link --tag=CXX $CXX $CXXFLAGS $LDFLAGS -o l/liba.la ]dnl
[lib.lo -no-undefined -version-info 1:0:0 -rpath $libdir],
[], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=link --tag=CXX $CXX $CXXFLAGS $LDFLAGS -o m/module.la ]dnl
[module.lo l/libcommon.la -module -avoid-version -no-undefined -rpath $moddir],
[], [ignore], [ignore])
# We need -export-dynamic for the exception handling in modules to work.
AT_CHECK([$LIBTOOL --mode=link --tag=CXX $CXX $CXXFLAGS $LDFLAGS -o main$EXEEXT ]dnl
[main.$OBJEXT l/liba.la l/libcommon.la -dlopen m/module.la $LIBLTDL -export-dynamic],
[], [ignore], [ignore])
LT_AT_NOINST_EXEC_CHECK([./main], [-dlopen m/module.la], [], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=install cp l/libcommon.la $libdir],
[], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=install cp l/liba.la $libdir],
[], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=install cp m/module.la $moddir],
[], [ignore], [ignore])
AT_CHECK([$LIBTOOL --mode=install cp main$EXEEXT $bindir],
[], [ignore], [ignore])
rm -rf l m main$EXEEXT
LTDL_LIBRARY_PATH=$moddir
export LTDL_LIBRARY_PATH
LT_AT_EXEC_CHECK([$bindir/main], [], [ignore], [ignore])
AT_CLEANUP