In D144319, Clang tried to land a change that would cause some functions that are not supposed to return nullptr to optimize better. As reported in https://reviews.llvm.org/D144319#4203982, libc++ started seeing failures in its CI shortly after this change was landed. As explained in D146379, the reason for these failures is that libc++'s throwing `operator new` can in fact return nullptr when compiled with exceptions disabled. However, this contradicts the Standard, which clearly says that the throwing version of `operator new(size_t)` should never return nullptr. This is actually a long standing issue. I've previously seen a case where LTO would optimize incorrectly based on the assumption that `operator new` doesn't return nullptr, an assumption that was violated in that case because libc++.dylib was compiled with -fno-exceptions. Unfortunately, fixing this is kind of tricky. The Standard has a few requirements for the allocation functions, some of which are impossible to satisfy under -fno-exceptions: 1. `operator new(size_t)` must never return nullptr 2. `operator new(size_t, nothrow_t)` must call the throwing version and return nullptr on failure to allocate 3. We can't throw exceptions when compiled with -fno-exceptions In the case where exceptions are enabled, things work nicely. `new(size_t)` throws and `new(size_t, nothrow_t)` uses a try-catch to return nullptr. However, when compiling the library with -fno-exceptions, we can't throw an exception from `new(size_t)`, and we can't catch anything from `new(size_t, nothrow_t)`. The only thing we can do from `new(size_t)` is actually abort the program, which does not make it possible for `new(size_t, nothrow_t)` to catch something and return nullptr. This patch makes the following changes: 1. When compiled with -fno-exceptions, the throwing version of `operator new` will now abort on failure instead of returning nullptr on failure. This resolves the issue that the compiler could mis-compile based on the assumption that nullptr is never returned. This constitutes an API and ABI breaking change for folks compiling the library with -fno-exceptions (which is not the general public, who merely uses libc++ headers but use a shared library that has already been compiled). This should mostly impact vendors and other folks who compile libc++.dylib themselves. 2. When the library is compiled with -fexceptions, the nothrow version of `operator new` has no change. When the library is compiled with -fno-exceptions, the nothrow version of `operator new` will now check whether the throwing version of `operator new` has been overridden. If it has not been overridden, then it will use an implementation equivalent to that of the throwing `operator new`, except it will return nullptr on failure to allocate (instead of terminating). However, if the throwing `operator new` has been overridden, it is now an error NOT to also override the nothrow `operator new`. Indeed, there is no way for us to implement a valid nothrow `operator new` without knowing the exact implementation of the throwing version. In summary, this change will impact people who fall into the following intersection of conditions: - They use the libc++ shared/static library built with `-fno-exceptions` - They do not override `operator new(..., std::nothrow_t)` - They override `operator new(...)` (the throwing version) - They use `operator new(..., std::nothrow_t)` We believe this represents a small number of people. Fixes #60129 rdar://103958777 Differential Revision: https://reviews.llvm.org/D150610
133 lines
4.8 KiB
C++
133 lines
4.8 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// REQUIRES: has-unix-headers
|
|
// UNSUPPORTED: c++03
|
|
// UNSUPPORTED: libcpp-hardening-mode=none
|
|
// XFAIL: availability-verbose_abort-missing
|
|
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
#include "check_assertion.h"
|
|
|
|
template <class Func>
|
|
bool TestDeathTest(
|
|
Outcome expected_outcome, DeathCause expected_cause, const char* stmt, Func&& func, const Matcher& matcher) {
|
|
auto get_matcher = [&] {
|
|
#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
|
return matcher;
|
|
#else
|
|
(void)matcher;
|
|
return MakeAnyMatcher();
|
|
#endif
|
|
};
|
|
|
|
DeathTest test_case;
|
|
DeathTestResult test_result = test_case.Run(std::array<DeathCause, 1>{expected_cause}, func, get_matcher());
|
|
std::string maybe_failure_description;
|
|
|
|
Outcome outcome = test_result.outcome();
|
|
if (expected_outcome != outcome) {
|
|
maybe_failure_description +=
|
|
std::string("Test outcome was different from expected; expected ") + ToString(expected_outcome) +
|
|
", got: " + ToString(outcome);
|
|
}
|
|
|
|
DeathCause cause = test_result.cause();
|
|
if (expected_cause != cause) {
|
|
auto failure_description =
|
|
std::string("Cause of death was different from expected; expected ") + ToString(expected_cause) +
|
|
", got: " + ToString(cause);
|
|
if (maybe_failure_description.empty()) {
|
|
maybe_failure_description = failure_description;
|
|
} else {
|
|
maybe_failure_description += std::string("; ") + failure_description;
|
|
}
|
|
}
|
|
|
|
if (!maybe_failure_description.empty()) {
|
|
test_case.PrintFailureDetails(maybe_failure_description, stmt, test_result.cause());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// clang-format off
|
|
|
|
#define TEST_DEATH_TEST(outcome, cause, ...) \
|
|
assert(( TestDeathTest(outcome, cause, #__VA_ARGS__, [&]() { __VA_ARGS__; }, MakeAnyMatcher()) ))
|
|
#define TEST_DEATH_TEST_MATCHES(outcome, cause, matcher, ...) \
|
|
assert(( TestDeathTest(outcome, cause, #__VA_ARGS__, [&]() { __VA_ARGS__; }, matcher) ))
|
|
|
|
// clang-format on
|
|
|
|
#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
|
DeathCause assertion_death_cause = DeathCause::VerboseAbort;
|
|
#else
|
|
DeathCause assertion_death_cause = DeathCause::Trap;
|
|
#endif
|
|
|
|
int main(int, char**) {
|
|
auto fail_assert = [] { _LIBCPP_ASSERT(false, "Some message"); };
|
|
Matcher good_matcher = MakeAssertionMessageMatcher("Some message");
|
|
Matcher bad_matcher = MakeAssertionMessageMatcher("Bad expected message");
|
|
|
|
// Test the implementation of death tests. We're bypassing the assertions added by the actual `EXPECT_DEATH` macros
|
|
// which allows us to test failure cases (where the assertion would fail) as well.
|
|
{
|
|
// Success -- `std::terminate`.
|
|
TEST_DEATH_TEST(Outcome::Success, DeathCause::StdTerminate, std::terminate());
|
|
|
|
// Success -- trapping.
|
|
TEST_DEATH_TEST(Outcome::Success, DeathCause::Trap, __builtin_trap());
|
|
|
|
// Success -- assertion failure with any matcher.
|
|
TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, MakeAnyMatcher(), fail_assert());
|
|
|
|
// Success -- assertion failure with a specific matcher.
|
|
TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, good_matcher, fail_assert());
|
|
|
|
#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
|
// Failure -- error message doesn't match.
|
|
TEST_DEATH_TEST_MATCHES(Outcome::UnexpectedErrorMessage, assertion_death_cause, bad_matcher, fail_assert());
|
|
#endif
|
|
|
|
// Invalid cause -- child did not die.
|
|
TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::DidNotDie, ((void)0));
|
|
|
|
// Invalid cause -- unknown.
|
|
TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::Unknown, std::exit(13));
|
|
}
|
|
|
|
// Test the `EXPECT_DEATH` macros themselves. Since they assert success, we can only test successful cases.
|
|
{
|
|
auto invoke_verbose_abort = [] { _LIBCPP_VERBOSE_ABORT("contains some message"); };
|
|
auto invoke_abort = [] { std::abort(); };
|
|
|
|
auto simple_matcher = [](const std::string& text) {
|
|
bool success = text.find("some") != std::string::npos;
|
|
return MatchResult(success, "");
|
|
};
|
|
|
|
EXPECT_ANY_DEATH(_LIBCPP_VERBOSE_ABORT(""));
|
|
EXPECT_ANY_DEATH(std::abort());
|
|
EXPECT_ANY_DEATH(std::terminate());
|
|
EXPECT_DEATH(invoke_verbose_abort());
|
|
EXPECT_DEATH_MATCHES(MakeAnyMatcher(), invoke_verbose_abort());
|
|
EXPECT_DEATH_MATCHES(simple_matcher, invoke_verbose_abort());
|
|
EXPECT_STD_ABORT(invoke_abort());
|
|
EXPECT_STD_TERMINATE([] { std::terminate(); });
|
|
TEST_LIBCPP_ASSERT_FAILURE(fail_assert(), "Some message");
|
|
}
|
|
|
|
return 0;
|
|
}
|