aboutsummaryrefslogtreecommitdiff
path: root/gdb/unittests/enum-flags-selftests.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/unittests/enum-flags-selftests.c')
-rw-r--r--gdb/unittests/enum-flags-selftests.c586
1 files changed, 586 insertions, 0 deletions
diff --git a/gdb/unittests/enum-flags-selftests.c b/gdb/unittests/enum-flags-selftests.c
new file mode 100644
index 0000000..17ab5c9
--- /dev/null
+++ b/gdb/unittests/enum-flags-selftests.c
@@ -0,0 +1,586 @@
+/* Self tests for enum-flags for GDB, the GNU debugger.
+
+ Copyright (C) 2016-2020 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program 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 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "defs.h"
+#include "gdbsupport/enum-flags.h"
+#include "gdbsupport/valid-expr.h"
+#include "gdbsupport/selftest.h"
+
+namespace selftests {
+namespace enum_flags_tests {
+
+/* The (real) enum types used in CHECK_VALID. Their names match the
+ template parameter names of the templates defined by CHECK_VALID to
+ make it simpler to use. They could be named differently. */
+
+/* A "real enum". */
+enum RE
+ {
+ RE_FLAG1 = 1 << 1,
+ RE_FLAG2 = 1 << 2,
+ };
+
+/* Another "real enum". */
+enum RE2
+ {
+ RE2_FLAG1 = 1 << 1,
+ RE2_FLAG2 = 1 << 2,
+ };
+
+/* An unsigned "real enum". */
+enum URE : unsigned
+ {
+ URE_FLAG1 = 1 << 1,
+ URE_FLAG2 = 1 << 2,
+ URE_FLAG3 = 0xffffffff,
+ };
+
+/* A non-flags enum. */
+enum NF
+ {
+ NF_FLAG1 = 1 << 1,
+ NF_FLAG2 = 1 << 2,
+ };
+
+/* The corresponding "enum flags" types. */
+DEF_ENUM_FLAGS_TYPE (RE, EF);
+DEF_ENUM_FLAGS_TYPE (RE2, EF2);
+DEF_ENUM_FLAGS_TYPE (URE, UEF);
+
+#if HAVE_IS_TRIVIALLY_COPYABLE
+
+/* So that std::vectors of types that have enum_flags fields can
+ reallocate efficiently memcpy. */
+gdb_static_assert (std::is_trivially_copyable<EF>::value);
+
+#endif
+
+/* A couple globals used as lvalues in the CHECK_VALID expressions
+ below. Their names (and types) match the uppercase type names
+ exposed by CHECK_VALID just to make the expressions easier to
+ follow. */
+static RE re ATTRIBUTE_UNUSED;
+static EF ef ATTRIBUTE_UNUSED;
+
+/* First, compile-time tests that:
+
+ - make sure that incorrect operations with mismatching enum types
+ are caught at compile time.
+
+ - make sure that the same operations but involving the right enum
+ types do compile and that they return the correct type.
+*/
+
+#define CHECK_VALID(VALID, EXPR_TYPE, EXPR) \
+ CHECK_VALID_EXPR_6 (EF, RE, EF2, RE2, UEF, URE, VALID, EXPR_TYPE, EXPR)
+
+typedef std::underlying_type<RE>::type und;
+
+/* Test construction / conversion from/to different types. */
+
+/* RE/EF -> underlying (explicit) */
+CHECK_VALID (true, und, und (RE ()))
+CHECK_VALID (true, und, und (EF ()))
+
+/* RE/EF -> int (explicit) */
+CHECK_VALID (true, int, int (RE ()))
+CHECK_VALID (true, int, int (EF ()))
+
+/* other -> RE */
+
+/* You can construct a raw enum value from an int explicitly to punch
+ a hole in the type system if need to. */
+CHECK_VALID (true, RE, RE (1))
+CHECK_VALID (true, RE, RE (RE2 ()))
+CHECK_VALID (false, void, RE (EF2 ()))
+CHECK_VALID (true, RE, RE (RE ()))
+CHECK_VALID (false, void, RE (EF ()))
+
+/* other -> EF. */
+
+/* As expected, enum-flags is a stronger type than the backing raw
+ enum. Unlike with raw enums, you can't construct an enum flags
+ from an integer nor from an unrelated enum type explicitly. Add an
+ intermediate conversion via the raw enum if you really need it. */
+CHECK_VALID (false, void, EF (1))
+CHECK_VALID (false, void, EF (1u))
+CHECK_VALID (false, void, EF (RE2 ()))
+CHECK_VALID (false, void, EF (EF2 ()))
+CHECK_VALID (true, EF, EF (RE ()))
+CHECK_VALID (true, EF, EF (EF ()))
+
+/* Test operators. */
+
+/* operator OP (raw_enum, int) */
+
+CHECK_VALID (false, void, RE () | 1)
+CHECK_VALID (false, void, RE () & 1)
+CHECK_VALID (false, void, RE () ^ 1)
+
+/* operator OP (int, raw_enum) */
+
+CHECK_VALID (false, void, 1 | RE ())
+CHECK_VALID (false, void, 1 & RE ())
+CHECK_VALID (false, void, 1 ^ RE ())
+
+/* operator OP (enum_flags, int) */
+
+CHECK_VALID (false, void, EF () | 1)
+CHECK_VALID (false, void, EF () & 1)
+CHECK_VALID (false, void, EF () ^ 1)
+
+/* operator OP (int, enum_flags) */
+
+CHECK_VALID (false, void, 1 | EF ())
+CHECK_VALID (false, void, 1 & EF ())
+CHECK_VALID (false, void, 1 ^ EF ())
+
+/* operator OP (raw_enum, raw_enum) */
+
+CHECK_VALID (false, void, RE () | RE2 ())
+CHECK_VALID (false, void, RE () & RE2 ())
+CHECK_VALID (false, void, RE () ^ RE2 ())
+CHECK_VALID (true, RE, RE () | RE ())
+CHECK_VALID (true, RE, RE () & RE ())
+CHECK_VALID (true, RE, RE () ^ RE ())
+
+/* operator OP (enum_flags, raw_enum) */
+
+CHECK_VALID (false, void, EF () | RE2 ())
+CHECK_VALID (false, void, EF () & RE2 ())
+CHECK_VALID (false, void, EF () ^ RE2 ())
+CHECK_VALID (true, EF, EF () | RE ())
+CHECK_VALID (true, EF, EF () & RE ())
+CHECK_VALID (true, EF, EF () ^ RE ())
+
+/* operator OP= (raw_enum, raw_enum), rvalue ref on the lhs. */
+
+CHECK_VALID (false, void, RE () |= RE2 ())
+CHECK_VALID (false, void, RE () &= RE2 ())
+CHECK_VALID (false, void, RE () ^= RE2 ())
+CHECK_VALID (true, RE&, RE () |= RE ())
+CHECK_VALID (true, RE&, RE () &= RE ())
+CHECK_VALID (true, RE&, RE () ^= RE ())
+
+/* operator OP= (raw_enum, raw_enum), lvalue ref on the lhs. */
+
+CHECK_VALID (false, void, re |= RE2 ())
+CHECK_VALID (false, void, re &= RE2 ())
+CHECK_VALID (false, void, re ^= RE2 ())
+CHECK_VALID (true, RE&, re |= RE ())
+CHECK_VALID (true, RE&, re &= RE ())
+CHECK_VALID (true, RE&, re ^= RE ())
+
+/* operator OP= (enum_flags, raw_enum), rvalue ref on the lhs. */
+
+CHECK_VALID (false, void, EF () |= RE2 ())
+CHECK_VALID (false, void, EF () &= RE2 ())
+CHECK_VALID (false, void, EF () ^= RE2 ())
+CHECK_VALID (true, EF&, EF () |= RE ())
+CHECK_VALID (true, EF&, EF () &= RE ())
+CHECK_VALID (true, EF&, EF () ^= RE ())
+
+/* operator OP= (enum_flags, raw_enum), lvalue ref on the lhs. */
+
+CHECK_VALID (false, void, ef |= RE2 ())
+CHECK_VALID (false, void, ef &= RE2 ())
+CHECK_VALID (false, void, ef ^= RE2 ())
+CHECK_VALID (true, EF&, ef |= EF ())
+CHECK_VALID (true, EF&, ef &= EF ())
+CHECK_VALID (true, EF&, ef ^= EF ())
+
+/* operator OP= (enum_flags, enum_flags), rvalue ref on the lhs. */
+
+CHECK_VALID (false, void, EF () |= EF2 ())
+CHECK_VALID (false, void, EF () &= EF2 ())
+CHECK_VALID (false, void, EF () ^= EF2 ())
+CHECK_VALID (true, EF&, EF () |= EF ())
+CHECK_VALID (true, EF&, EF () &= EF ())
+CHECK_VALID (true, EF&, EF () ^= EF ())
+
+/* operator OP= (enum_flags, enum_flags), lvalue ref on the lhs. */
+
+CHECK_VALID (false, void, ef |= EF2 ())
+CHECK_VALID (false, void, ef &= EF2 ())
+CHECK_VALID (false, void, ef ^= EF2 ())
+CHECK_VALID (true, EF&, ef |= EF ())
+CHECK_VALID (true, EF&, ef &= EF ())
+CHECK_VALID (true, EF&, ef ^= EF ())
+
+/* operator~ (raw_enum) */
+
+CHECK_VALID (false, void, ~RE ())
+CHECK_VALID (true, URE, ~URE ())
+
+/* operator~ (enum_flags) */
+
+CHECK_VALID (false, void, ~EF ())
+CHECK_VALID (true, UEF, ~UEF ())
+
+/* Check ternary operator. This exercises implicit conversions. */
+
+CHECK_VALID (true, EF, true ? EF () : RE ())
+CHECK_VALID (true, EF, true ? RE () : EF ())
+
+/* These are valid, but it's not a big deal since you won't be able to
+ assign the resulting integer to an enum or an enum_flags without a
+ cast.
+
+ The latter two tests are disabled on older GCCs because they
+ incorrectly fail with gcc 4.8 and 4.9 at least. Running the test
+ outside a SFINAE context shows:
+
+ invalid user-defined conversion from ‘EF’ to ‘RE2’
+
+ They've been confirmed to compile/pass with gcc 5.3, gcc 7.1 and
+ clang 3.7. */
+
+CHECK_VALID (true, int, true ? EF () : EF2 ())
+CHECK_VALID (true, int, true ? EF2 () : EF ())
+#if GCC_VERSION >= 5003 || defined __clang__
+CHECK_VALID (true, int, true ? EF () : RE2 ())
+CHECK_VALID (true, int, true ? RE2 () : EF ())
+#endif
+
+/* Same, but with an unsigned enum. */
+
+typedef unsigned int uns;
+
+CHECK_VALID (true, uns, true ? EF () : UEF ())
+CHECK_VALID (true, uns, true ? UEF () : EF ())
+#if GCC_VERSION >= 5003 || defined __clang__
+CHECK_VALID (true, uns, true ? EF () : URE ())
+CHECK_VALID (true, uns, true ? URE () : EF ())
+#endif
+
+/* Unfortunately this can't work due to the way C++ computes the
+ return type of the ternary conditional operator. int isn't
+ implicitly convertible to the raw enum type, so the type of the
+ expression is int. And then int is not implicitly convertible to
+ enum_flags.
+
+ GCC 4.8 fails to compile this test with:
+ error: operands to ?: have different types ‘enum_flags<RE>’ and ‘int’
+ Confirmed to work with gcc 4.9, 5.3 and clang 3.7.
+*/
+#if GCC_VERSION >= 4009 || defined __clang__
+CHECK_VALID (false, void, true ? EF () : 0)
+CHECK_VALID (false, void, true ? 0 : EF ())
+#endif
+
+/* Check that the ++/--/<</>>/<<=/>>= operators are deleted. */
+
+CHECK_VALID (false, void, RE ()++)
+CHECK_VALID (false, void, ++RE ())
+CHECK_VALID (false, void, --RE ())
+CHECK_VALID (false, void, RE ()--)
+
+CHECK_VALID (false, void, RE () << 1)
+CHECK_VALID (false, void, RE () >> 1)
+CHECK_VALID (false, void, EF () << 1)
+CHECK_VALID (false, void, EF () >> 1)
+
+CHECK_VALID (false, void, RE () <<= 1)
+CHECK_VALID (false, void, RE () >>= 1)
+CHECK_VALID (false, void, EF () <<= 1)
+CHECK_VALID (false, void, EF () >>= 1)
+
+/* Test comparison operators. */
+
+CHECK_VALID (false, void, EF () == EF2 ())
+CHECK_VALID (false, void, EF () == RE2 ())
+CHECK_VALID (false, void, RE () == EF2 ())
+
+CHECK_VALID (true, bool, EF (RE (1)) == EF (RE (1)))
+CHECK_VALID (true, bool, EF (RE (1)) == RE (1))
+CHECK_VALID (true, bool, RE (1) == EF (RE (1)))
+
+CHECK_VALID (false, void, EF () != EF2 ())
+CHECK_VALID (false, void, EF () != RE2 ())
+CHECK_VALID (false, void, RE () != EF2 ())
+
+/* On clang, disable -Wenum-compare due to "error: comparison of two
+ values with different enumeration types [-Werror,-Wenum-compare]".
+ clang doesn't suppress -Wenum-compare in SFINAE contexts. Not a
+ big deal since misuses like these in GDB will be caught by -Werror
+ anyway. This check is here mainly for completeness. */
+#if defined __clang__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wenum-compare"
+#endif
+CHECK_VALID (true, bool, RE () == RE2 ())
+CHECK_VALID (true, bool, RE () != RE2 ())
+#if defined __clang__
+# pragma GCC diagnostic pop
+#endif
+
+CHECK_VALID (true, bool, EF (RE (1)) != EF (RE (2)))
+CHECK_VALID (true, bool, EF (RE (1)) != RE (2))
+CHECK_VALID (true, bool, RE (1) != EF (RE (2)))
+
+CHECK_VALID (true, bool, EF () == 0)
+
+/* Check we didn't disable/delete comparison between non-flags enums
+ and unrelated types by mistake. */
+CHECK_VALID (true, bool, NF (1) == NF (1))
+CHECK_VALID (true, bool, NF (1) == int (1))
+CHECK_VALID (true, bool, NF (1) == char (1))
+
+/* -------------------------------------------------------------------- */
+
+/* Follows misc tests that exercise the API. Some are compile time,
+ when possible, others are run time. */
+
+enum test_flag
+ {
+ FLAG1 = 1 << 1,
+ FLAG2 = 1 << 2,
+ FLAG3 = 1 << 3,
+ };
+
+enum test_uflag : unsigned
+ {
+ UFLAG1 = 1 << 1,
+ UFLAG2 = 1 << 2,
+ UFLAG3 = 1 << 3,
+ };
+
+DEF_ENUM_FLAGS_TYPE (test_flag, test_flags);
+DEF_ENUM_FLAGS_TYPE (test_uflag, test_uflags);
+
+static void
+self_test ()
+{
+ /* Check that default construction works. */
+ {
+ constexpr test_flags f;
+
+ gdb_static_assert (f == 0);
+ }
+
+ /* Check that assignment from zero works. */
+ {
+ test_flags f (FLAG1);
+
+ SELF_CHECK (f == FLAG1);
+
+ f = 0;
+
+ SELF_CHECK (f == 0);
+ }
+
+ /* Check that construction from zero works. */
+ {
+ constexpr test_flags zero1 = 0;
+ constexpr test_flags zero2 (0);
+ constexpr test_flags zero3 {0};
+ constexpr test_flags zero4 = {0};
+
+ gdb_static_assert (zero1 == 0);
+ gdb_static_assert (zero2 == 0);
+ gdb_static_assert (zero3 == 0);
+ gdb_static_assert (zero4 == 0);
+ }
+
+ /* Check construction from enum value. */
+ {
+ gdb_static_assert (test_flags (FLAG1) == FLAG1);
+ gdb_static_assert (test_flags (FLAG2) != FLAG1);
+ }
+
+ /* Check copy/assignment. */
+ {
+ constexpr test_flags src = FLAG1;
+
+ constexpr test_flags f1 = src;
+ constexpr test_flags f2 (src);
+ constexpr test_flags f3 {src};
+ constexpr test_flags f4 = {src};
+
+ gdb_static_assert (f1 == FLAG1);
+ gdb_static_assert (f2 == FLAG1);
+ gdb_static_assert (f3 == FLAG1);
+ gdb_static_assert (f4 == FLAG1);
+ }
+
+ /* Check moving. */
+ {
+ test_flags src = FLAG1;
+ test_flags dst = 0;
+
+ dst = std::move (src);
+ SELF_CHECK (dst == FLAG1);
+ }
+
+ /* Check construction from an 'or' of multiple bits. For this to
+ work, operator| must be overridden to return an enum type. The
+ builtin version would return int instead and then the conversion
+ to test_flags would fail. */
+ {
+ constexpr test_flags f = FLAG1 | FLAG2;
+ gdb_static_assert (f == (FLAG1 | FLAG2));
+ }
+
+ /* Similarly, check that "FLAG1 | FLAG2" on the rhs of an assignment
+ operator works. */
+ {
+ test_flags f = 0;
+ f |= FLAG1 | FLAG2;
+ SELF_CHECK (f == (FLAG1 | FLAG2));
+
+ f &= FLAG1 | FLAG2;
+ SELF_CHECK (f == (FLAG1 | FLAG2));
+
+ f ^= FLAG1 | FLAG2;
+ SELF_CHECK (f == 0);
+ }
+
+ /* Check explicit conversion to int works. */
+ {
+ constexpr int some_bits (FLAG1 | FLAG2);
+
+ /* And comparison with int works too. */
+ gdb_static_assert (some_bits == (FLAG1 | FLAG2));
+ gdb_static_assert (some_bits == test_flags (FLAG1 | FLAG2));
+ }
+
+ /* Check operator| and operator|=. Particularly interesting is
+ making sure that putting the enum value on the lhs side of the
+ expression works (FLAG | f). */
+ {
+ test_flags f = FLAG1;
+ f |= FLAG2;
+ SELF_CHECK (f == (FLAG1 | FLAG2));
+ }
+ {
+ test_flags f = FLAG1;
+ f = f | FLAG2;
+ SELF_CHECK (f == (FLAG1 | FLAG2));
+ }
+ {
+ test_flags f = FLAG1;
+ f = FLAG2 | f;
+ SELF_CHECK (f == (FLAG1 | FLAG2));
+ }
+
+ /* Check the &/&= operators. */
+ {
+ test_flags f = FLAG1 & FLAG2;
+ SELF_CHECK (f == 0);
+
+ f = FLAG1 | FLAG2;
+ f &= FLAG2;
+ SELF_CHECK (f == FLAG2);
+
+ f = FLAG1 | FLAG2;
+ f = f & FLAG2;
+ SELF_CHECK (f == FLAG2);
+
+ f = FLAG1 | FLAG2;
+ f = FLAG2 & f;
+ SELF_CHECK (f == FLAG2);
+ }
+
+ /* Check the ^/^= operators. */
+ {
+ constexpr test_flags f = FLAG1 ^ FLAG2;
+ gdb_static_assert (f == (FLAG1 ^ FLAG2));
+ }
+
+ {
+ test_flags f = FLAG1 ^ FLAG2;
+ f ^= FLAG3;
+ SELF_CHECK (f == (FLAG1 | FLAG2 | FLAG3));
+ f = f ^ FLAG3;
+ SELF_CHECK (f == (FLAG1 | FLAG2));
+ f = FLAG3 ^ f;
+ SELF_CHECK (f == (FLAG1 | FLAG2 | FLAG3));
+ }
+
+ /* Check operator~. Note this only compiles with unsigned
+ flags. */
+ {
+ constexpr test_uflags f1 = ~UFLAG1;
+ constexpr test_uflags f2 = ~f1;
+ gdb_static_assert (f2 == UFLAG1);
+ }
+
+ /* Check the ternary operator. */
+
+ {
+ /* raw enum, raw enum */
+ constexpr test_flags f1 = true ? FLAG1 : FLAG2;
+ gdb_static_assert (f1 == FLAG1);
+ constexpr test_flags f2 = false ? FLAG1 : FLAG2;
+ gdb_static_assert (f2 == FLAG2);
+ }
+
+ {
+ /* enum flags, raw enum */
+ constexpr test_flags src = FLAG1;
+ constexpr test_flags f1 = true ? src : FLAG2;
+ gdb_static_assert (f1 == FLAG1);
+ constexpr test_flags f2 = false ? src : FLAG2;
+ gdb_static_assert (f2 == FLAG2);
+ }
+
+ {
+ /* enum flags, enum flags */
+ constexpr test_flags src1 = FLAG1;
+ constexpr test_flags src2 = FLAG2;
+ constexpr test_flags f1 = true ? src1 : src2;
+ gdb_static_assert (f1 == src1);
+ constexpr test_flags f2 = false ? src1 : src2;
+ gdb_static_assert (f2 == src2);
+ }
+
+ /* Check that we can use flags in switch expressions (requires
+ unambiguous conversion to integer). Also check that we can use
+ operator| in switch cases, where only constants are allowed.
+ This should work because operator| is constexpr. */
+ {
+ test_flags f = FLAG1 | FLAG2;
+ bool ok = false;
+
+ switch (f)
+ {
+ case FLAG1:
+ break;
+ case FLAG2:
+ break;
+ case FLAG1 | FLAG2:
+ ok = true;
+ break;
+ }
+
+ SELF_CHECK (ok);
+ }
+}
+
+} /* namespace enum_flags_tests */
+} /* namespace selftests */
+
+void _initialize_enum_flags_selftests ();
+
+void
+_initialize_enum_flags_selftests ()
+{
+ selftests::register_test ("enum-flags",
+ selftests::enum_flags_tests::self_test);
+}