[libcxx] Fix using the vcruntime ABI with _HAS_EXCEPTIONS=0 defined

_HAS_EXCEPTIONS=0 allows disabling the exception parts of the MS STL
and vcruntime, and e.g. compiler-rt/lib/fuzzer sets this define (to
work around issues with MS STL). If using libc++ instead of MS STL,
this define previously broke the libc++ headers.

If _HAS_EXCEPTIONS is set to 0, the vcruntime_exception.h header
doesn't define the ABI base class std::exception. If no exceptions
are going to be thrown, this probably is fine (although it also
breaks using subclasses of it as regular objects that aren't thrown),
but it requires ifdeffing out all subclasses of all exception/error
derived objects (which are sprinkled throughout the headers).

Instead, libc++ will supply an ABI compatible definition when
_HAS_EXCEPTIONS is set to 0, which will make the class hierarchies
complete.

In this build configuration, one can still create instances of
exception subclasses, and those objects will be ABI incompatible
with the ones from when _HAS_EXCEPTIONS isn't defined to 0 - but
one may argue that's a pathological/self-imposed problem in that case.

Reviewed By: #libc, ldionne

Differential Revision: https://reviews.llvm.org/D103947
This commit is contained in:
Paul Kirth 2022-08-17 20:57:59 +00:00
parent 8de51375f1
commit 56a34451e1
6 changed files with 151 additions and 16 deletions

View File

@ -85,8 +85,10 @@ template <class E> void rethrow_if_nested(const E& e);
#include <type_traits>
#include <version>
// <vcruntime_exception.h> defines its own std::exception and std::bad_exception types,
// which we use in order to be ABI-compatible with other STLs on Windows.
#if defined(_LIBCPP_ABI_VCRUNTIME)
#include <vcruntime_exception.h>
# include <vcruntime_exception.h>
#endif
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@ -96,24 +98,66 @@ template <class E> void rethrow_if_nested(const E& e);
namespace std // purposefully not using versioning namespace
{
#if !defined(_LIBCPP_ABI_VCRUNTIME)
class _LIBCPP_EXCEPTION_ABI exception
{
public:
_LIBCPP_INLINE_VISIBILITY exception() _NOEXCEPT {}
_LIBCPP_INLINE_VISIBILITY exception(const exception&) _NOEXCEPT = default;
#if defined(_LIBCPP_ABI_VCRUNTIME) && (!defined(_HAS_EXCEPTIONS) || _HAS_EXCEPTIONS != 0)
// The std::exception class was already included above, but we're explicit about this condition here for clarity.
virtual ~exception() _NOEXCEPT;
virtual const char* what() const _NOEXCEPT;
#elif defined(_LIBCPP_ABI_VCRUNTIME) && _HAS_EXCEPTIONS == 0
// However, <vcruntime_exception.h> does not define std::exception and std::bad_exception
// when _HAS_EXCEPTIONS == 0.
//
// Since libc++ still wants to provide the std::exception hierarchy even when _HAS_EXCEPTIONS == 0
// (after all those are simply types like any other), we define an ABI-compatible version
// of the VCRuntime std::exception and std::bad_exception types in that mode.
struct __std_exception_data {
char const* _What;
bool _DoFree;
};
class _LIBCPP_EXCEPTION_ABI bad_exception
: public exception
{
class exception { // base of all library exceptions
public:
_LIBCPP_INLINE_VISIBILITY bad_exception() _NOEXCEPT {}
virtual ~bad_exception() _NOEXCEPT;
virtual const char* what() const _NOEXCEPT;
exception() _NOEXCEPT : _Data() {}
explicit exception(char const* __message) _NOEXCEPT : _Data() {
_Data._What = __message;
_Data._DoFree = true;
}
exception(exception const&) _NOEXCEPT {}
exception& operator=(exception const&) _NOEXCEPT { return *this; }
virtual ~exception() _NOEXCEPT {}
virtual char const* what() const _NOEXCEPT { return _Data._What ? _Data._What : "Unknown exception"; }
private:
__std_exception_data _Data;
};
class bad_exception : public exception {
public:
bad_exception() _NOEXCEPT : exception("bad exception") {}
};
#else // !defined(_LIBCPP_ABI_VCRUNTIME)
// On all other platforms, we define our own std::exception and std::bad_exception types
// regardless of whether exceptions are turned on as a language feature.
class _LIBCPP_EXCEPTION_ABI exception {
public:
_LIBCPP_INLINE_VISIBILITY exception() _NOEXCEPT {}
_LIBCPP_INLINE_VISIBILITY exception(const exception&) _NOEXCEPT = default;
virtual ~exception() _NOEXCEPT;
virtual const char* what() const _NOEXCEPT;
};
class _LIBCPP_EXCEPTION_ABI bad_exception : public exception {
public:
_LIBCPP_INLINE_VISIBILITY bad_exception() _NOEXCEPT {}
virtual ~bad_exception() _NOEXCEPT;
virtual const char* what() const _NOEXCEPT;
};
#endif // !_LIBCPP_ABI_VCRUNTIME

View File

@ -146,7 +146,25 @@ typedef void (*new_handler)();
_LIBCPP_FUNC_VIS new_handler set_new_handler(new_handler) _NOEXCEPT;
_LIBCPP_FUNC_VIS new_handler get_new_handler() _NOEXCEPT;
#endif // !_LIBCPP_ABI_VCRUNTIME
#elif defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS == 0 // !_LIBCPP_ABI_VCRUNTIME
// When _HAS_EXCEPTIONS == 0, these complete definitions are needed,
// since they would normally be provided in vcruntime_exception.h
class bad_alloc : public exception {
public:
bad_alloc() noexcept : exception("bad allocation") {}
private:
friend class bad_array_new_length;
bad_alloc(char const* const __message) noexcept : exception(__message) {}
};
class bad_array_new_length : public bad_alloc {
public:
bad_array_new_length() noexcept : bad_alloc("bad array new length") {}
};
#endif // defined(_LIBCPP_ABI_VCRUNTIME) && defined(_HAS_EXCEPTIONS) &&_HAS_EXCEPTIONS == 0
_LIBCPP_NORETURN _LIBCPP_FUNC_VIS void __throw_bad_alloc(); // not in C++ spec

View File

@ -370,6 +370,30 @@ class _LIBCPP_EXCEPTION_ABI bad_typeid
#endif // defined(_LIBCPP_ABI_VCRUNTIME)
#if defined(_LIBCPP_ABI_VCRUNTIME) && _HAS_EXCEPTIONS == 0
namespace std {
class bad_cast : public exception {
public:
bad_cast() _NOEXCEPT : exception("bad cast") {}
private:
bad_cast(const char* const __message) _NOEXCEPT : exception(__message) {}
};
class bad_typeid : public exception {
public:
bad_typeid() _NOEXCEPT : exception("bad typeid") {}
private:
bad_typeid(const char* const __message) _NOEXCEPT : exception(__message) {}
};
} // namespace std
#endif // defined(_LIBCPP_ABI_VCRUNTIME) && _HAS_EXCEPTIONS == 0
_LIBCPP_BEGIN_NAMESPACE_STD
_LIBCPP_NORETURN inline _LIBCPP_INLINE_VISIBILITY
void __throw_bad_cast()

View File

@ -0,0 +1,26 @@
# This testing configuration handles running the test suite against LLVM's libc++
# using a DLL, with Clang-cl on Windows. This variant sets _HAS_EXCEPTIONS = 0
# which removes exception class definitions from the vcruntime.
lit_config.load_config(config, '@CMAKE_CURRENT_BINARY_DIR@/cmake-bridge.cfg')
config.substitutions.append(('%{flags}', '--driver-mode=g++'))
config.substitutions.append(('%{compile_flags}',
'-nostdinc++ -I %{include} -I %{target-include} -I %{libcxx}/test/support -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_STDIO_ISO_WIDE_SPECIFIERS -DNOMINMAX -D_HAS_EXCEPTIONS=0'
))
config.substitutions.append(('%{link_flags}',
'-nostdlib -L %{lib} -lc++ -lmsvcrt -lmsvcprt -loldnames'
))
config.substitutions.append(('%{exec}',
'%{executor} --execdir %T --env PATH=%{lib} -- '
))
import os, site
site.addsitedir(os.path.join('@LIBCXX_SOURCE_DIR@', 'utils'))
import libcxx.test.params, libcxx.test.newconfig
libcxx.test.newconfig.configure(
libcxx.test.params.DEFAULT_PARAMETERS,
libcxx.test.features.DEFAULT_FEATURES,
config,
lit_config
)

View File

@ -592,6 +592,18 @@ steps:
limit: 2
timeout_in_minutes: 120
- label: "Clang-cl (no vcruntime exceptions)"
command: "bash libcxx/utils/ci/run-buildbot clang-cl-no-vcruntime"
artifact_paths:
- "**/test-results.xml"
- "**/*.abilist"
agents:
queue: "windows"
retry:
automatic:
- exit_status: -1 # Agent was lost
limit: 2
- label: "MinGW (DLL, x86_64)"
command: "bash libcxx/utils/ci/run-buildbot mingw-dll"
artifact_paths:

View File

@ -577,6 +577,17 @@ clang-cl-static)
echo "+++ Running the libc++ tests"
${NINJA} -vC "${BUILD_DIR}" check-cxx
;;
clang-cl-no-vcruntime)
clean
# Building libc++ in the same way as in clang-cl-dll above, but running
# tests with -D_HAS_EXCEPTIONS=0, which users might set in certain
# translation units while using libc++, even if libc++ is built with
# exceptions enabled.
generate-cmake-libcxx-win -DLIBCXX_TEST_PARAMS="enable_experimental=False" \
-DLIBCXX_TEST_CONFIG="llvm-libc++-shared-no-vcruntime-clangcl.cfg.in"
echo "+++ Running the libc++ tests"
${NINJA} -vC "${BUILD_DIR}" check-cxx
;;
mingw-dll)
clean
# Explicitly specify the compiler with a triple prefix. The CI