Files
libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.pass.cpp
JF Bastien e15dd4e32e Support tests in freestanding
Summary:
Freestanding is *weird*. The standard allows it to differ in a bunch of odd
manners from regular C++, and the committee would like to improve that
situation. I'd like to make libc++ behave better with what freestanding should
be, so that it can be a tool we use in improving the standard. To do that we
need to try stuff out, both with "freestanding the language mode" and
"freestanding the library subset".

Let's start with the super basic: run the libc++ tests in freestanding, using
clang as the compiler, and see what works. The easiest hack to do this:

In utils/libcxx/test/config.py add:

  self.cxx.compile_flags += ['-ffreestanding']

Run the tests and they all fail.

Why? Because in freestanding `main` isn't special. This "not special" property
has two effects: main doesn't get mangled, and main isn't allowed to omit its
`return` statement. The first means main gets mangled and the linker can't
create a valid executable for us to test. The second means we spew out warnings
(ew) and the compiler doesn't insert the `return` we omitted, and main just
falls of the end and does whatever undefined behavior (if you're luck, ud2
leading to non-zero return code).

Let's start my work with the basics. This patch changes all libc++ tests to
declare `main` as `int main(int, char**` so it mangles consistently (enabling us
to declare another `extern "C"` main for freestanding which calls the mangled
one), and adds `return 0;` to all places where it was missing. This touches 6124
files, and I apologize.

The former was done with The Magic Of Sed.

The later was done with a (not quite correct but decent) clang tool:

  https://gist.github.com/jfbastien/793819ff360baa845483dde81170feed

This works for most tests, though I did have to adjust a few places when e.g.
the test runs with `-x c`, macros are used for main (such as for the filesystem
tests), etc.

Once this is in we can create a freestanding bot which will prevent further
regressions. After that, we can start the real work of supporting C++
freestanding fairly well in libc++.

<rdar://problem/47754795>

Reviewers: ldionne, mclow.lists, EricWF

Subscribers: christof, jkorous, dexonsmith, arphaman, miyuki, libcxx-commits

Differential Revision: https://reviews.llvm.org/D57624

git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@353086 91177308-0d34-0410-b5e6-96231b3b80d8
2019-02-04 20:31:13 +00:00

618 lines
19 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
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++98, c++03, c++11, c++14
// template <class F> unspecified not_fn(F&& f);
#include <functional>
#include <type_traits>
#include <string>
#include <cassert>
#include "test_macros.h"
#include "type_id.h"
///////////////////////////////////////////////////////////////////////////////
// CALLABLE TEST TYPES
///////////////////////////////////////////////////////////////////////////////
bool returns_true() { return true; }
template <class Ret = bool>
struct MoveOnlyCallable {
MoveOnlyCallable(MoveOnlyCallable const&) = delete;
MoveOnlyCallable(MoveOnlyCallable&& other)
: value(other.value)
{ other.value = !other.value; }
template <class ...Args>
Ret operator()(Args&&...) { return Ret{value}; }
explicit MoveOnlyCallable(bool x) : value(x) {}
Ret value;
};
template <class Ret = bool>
struct CopyCallable {
CopyCallable(CopyCallable const& other)
: value(other.value) {}
CopyCallable(CopyCallable&& other)
: value(other.value) { other.value = !other.value; }
template <class ...Args>
Ret operator()(Args&&...) { return Ret{value}; }
explicit CopyCallable(bool x) : value(x) {}
Ret value;
};
template <class Ret = bool>
struct ConstCallable {
ConstCallable(ConstCallable const& other)
: value(other.value) {}
ConstCallable(ConstCallable&& other)
: value(other.value) { other.value = !other.value; }
template <class ...Args>
Ret operator()(Args&&...) const { return Ret{value}; }
explicit ConstCallable(bool x) : value(x) {}
Ret value;
};
template <class Ret = bool>
struct NoExceptCallable {
NoExceptCallable(NoExceptCallable const& other)
: value(other.value) {}
template <class ...Args>
Ret operator()(Args&&...) noexcept { return Ret{value}; }
template <class ...Args>
Ret operator()(Args&&...) const noexcept { return Ret{value}; }
explicit NoExceptCallable(bool x) : value(x) {}
Ret value;
};
struct CopyAssignableWrapper {
CopyAssignableWrapper(CopyAssignableWrapper const&) = default;
CopyAssignableWrapper(CopyAssignableWrapper&&) = default;
CopyAssignableWrapper& operator=(CopyAssignableWrapper const&) = default;
CopyAssignableWrapper& operator=(CopyAssignableWrapper &&) = default;
template <class ...Args>
bool operator()(Args&&...) { return value; }
explicit CopyAssignableWrapper(bool x) : value(x) {}
bool value;
};
struct MoveAssignableWrapper {
MoveAssignableWrapper(MoveAssignableWrapper const&) = delete;
MoveAssignableWrapper(MoveAssignableWrapper&&) = default;
MoveAssignableWrapper& operator=(MoveAssignableWrapper const&) = delete;
MoveAssignableWrapper& operator=(MoveAssignableWrapper &&) = default;
template <class ...Args>
bool operator()(Args&&...) { return value; }
explicit MoveAssignableWrapper(bool x) : value(x) {}
bool value;
};
struct MemFunCallable {
explicit MemFunCallable(bool x) : value(x) {}
bool return_value() const { return value; }
bool return_value_nc() { return value; }
bool value;
};
enum CallType : unsigned {
CT_None,
CT_NonConst = 1,
CT_Const = 2,
CT_LValue = 4,
CT_RValue = 8
};
inline constexpr CallType operator|(CallType LHS, CallType RHS) {
return static_cast<CallType>(static_cast<unsigned>(LHS) | static_cast<unsigned>(RHS));
}
struct ForwardingCallObject {
template <class ...Args>
bool operator()(Args&&...) & {
set_call<Args&&...>(CT_NonConst | CT_LValue);
return true;
}
template <class ...Args>
bool operator()(Args&&...) const & {
set_call<Args&&...>(CT_Const | CT_LValue);
return true;
}
// Don't allow the call operator to be invoked as an rvalue.
template <class ...Args>
bool operator()(Args&&...) && {
set_call<Args&&...>(CT_NonConst | CT_RValue);
return true;
}
template <class ...Args>
bool operator()(Args&&...) const && {
set_call<Args&&...>(CT_Const | CT_RValue);
return true;
}
template <class ...Args>
static void set_call(CallType type) {
assert(last_call_type == CT_None);
assert(last_call_args == nullptr);
last_call_type = type;
last_call_args = &makeArgumentID<Args...>();
}
template <class ...Args>
static bool check_call(CallType type) {
bool result =
last_call_type == type
&& last_call_args
&& *last_call_args == makeArgumentID<Args...>();
last_call_type = CT_None;
last_call_args = nullptr;
return result;
}
static CallType last_call_type;
static TypeID const* last_call_args;
};
CallType ForwardingCallObject::last_call_type = CT_None;
TypeID const* ForwardingCallObject::last_call_args = nullptr;
///////////////////////////////////////////////////////////////////////////////
// BOOL TEST TYPES
///////////////////////////////////////////////////////////////////////////////
struct EvilBool {
static int bang_called;
EvilBool(EvilBool const&) = default;
EvilBool(EvilBool&&) = default;
friend EvilBool operator!(EvilBool const& other) {
++bang_called;
return EvilBool{!other.value};
}
private:
friend struct MoveOnlyCallable<EvilBool>;
friend struct CopyCallable<EvilBool>;
friend struct NoExceptCallable<EvilBool>;
explicit EvilBool(bool x) : value(x) {}
EvilBool& operator=(EvilBool const& other) = default;
public:
bool value;
};
int EvilBool::bang_called = 0;
struct ExplicitBool {
ExplicitBool(ExplicitBool const&) = default;
ExplicitBool(ExplicitBool&&) = default;
explicit operator bool() const { return value; }
private:
friend struct MoveOnlyCallable<ExplicitBool>;
friend struct CopyCallable<ExplicitBool>;
explicit ExplicitBool(bool x) : value(x) {}
ExplicitBool& operator=(bool x) {
value = x;
return *this;
}
bool value;
};
struct NoExceptEvilBool {
NoExceptEvilBool(NoExceptEvilBool const&) = default;
NoExceptEvilBool(NoExceptEvilBool&&) = default;
NoExceptEvilBool& operator=(NoExceptEvilBool const& other) = default;
explicit NoExceptEvilBool(bool x) : value(x) {}
friend NoExceptEvilBool operator!(NoExceptEvilBool const& other) noexcept {
return NoExceptEvilBool{!other.value};
}
bool value;
};
void constructor_tests()
{
{
using T = MoveOnlyCallable<bool>;
T value(true);
using RetT = decltype(std::not_fn(std::move(value)));
static_assert(std::is_move_constructible<RetT>::value, "");
static_assert(!std::is_copy_constructible<RetT>::value, "");
static_assert(!std::is_move_assignable<RetT>::value, "");
static_assert(!std::is_copy_assignable<RetT>::value, "");
auto ret = std::not_fn(std::move(value));
// test it was moved from
assert(value.value == false);
// test that ret() negates the original value 'true'
assert(ret() == false);
assert(ret(0, 0.0, "blah") == false);
// Move ret and test that it was moved from and that ret2 got the
// original value.
auto ret2 = std::move(ret);
assert(ret() == true);
assert(ret2() == false);
assert(ret2(42) == false);
}
{
using T = CopyCallable<bool>;
T value(false);
using RetT = decltype(std::not_fn(value));
static_assert(std::is_move_constructible<RetT>::value, "");
static_assert(std::is_copy_constructible<RetT>::value, "");
static_assert(!std::is_move_assignable<RetT>::value, "");
static_assert(!std::is_copy_assignable<RetT>::value, "");
auto ret = std::not_fn(value);
// test that value is unchanged (copied not moved)
assert(value.value == false);
// test 'ret' has the original value
assert(ret() == true);
assert(ret(42, 100) == true);
// move from 'ret' and check that 'ret2' has the original value.
auto ret2 = std::move(ret);
assert(ret() == false);
assert(ret2() == true);
assert(ret2("abc") == true);
}
{
using T = CopyAssignableWrapper;
T value(true);
T value2(false);
using RetT = decltype(std::not_fn(value));
static_assert(std::is_move_constructible<RetT>::value, "");
static_assert(std::is_copy_constructible<RetT>::value, "");
LIBCPP_STATIC_ASSERT(std::is_move_assignable<RetT>::value, "");
LIBCPP_STATIC_ASSERT(std::is_copy_assignable<RetT>::value, "");
auto ret = std::not_fn(value);
assert(ret() == false);
auto ret2 = std::not_fn(value2);
assert(ret2() == true);
#if defined(_LIBCPP_VERSION)
ret = ret2;
assert(ret() == true);
assert(ret2() == true);
#endif // _LIBCPP_VERSION
}
{
using T = MoveAssignableWrapper;
T value(true);
T value2(false);
using RetT = decltype(std::not_fn(std::move(value)));
static_assert(std::is_move_constructible<RetT>::value, "");
static_assert(!std::is_copy_constructible<RetT>::value, "");
LIBCPP_STATIC_ASSERT(std::is_move_assignable<RetT>::value, "");
static_assert(!std::is_copy_assignable<RetT>::value, "");
auto ret = std::not_fn(std::move(value));
assert(ret() == false);
auto ret2 = std::not_fn(std::move(value2));
assert(ret2() == true);
#if defined(_LIBCPP_VERSION)
ret = std::move(ret2);
assert(ret() == true);
#endif // _LIBCPP_VERSION
}
}
void return_type_tests()
{
using std::is_same;
{
using T = CopyCallable<bool>;
auto ret = std::not_fn(T{false});
static_assert(is_same<decltype(ret()), bool>::value, "");
static_assert(is_same<decltype(ret("abc")), bool>::value, "");
assert(ret() == true);
}
{
using T = CopyCallable<ExplicitBool>;
auto ret = std::not_fn(T{true});
static_assert(is_same<decltype(ret()), bool>::value, "");
static_assert(is_same<decltype(ret(std::string("abc"))), bool>::value, "");
assert(ret() == false);
}
{
using T = CopyCallable<EvilBool>;
auto ret = std::not_fn(T{false});
static_assert(is_same<decltype(ret()), EvilBool>::value, "");
EvilBool::bang_called = 0;
auto value_ret = ret();
assert(EvilBool::bang_called == 1);
assert(value_ret.value == true);
ret();
assert(EvilBool::bang_called == 2);
}
}
// Other tests only test using objects with call operators. Test various
// other callable types here.
void other_callable_types_test()
{
{ // test with function pointer
auto ret = std::not_fn(returns_true);
assert(ret() == false);
}
{ // test with lambda
auto returns_value = [](bool value) { return value; };
auto ret = std::not_fn(returns_value);
assert(ret(true) == false);
assert(ret(false) == true);
}
{ // test with pointer to member function
MemFunCallable mt(true);
const MemFunCallable mf(false);
auto ret = std::not_fn(&MemFunCallable::return_value);
assert(ret(mt) == false);
assert(ret(mf) == true);
assert(ret(&mt) == false);
assert(ret(&mf) == true);
}
{ // test with pointer to member function
MemFunCallable mt(true);
MemFunCallable mf(false);
auto ret = std::not_fn(&MemFunCallable::return_value_nc);
assert(ret(mt) == false);
assert(ret(mf) == true);
assert(ret(&mt) == false);
assert(ret(&mf) == true);
}
{ // test with pointer to member data
MemFunCallable mt(true);
const MemFunCallable mf(false);
auto ret = std::not_fn(&MemFunCallable::value);
assert(ret(mt) == false);
assert(ret(mf) == true);
assert(ret(&mt) == false);
assert(ret(&mf) == true);
}
}
void throws_in_constructor_test()
{
#ifndef TEST_HAS_NO_EXCEPTIONS
struct ThrowsOnCopy {
ThrowsOnCopy(ThrowsOnCopy const&) {
throw 42;
}
ThrowsOnCopy() = default;
bool operator()() const {
assert(false);
#if defined(TEST_COMPILER_C1XX)
__assume(0);
#else
__builtin_unreachable();
#endif
}
};
{
ThrowsOnCopy cp;
try {
(void)std::not_fn(cp);
assert(false);
} catch (int const& value) {
assert(value == 42);
}
}
#endif
}
void call_operator_sfinae_test() {
{ // wrong number of arguments
using T = decltype(std::not_fn(returns_true));
static_assert(std::is_invocable<T>::value, ""); // callable only with no args
static_assert(!std::is_invocable<T, bool>::value, "");
}
{ // violates const correctness (member function pointer)
using T = decltype(std::not_fn(&MemFunCallable::return_value_nc));
static_assert(std::is_invocable<T, MemFunCallable&>::value, "");
static_assert(!std::is_invocable<T, const MemFunCallable&>::value, "");
}
{ // violates const correctness (call object)
using Obj = CopyCallable<bool>;
using NCT = decltype(std::not_fn(Obj{true}));
using CT = const NCT;
static_assert(std::is_invocable<NCT>::value, "");
static_assert(!std::is_invocable<CT>::value, "");
}
{ // returns bad type with no operator!
auto fn = [](auto x) { return x; };
using T = decltype(std::not_fn(fn));
static_assert(std::is_invocable<T, bool>::value, "");
static_assert(!std::is_invocable<T, std::string>::value, "");
}
}
void call_operator_forwarding_test()
{
using Fn = ForwardingCallObject;
auto obj = std::not_fn(Fn{});
const auto& c_obj = obj;
{ // test zero args
obj();
assert(Fn::check_call<>(CT_NonConst | CT_LValue));
std::move(obj)();
assert(Fn::check_call<>(CT_NonConst | CT_RValue));
c_obj();
assert(Fn::check_call<>(CT_Const | CT_LValue));
std::move(c_obj)();
assert(Fn::check_call<>(CT_Const | CT_RValue));
}
{ // test value categories
int x = 42;
const int cx = 42;
obj(x);
assert(Fn::check_call<int&>(CT_NonConst | CT_LValue));
obj(cx);
assert(Fn::check_call<const int&>(CT_NonConst | CT_LValue));
obj(std::move(x));
assert(Fn::check_call<int&&>(CT_NonConst | CT_LValue));
obj(std::move(cx));
assert(Fn::check_call<const int&&>(CT_NonConst | CT_LValue));
obj(42);
assert(Fn::check_call<int&&>(CT_NonConst | CT_LValue));
}
{ // test value categories - rvalue
int x = 42;
const int cx = 42;
std::move(obj)(x);
assert(Fn::check_call<int&>(CT_NonConst | CT_RValue));
std::move(obj)(cx);
assert(Fn::check_call<const int&>(CT_NonConst | CT_RValue));
std::move(obj)(std::move(x));
assert(Fn::check_call<int&&>(CT_NonConst | CT_RValue));
std::move(obj)(std::move(cx));
assert(Fn::check_call<const int&&>(CT_NonConst | CT_RValue));
std::move(obj)(42);
assert(Fn::check_call<int&&>(CT_NonConst | CT_RValue));
}
{ // test value categories - const call
int x = 42;
const int cx = 42;
c_obj(x);
assert(Fn::check_call<int&>(CT_Const | CT_LValue));
c_obj(cx);
assert(Fn::check_call<const int&>(CT_Const | CT_LValue));
c_obj(std::move(x));
assert(Fn::check_call<int&&>(CT_Const | CT_LValue));
c_obj(std::move(cx));
assert(Fn::check_call<const int&&>(CT_Const | CT_LValue));
c_obj(42);
assert(Fn::check_call<int&&>(CT_Const | CT_LValue));
}
{ // test value categories - const call rvalue
int x = 42;
const int cx = 42;
std::move(c_obj)(x);
assert(Fn::check_call<int&>(CT_Const | CT_RValue));
std::move(c_obj)(cx);
assert(Fn::check_call<const int&>(CT_Const | CT_RValue));
std::move(c_obj)(std::move(x));
assert(Fn::check_call<int&&>(CT_Const | CT_RValue));
std::move(c_obj)(std::move(cx));
assert(Fn::check_call<const int&&>(CT_Const | CT_RValue));
std::move(c_obj)(42);
assert(Fn::check_call<int&&>(CT_Const | CT_RValue));
}
{ // test multi arg
const double y = 3.14;
std::string s = "abc";
obj(42, std::move(y), s, std::string{"foo"});
Fn::check_call<int&&, const double&&, std::string&, std::string&&>(CT_NonConst | CT_LValue);
std::move(obj)(42, std::move(y), s, std::string{"foo"});
Fn::check_call<int&&, const double&&, std::string&, std::string&&>(CT_NonConst | CT_RValue);
c_obj(42, std::move(y), s, std::string{"foo"});
Fn::check_call<int&&, const double&&, std::string&, std::string&&>(CT_Const | CT_LValue);
std::move(c_obj)(42, std::move(y), s, std::string{"foo"});
Fn::check_call<int&&, const double&&, std::string&, std::string&&>(CT_Const | CT_RValue);
}
}
void call_operator_noexcept_test()
{
{
using T = ConstCallable<bool>;
T value(true);
auto ret = std::not_fn(value);
static_assert(!noexcept(ret()), "call should not be noexcept");
auto const& cret = ret;
static_assert(!noexcept(cret()), "call should not be noexcept");
}
{
using T = NoExceptCallable<bool>;
T value(true);
auto ret = std::not_fn(value);
LIBCPP_STATIC_ASSERT(noexcept(!_VSTD::__invoke(value)), "");
#if TEST_STD_VER > 14
static_assert(noexcept(!std::invoke(value)), "");
#endif
static_assert(noexcept(ret()), "call should be noexcept");
auto const& cret = ret;
static_assert(noexcept(cret()), "call should be noexcept");
}
{
using T = NoExceptCallable<NoExceptEvilBool>;
T value(true);
auto ret = std::not_fn(value);
static_assert(noexcept(ret()), "call should not be noexcept");
auto const& cret = ret;
static_assert(noexcept(cret()), "call should not be noexcept");
}
{
using T = NoExceptCallable<EvilBool>;
T value(true);
auto ret = std::not_fn(value);
static_assert(!noexcept(ret()), "call should not be noexcept");
auto const& cret = ret;
static_assert(!noexcept(cret()), "call should not be noexcept");
}
}
void test_lwg2767() {
// See https://cplusplus.github.io/LWG/lwg-defects.html#2767
struct Abstract { virtual void f() const = 0; };
struct Derived : public Abstract { void f() const {} };
struct F { bool operator()(Abstract&&) { return false; } };
{
Derived d;
Abstract &a = d;
bool b = std::not_fn(F{})(std::move(a));
assert(b);
}
}
int main(int, char**)
{
constructor_tests();
return_type_tests();
other_callable_types_test();
throws_in_constructor_test();
call_operator_sfinae_test(); // somewhat of an extension
call_operator_forwarding_test();
call_operator_noexcept_test();
test_lwg2767();
return 0;
}