diff --git a/libcxx/include/exception b/libcxx/include/exception --- a/libcxx/include/exception +++ b/libcxx/include/exception @@ -85,8 +85,10 @@ #include #include +// 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 +# include #endif #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -96,24 +98,68 @@ namespace std // purposefully not using versioning namespace { -#if !defined(_LIBCPP_ABI_VCRUNTIME) -class _LIBCPP_EXCEPTION_ABI exception -{ +#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. + +#elif defined(_LIBCPP_ABI_VCRUNTIME) && _HAS_EXCEPTIONS == 0 +// However, 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 exception { // base of all library exceptions +public: + exception() _NOEXCEPT : _Data() {} + + explicit exception(char const* _Message) _NOEXCEPT : _Data() { + _Data._What = _Message; + _Data._DoFree = true; + } + + exception(char const* _Message, int) _NOEXCEPT : _Data() { _Data._What = _Message; } + + exception(exception const&) _NOEXCEPT {} + + exception& operator=(exception const&) _NOEXCEPT { return *this; } + + virtual ~exception() _NOEXCEPT {} + + _LIBCPP_NODISCARD virtual char const* what() const { return _Data._What ? _Data._What : "Unknown exception"; } + +private: + __std_exception_data _Data; +}; + +class bad_exception : public exception { public: - _LIBCPP_INLINE_VISIBILITY exception() _NOEXCEPT {} - _LIBCPP_INLINE_VISIBILITY exception(const exception&) _NOEXCEPT = default; + bad_exception() _NOEXCEPT : exception("bad exception", 1) {} +}; - virtual ~exception() _NOEXCEPT; - virtual const char* what() const _NOEXCEPT; +#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 -{ +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; + _LIBCPP_INLINE_VISIBILITY bad_exception() _NOEXCEPT {} + virtual ~bad_exception() _NOEXCEPT; + virtual const char* what() const _NOEXCEPT; }; #endif // !_LIBCPP_ABI_VCRUNTIME diff --git a/libcxx/include/new b/libcxx/include/new --- a/libcxx/include/new +++ b/libcxx/include/new @@ -148,6 +148,26 @@ #endif // !_LIBCPP_ABI_VCRUNTIME +// When _HAS_EXCEPTIONS == 0, these complete definitions are needed, +// since they would normally be provided in vcruntime_exception.h +#if defined(_LIBCPP_ABI_VCRUNTIME) && _HAS_EXCEPTIONS == 0 +class bad_alloc : public exception { +public: + bad_alloc() noexcept : exception("bad allocation", 1) {} + +private: + friend class bad_array_new_length; + + bad_alloc(char const* const _Message) noexcept : exception(_Message, 1) {} +}; + +class bad_array_new_length : public bad_alloc { +public: + bad_array_new_length() noexcept : bad_alloc("bad array new length") {} +}; + +#endif // _LIBCPP_ABI_VCRUNTIME _HAS_EXCEPTIONS == 0 + _LIBCPP_NORETURN _LIBCPP_FUNC_VIS void __throw_bad_alloc(); // not in C++ spec _LIBCPP_NORETURN inline _LIBCPP_INLINE_VISIBILITY diff --git a/libcxx/include/typeinfo b/libcxx/include/typeinfo --- a/libcxx/include/typeinfo +++ b/libcxx/include/typeinfo @@ -366,6 +366,50 @@ #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", 1) {} + + static bad_cast __construct_from_string_literal(const char* const _Message) _NOEXCEPT { + return bad_cast(_Message, 1); + } + +private: + bad_cast(const char* const _Message, int) _NOEXCEPT : exception(_Message, 1) {} +}; + +class bad_typeid : public exception { +public: + bad_typeid() _NOEXCEPT : exception("bad typeid", 1) {} + + static bad_typeid __construct_from_string_literal(const char* const _Message) _NOEXCEPT { + return bad_typeid(_Message, 1); + } + +private: + friend class __non_rtti_object; + + bad_typeid(const char* const _Message, int) _NOEXCEPT : exception(_Message, 1) {} +}; + +class __non_rtti_object : public bad_typeid { +public: + static __non_rtti_object __construct_from_string_literal(const char* const _Message) _NOEXCEPT { + return __non_rtti_object(_Message, 1); + } + +private: + __non_rtti_object(const char* const _Message, int) _NOEXCEPT : bad_typeid(_Message, 1) {} +}; + +} // namespace std + +#endif // defined(_LIBCPP_ABI_VCRUNTIME) && _HAS_EXCEPTIONS == 0 + _LIBCPP_BEGIN_NAMESPACE_STD _LIBCPP_NORETURN inline _LIBCPP_INLINE_VISIBILITY void __throw_bad_cast() diff --git a/libcxx/test/configs/llvm-libc++-shared-no-exception-clangcl.cfg.in b/libcxx/test/configs/llvm-libc++-shared-no-exception-clangcl.cfg.in new file mode 100644 --- /dev/null +++ b/libcxx/test/configs/llvm-libc++-shared-no-exception-clangcl.cfg.in @@ -0,0 +1,32 @@ +# This testing configuration handles running the test suite against LLVM's libc++ +# using a DLL, with Clang-cl on Windows. + +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} -- ' +)) + +# LIBCXX-WINDOWS-FIXME is the feature name used to XFAIL the +# initial Windows failures until they can be properly diagnosed +# and fixed. This allows easier detection of new test failures +# and regressions. Note: New failures should not be suppressed +# using this feature. (Also see llvm.org/PR32730) +config.available_features.add('LIBCXX-WINDOWS-FIXME') + +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 +) diff --git a/libcxx/test/support/test.support/test_macros_header.no_exceptions.verify.cpp b/libcxx/test/support/test.support/test_macros_header.no_exceptions.verify.cpp --- a/libcxx/test/support/test.support/test_macros_header.no_exceptions.verify.cpp +++ b/libcxx/test/support/test.support/test_macros_header.no_exceptions.verify.cpp @@ -18,6 +18,24 @@ #endif int main(int, char**) { - try { (void)0; } catch (...) { } // expected-error {{exceptions disabled}} - return 0; +#ifndef TEST_COMPILING_WITH_NO_EXCEPTIONS + // In this configuration, we are compiling with exceptions enabled by the + // compiler, but disabling them through _HAS_EXCEPTIONS + // If _HAS_EXCEPTIONS == 0, TEST_HAS_NO_EXCEPTIONS will still be defined, but + // exceptions aren't really disabled in the compiler, and we won't get the + // expected error diagnostic in this case. We use the preprocessor here to + // avoid adding another test feature flag by special casing this single test, + // since _HAS_EXCEPTIONS == 0 otherwise behaves consistently with + // no-exceptions + try { + (void)0; + } catch (...) { + } // expected-no-diagnostics +#else + try { + (void)0; + } catch (...) { + } // expected-error {{exceptions disabled}} +#endif + return 0; } diff --git a/libcxx/test/support/test_macros.h b/libcxx/test/support/test_macros.h --- a/libcxx/test/support/test_macros.h +++ b/libcxx/test/support/test_macros.h @@ -179,11 +179,16 @@ # define RTTI_ASSERT(X) #endif -#if !TEST_HAS_FEATURE(cxx_exceptions) && !defined(__cpp_exceptions) \ - && !defined(__EXCEPTIONS) -#define TEST_HAS_NO_EXCEPTIONS +#if (!TEST_HAS_FEATURE(cxx_exceptions) && !defined(__cpp_exceptions) && !defined(__EXCEPTIONS)) || \ + (defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS == 0) +# define TEST_HAS_NO_EXCEPTIONS #endif +#if (!TEST_HAS_FEATURE(cxx_exceptions) && !defined(__cpp_exceptions) && !defined(__EXCEPTIONS)) +# define TEST_COMPILING_WITH_NO_EXCEPTIONS +#endif + + #if TEST_HAS_FEATURE(address_sanitizer) || TEST_HAS_FEATURE(memory_sanitizer) || \ TEST_HAS_FEATURE(thread_sanitizer) #define TEST_HAS_SANITIZERS diff --git a/libcxx/utils/ci/buildkite-pipeline.yml b/libcxx/utils/ci/buildkite-pipeline.yml --- a/libcxx/utils/ci/buildkite-pipeline.yml +++ b/libcxx/utils/ci/buildkite-pipeline.yml @@ -579,6 +579,18 @@ limit: 2 timeout_in_minutes: 120 + - label: "Clang-cl (no-exceptions)" + command: "bash libcxx/utils/ci/run-buildbot clang-cl-noexceptions" + artifact_paths: + - "**/test-results.xml" + - "**/*.abilist" + agents: + queue: "windows" + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + - label: "MinGW (DLL)" command: "bash libcxx/utils/ci/run-buildbot mingw-dll" artifact_paths: diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot --- a/libcxx/utils/ci/run-buildbot +++ b/libcxx/utils/ci/run-buildbot @@ -554,6 +554,17 @@ echo "+++ Running the libc++ tests" ${NINJA} -vC "${BUILD_DIR}" check-cxx ;; +clang-cl-noexceptions) + 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_ENABLE_EXPERIMENTAL_LIBRARY=OFF \ + -DLIBCXX_TEST_CONFIG="llvm-libc++-shared-no-exception-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 diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py --- a/libcxx/utils/libcxx/test/features.py +++ b/libcxx/utils/libcxx/test/features.py @@ -39,6 +39,13 @@ when=lambda cfg: hasCompileFlag(cfg, '-Wuser-defined-warnings'), actions=[AddCompileFlag('-Wuser-defined-warnings')]), + # Normally, the feature 'no-exceptions' is set in params.py if + # running tests with enable_exceptions=false, but if we didn't set + # that and only defined _HAS_EXCEPTIONS=0, pick up on that and set the + # no-exceptions feature so certain tests are omitted. + # It would probably be fine to just run that test configuration with + # enable_exceptions=false too. + Feature(name='no-exceptions', when=lambda cfg: '_HAS_EXCEPTIONS' in compilerMacros(cfg) and compilerMacros(cfg)['_HAS_EXCEPTIONS'] == '0'), Feature(name='has-fblocks', when=lambda cfg: hasCompileFlag(cfg, '-fblocks')), Feature(name='-fsized-deallocation', when=lambda cfg: hasCompileFlag(cfg, '-fsized-deallocation')), Feature(name='-faligned-allocation', when=lambda cfg: hasCompileFlag(cfg, '-faligned-allocation')),