diff options
-rw-r--r-- | gdb/Makefile.in | 1 | ||||
-rw-r--r-- | gdb/btrace.c | 4 | ||||
-rw-r--r-- | gdb/compile/compile-c-types.c | 3 | ||||
-rw-r--r-- | gdb/compile/compile-cplus-symbols.c | 4 | ||||
-rw-r--r-- | gdb/compile/compile-cplus-types.c | 10 | ||||
-rw-r--r-- | gdb/go-exp.y | 2 | ||||
-rw-r--r-- | gdb/record-btrace.c | 10 | ||||
-rw-r--r-- | gdb/unittests/enum-flags-selftests.c | 586 | ||||
-rw-r--r-- | gdbsupport/enum-flags.h | 366 | ||||
-rw-r--r-- | gdbsupport/valid-expr.h | 15 |
10 files changed, 903 insertions, 98 deletions
diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 66abac4..a683d23 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -435,6 +435,7 @@ SELFTESTS_SRCS = \ unittests/command-def-selftests.c \ unittests/common-utils-selftests.c \ unittests/copy_bitwise-selftests.c \ + unittests/enum-flags-selftests.c \ unittests/environ-selftests.c \ unittests/filtered_iterator-selftests.c \ unittests/format_pieces-selftests.c \ diff --git a/gdb/btrace.c b/gdb/btrace.c index 2a0c61d..9022aed 100644 --- a/gdb/btrace.c +++ b/gdb/btrace.c @@ -265,7 +265,7 @@ ftrace_new_function (struct btrace_thread_info *btinfo, static void ftrace_update_caller (struct btrace_function *bfun, struct btrace_function *caller, - enum btrace_function_flag flags) + btrace_function_flags flags) { if (bfun->up != 0) ftrace_debug (bfun, "updating caller"); @@ -283,7 +283,7 @@ static void ftrace_fixup_caller (struct btrace_thread_info *btinfo, struct btrace_function *bfun, struct btrace_function *caller, - enum btrace_function_flag flags) + btrace_function_flags flags) { unsigned int prev, next; diff --git a/gdb/compile/compile-c-types.c b/gdb/compile/compile-c-types.c index 2b25783..0234db5 100644 --- a/gdb/compile/compile-c-types.c +++ b/gdb/compile/compile-c-types.c @@ -254,7 +254,8 @@ convert_qualified (compile_c_instance *context, struct type *type) if (TYPE_RESTRICT (type)) quals |= GCC_QUALIFIER_RESTRICT; - return context->plugin ().build_qualified_type (unqual_converted, quals); + return context->plugin ().build_qualified_type (unqual_converted, + quals.raw ()); } /* Convert a complex type to its gcc representation. */ diff --git a/gdb/compile/compile-cplus-symbols.c b/gdb/compile/compile-cplus-symbols.c index 11a2d32..9840485 100644 --- a/gdb/compile/compile-cplus-symbols.c +++ b/gdb/compile/compile-cplus-symbols.c @@ -208,7 +208,7 @@ convert_one_symbol (compile_cplus_instance *instance, /* Define the decl. */ instance->plugin ().build_decl - ("variable", name.c_str (), kind, sym_type, + ("variable", name.c_str (), kind.raw (), sym_type, symbol_name.get (), addr, filename, line); /* Pop scope for non-local symbols. */ @@ -323,7 +323,7 @@ convert_symbol_bmsym (compile_cplus_instance *instance, sym_type = instance->convert_type (type); instance->plugin ().push_namespace (""); instance->plugin ().build_decl - ("minsym", msym->natural_name (), kind, sym_type, nullptr, addr, + ("minsym", msym->natural_name (), kind.raw (), sym_type, nullptr, addr, nullptr, 0); instance->plugin ().pop_binding_level (""); } diff --git a/gdb/compile/compile-cplus-types.c b/gdb/compile/compile-cplus-types.c index 02df7ab..022cc88 100644 --- a/gdb/compile/compile-cplus-types.c +++ b/gdb/compile/compile-cplus-types.c @@ -668,7 +668,7 @@ compile_cplus_convert_method (compile_cplus_instance *instance, type and corresponding qualifier flags. */ gcc_type func_type = compile_cplus_convert_func (instance, method_type, true); gcc_type class_type = instance->convert_type (parent_type); - gcc_cp_qualifiers_flags quals = (enum gcc_cp_qualifiers) 0; + gcc_cp_qualifiers_flags quals = 0; if (TYPE_CONST (method_type)) quals |= GCC_CP_QUALIFIER_CONST; @@ -681,7 +681,7 @@ compile_cplus_convert_method (compile_cplus_instance *instance, gcc_cp_ref_qualifiers_flags rquals = GCC_CP_REF_QUAL_NONE; return instance->plugin ().build_method_type - (class_type, func_type, quals, rquals); + (class_type, func_type, quals.raw (), rquals.raw ()); } /* Convert a member or method pointer represented by TYPE. */ @@ -745,7 +745,7 @@ compile_cplus_convert_struct_or_union_methods (compile_cplus_instance *instance, (sym_kind | get_method_access_flag (type, i, j) | GCC_CP_FLAG_VIRTUAL_FUNCTION - | GCC_CP_FLAG_PURE_VIRTUAL_FUNCTION), + | GCC_CP_FLAG_PURE_VIRTUAL_FUNCTION).raw (), method_type, nullptr, 0, nullptr, 0); continue; } @@ -787,7 +787,7 @@ compile_cplus_convert_struct_or_union_methods (compile_cplus_instance *instance, instance->plugin ().build_decl (kind, overloaded_name.get (), - sym_kind | get_method_access_flag (type, i, j), + (sym_kind | get_method_access_flag (type, i, j)).raw (), method_type, nullptr, address, filename, line); } } @@ -1060,7 +1060,7 @@ compile_cplus_instance::convert_qualified_base (gcc_type base, gcc_type result = base; if (quals != 0) - result = plugin ().build_qualified_type (base, quals); + result = plugin ().build_qualified_type (base, quals.raw ()); return result; } diff --git a/gdb/go-exp.y b/gdb/go-exp.y index 17c76ac..ee1db2b 100644 --- a/gdb/go-exp.y +++ b/gdb/go-exp.y @@ -924,7 +924,7 @@ parse_string_or_char (const char *tokptr, const char **outptr, } ++tokptr; - value->type = C_STRING | (quote == '\'' ? C_CHAR : 0); /*FIXME*/ + value->type = (int) C_STRING | (quote == '\'' ? C_CHAR : 0); /*FIXME*/ value->ptr = (char *) obstack_base (&tempbuf); value->length = obstack_object_size (&tempbuf); diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c index a1a3efc..fd0d13f 100644 --- a/gdb/record-btrace.c +++ b/gdb/record-btrace.c @@ -1928,7 +1928,7 @@ record_btrace_target::get_tailcall_unwinder () /* Return a human-readable string for FLAG. */ static const char * -btrace_thread_flag_to_str (enum btrace_thread_flag flag) +btrace_thread_flag_to_str (btrace_thread_flags flag) { switch (flag) { @@ -2221,7 +2221,7 @@ record_btrace_target::commit_resume () static void record_btrace_cancel_resume (struct thread_info *tp) { - enum btrace_thread_flag flags; + btrace_thread_flags flags; flags = tp->btrace.flags & (BTHR_MOVE | BTHR_STOP); if (flags == 0) @@ -2229,7 +2229,7 @@ record_btrace_cancel_resume (struct thread_info *tp) DEBUG ("cancel resume thread %s (%s): %x (%s)", print_thread_id (tp), - target_pid_to_str (tp->ptid).c_str (), flags, + target_pid_to_str (tp->ptid).c_str (), flags.raw (), btrace_thread_flag_to_str (flags)); tp->btrace.flags &= ~(BTHR_MOVE | BTHR_STOP); @@ -2449,7 +2449,7 @@ record_btrace_step_thread (struct thread_info *tp) { struct btrace_thread_info *btinfo; struct target_waitstatus status; - enum btrace_thread_flag flags; + btrace_thread_flags flags; btinfo = &tp->btrace; @@ -2457,7 +2457,7 @@ record_btrace_step_thread (struct thread_info *tp) btinfo->flags &= ~(BTHR_MOVE | BTHR_STOP); DEBUG ("stepping thread %s (%s): %x (%s)", print_thread_id (tp), - target_pid_to_str (tp->ptid).c_str (), flags, + target_pid_to_str (tp->ptid).c_str (), flags.raw (), btrace_thread_flag_to_str (flags)); /* We can't step without an execution history. */ 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); +} diff --git a/gdbsupport/enum-flags.h b/gdbsupport/enum-flags.h index 825ff4f..b3e317e 100644 --- a/gdbsupport/enum-flags.h +++ b/gdbsupport/enum-flags.h @@ -18,6 +18,8 @@ #ifndef COMMON_ENUM_FLAGS_H #define COMMON_ENUM_FLAGS_H +#include "traits.h" + /* Type-safe wrapper for enum flags. enum flags are enums where the values are bits that are meant to be ORed together. @@ -51,23 +53,31 @@ #ifdef __cplusplus -/* Traits type used to prevent the global operator overloads from - instantiating for non-flag enums. */ -template<typename T> struct enum_flags_type {}; - -/* Use this to mark an enum as flags enum. It defines FLAGS as +/* Use this to mark an enum as flags enum. It defines FLAGS_TYPE as enum_flags wrapper class for ENUM, and enables the global operator overloads for ENUM. */ #define DEF_ENUM_FLAGS_TYPE(enum_type, flags_type) \ typedef enum_flags<enum_type> flags_type; \ - template<> \ - struct enum_flags_type<enum_type> \ - { \ - typedef enum_flags<enum_type> type; \ - } + void is_enum_flags_enum_type (enum_type *) + +/* To enable the global enum_flags operators for enum, declare an + "is_enum_flags_enum_type" overload that has exactly one parameter, + of type a pointer to that enum class. E.g.,: + + void is_enum_flags_enum_type (enum some_flag *); + + The function does not need to be defined, only declared. + DEF_ENUM_FLAGS_TYPE declares this. + + A function declaration is preferred over a traits type, because the + former allows calling the DEF_ENUM_FLAGS_TYPE macro inside a + namespace to define the corresponding enum flags type in that + namespace. The compiler finds the corresponding + is_enum_flags_enum_type function via ADL. */ -/* Until we can rely on std::underlying type being universally - available (C++11), roll our own for enums. */ +/* Note that std::underlying_type<enum_type> is not what we want here, + since that returns unsigned int even when the enum decays to signed + int. */ template<int size, bool sign> class integer_for_size { typedef void type; }; template<> struct integer_for_size<1, 0> { typedef uint8_t type; }; template<> struct integer_for_size<2, 0> { typedef uint16_t type; }; @@ -86,128 +96,320 @@ struct enum_underlying_type type; }; -template <typename E> -class enum_flags +namespace enum_flags_detail { -public: - typedef E enum_type; - typedef typename enum_underlying_type<enum_type>::type underlying_type; -private: - /* Private type used to support initializing flag types with zero: +/* Private type used to support initializing flag types with zero: - foo_flags f = 0; + foo_flags f = 0; - but not other integers: + but not other integers: - foo_flags f = 1; + foo_flags f = 1; - The way this works is that we define an implicit constructor that - takes a pointer to this private type. Since nothing can - instantiate an object of this type, the only possible pointer to - pass to the constructor is the NULL pointer, or, zero. */ - struct zero_type; + The way this works is that we define an implicit constructor that + takes a pointer to this private type. Since nothing can + instantiate an object of this type, the only possible pointer to + pass to the constructor is the NULL pointer, or, zero. */ +struct zero_type; - underlying_type - underlying_value () const - { - return m_enum_value; - } +/* gdb::Requires trait helpers. */ +template <typename enum_type> +using EnumIsUnsigned + = std::is_unsigned<typename enum_underlying_type<enum_type>::type>; +template <typename enum_type> +using EnumIsSigned + = std::is_signed<typename enum_underlying_type<enum_type>::type>; + +} + +template <typename E> +class enum_flags +{ +public: + typedef E enum_type; + typedef typename enum_underlying_type<enum_type>::type underlying_type; public: /* Allow default construction. */ - enum_flags () + constexpr enum_flags () : m_enum_value ((enum_type) 0) {} + /* The default move/copy ctor/assignment do the right thing. */ + /* If you get an error saying these two overloads are ambiguous, then you tried to mix values of different enum types. */ - enum_flags (enum_type e) + constexpr enum_flags (enum_type e) : m_enum_value (e) {} - enum_flags (struct enum_flags::zero_type *zero) + constexpr enum_flags (enum_flags_detail::zero_type *zero) : m_enum_value ((enum_type) 0) {} - enum_flags &operator&= (enum_type e) + enum_flags &operator&= (enum_flags e) { - m_enum_value = (enum_type) (underlying_value () & e); + m_enum_value = (enum_type) (m_enum_value & e.m_enum_value); return *this; } - enum_flags &operator|= (enum_type e) + enum_flags &operator|= (enum_flags e) { - m_enum_value = (enum_type) (underlying_value () | e); + m_enum_value = (enum_type) (m_enum_value | e.m_enum_value); return *this; } - enum_flags &operator^= (enum_type e) + enum_flags &operator^= (enum_flags e) { - m_enum_value = (enum_type) (underlying_value () ^ e); + m_enum_value = (enum_type) (m_enum_value ^ e.m_enum_value); return *this; } - operator enum_type () const + /* Like raw enums, allow conversion to the underlying type. */ + constexpr operator underlying_type () const { return m_enum_value; } - enum_flags operator& (enum_type e) const - { - return (enum_type) (underlying_value () & e); - } - enum_flags operator| (enum_type e) const - { - return (enum_type) (underlying_value () | e); - } - enum_flags operator^ (enum_type e) const - { - return (enum_type) (underlying_value () ^ e); - } - enum_flags operator~ () const + /* Get the underlying value as a raw enum. */ + constexpr enum_type raw () const { - // We only the underlying type to be unsigned when actually using - // operator~ -- if it were not unsigned, undefined behavior could - // result. However, asserting this in the class itself would - // require too many unnecessary changes to otherwise ok enum - // types. - gdb_static_assert (std::is_unsigned<underlying_type>::value); - return (enum_type) ~underlying_value (); + return m_enum_value; } + /* Binary operations involving some unrelated type (which would be a + bug) are implemented as non-members, and deleted. */ + private: /* Stored as enum_type because GDB knows to print the bit flags neatly if the enum values look like bit flags. */ enum_type m_enum_value; }; +template <typename E> +using is_enum_flags_enum_type_t + = decltype (is_enum_flags_enum_type (std::declval<E *> ())); + /* Global operator overloads. */ -template <typename enum_type> -typename enum_flags_type<enum_type>::type -operator& (enum_type e1, enum_type e2) +/* Generate binary operators. */ + +#define ENUM_FLAGS_GEN_BINOP(OPERATOR_OP, OP) \ + \ + /* Raw enum on both LHS/RHS. Returns raw enum type. */ \ + template <typename enum_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_type \ + OPERATOR_OP (enum_type e1, enum_type e2) \ + { \ + using underlying = typename enum_flags<enum_type>::underlying_type; \ + return (enum_type) (underlying (e1) OP underlying (e2)); \ + } \ + \ + /* enum_flags on the LHS. */ \ + template <typename enum_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_flags<enum_type> \ + OPERATOR_OP (enum_flags<enum_type> e1, enum_type e2) \ + { return e1.raw () OP e2; } \ + \ + /* enum_flags on the RHS. */ \ + template <typename enum_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_flags<enum_type> \ + OPERATOR_OP (enum_type e1, enum_flags<enum_type> e2) \ + { return e1 OP e2.raw (); } \ + \ + /* enum_flags on both LHS/RHS. */ \ + template <typename enum_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_flags<enum_type> \ + OPERATOR_OP (enum_flags<enum_type> e1, enum_flags<enum_type> e2) \ + { return e1.raw () OP e2.raw (); } \ + \ + /* Delete cases involving unrelated types. */ \ + \ + template <typename enum_type, typename unrelated_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_flags<enum_type> \ + OPERATOR_OP (enum_type e1, unrelated_type e2) = delete; \ + \ + template <typename enum_type, typename unrelated_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_flags<enum_type> \ + OPERATOR_OP (unrelated_type e1, enum_type e2) = delete; \ + \ + template <typename enum_type, typename unrelated_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_flags<enum_type> \ + OPERATOR_OP (enum_flags<enum_type> e1, unrelated_type e2) = delete; \ + \ + template <typename enum_type, typename unrelated_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_flags<enum_type> \ + OPERATOR_OP (unrelated_type e1, enum_flags<enum_type> e2) = delete; + +/* Generate non-member compound assignment operators. Only the raw + enum versions are defined here. The enum_flags versions are + defined as member functions, simply because it's less code that + way. + + Note we delete operators that would allow e.g., + + "enum_type | 1" or "enum_type1 | enum_type2" + + because that would allow a mistake like : + enum flags1 { F1_FLAGS1 = 1 }; + enum flags2 { F2_FLAGS2 = 2 }; + enum flags1 val; + switch (val) { + case F1_FLAGS1 | F2_FLAGS2: + ... + + If you really need to 'or' enumerators of different flag types, + cast to integer first. +*/ +#define ENUM_FLAGS_GEN_COMPOUND_ASSIGN(OPERATOR_OP, OP) \ + /* lval reference version. */ \ + template <typename enum_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_type & \ + OPERATOR_OP (enum_type &e1, enum_type e2) \ + { return e1 = e1 OP e2; } \ + \ + /* rval reference version. */ \ + template <typename enum_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_type & \ + OPERATOR_OP (enum_type &&e1, enum_type e2) \ + { return e1 = e1 OP e2; } \ + \ + /* Delete compound assignment from unrelated types. */ \ + \ + template <typename enum_type, typename other_enum_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_type & \ + OPERATOR_OP (enum_type &e1, other_enum_type e2) = delete; \ + \ + template <typename enum_type, typename other_enum_type, \ + typename = is_enum_flags_enum_type_t<enum_type>> \ + constexpr enum_type & \ + OPERATOR_OP (enum_type &&e1, other_enum_type e2) = delete; + +ENUM_FLAGS_GEN_BINOP (operator|, |) +ENUM_FLAGS_GEN_BINOP (operator&, &) +ENUM_FLAGS_GEN_BINOP (operator^, ^) + +ENUM_FLAGS_GEN_COMPOUND_ASSIGN (operator|=, |) +ENUM_FLAGS_GEN_COMPOUND_ASSIGN (operator&=, &) +ENUM_FLAGS_GEN_COMPOUND_ASSIGN (operator^=, ^) + +/* Allow comparison with enum_flags, raw enum, and integers, only. + The latter case allows "== 0". As side effect, it allows comparing + with integer variables too, but that's not a common mistake to + make. It's important to disable comparison with unrelated types to + prevent accidentally comparing with unrelated enum values, which + are convertible to integer, and thus coupled with enum_flags + convertion to underlying type too, would trigger the built-in 'bool + operator==(unsigned, int)' operator. */ + +#define ENUM_FLAGS_GEN_COMP(OPERATOR_OP, OP) \ + \ + /* enum_flags OP enum_flags */ \ + \ + template <typename enum_type> \ + constexpr bool \ + OPERATOR_OP (enum_flags<enum_type> lhs, enum_flags<enum_type> rhs) \ + { return lhs.raw () OP rhs.raw (); } \ + \ + /* enum_flags OP other */ \ + \ + template <typename enum_type> \ + constexpr bool \ + OPERATOR_OP (enum_flags<enum_type> lhs, enum_type rhs) \ + { return lhs.raw () OP rhs; } \ + \ + template <typename enum_type> \ + constexpr bool \ + OPERATOR_OP (enum_flags<enum_type> lhs, int rhs) \ + { return lhs.raw () OP rhs; } \ + \ + template <typename enum_type, typename U> \ + constexpr bool \ + OPERATOR_OP (enum_flags<enum_type> lhs, U rhs) = delete; \ + \ + /* other OP enum_flags */ \ + \ + template <typename enum_type> \ + constexpr bool \ + OPERATOR_OP (enum_type lhs, enum_flags<enum_type> rhs) \ + { return lhs OP rhs.raw (); } \ + \ + template <typename enum_type> \ + constexpr bool \ + OPERATOR_OP (int lhs, enum_flags<enum_type> rhs) \ + { return lhs OP rhs.raw (); } \ + \ + template <typename enum_type, typename U> \ + constexpr bool \ + OPERATOR_OP (U lhs, enum_flags<enum_type> rhs) = delete; + +ENUM_FLAGS_GEN_COMP (operator==, ==) +ENUM_FLAGS_GEN_COMP (operator!=, !=) + +/* Unary operators for the raw flags enum. */ + +/* We require underlying type to be unsigned when using operator~ -- + if it were not unsigned, undefined behavior could result. However, + asserting this in the class itself would require too many + unnecessary changes to usages of otherwise OK enum types. */ +template <typename enum_type, + typename = is_enum_flags_enum_type_t<enum_type>, + typename + = gdb::Requires<enum_flags_detail::EnumIsUnsigned<enum_type>>> +constexpr enum_type +operator~ (enum_type e) { - return enum_flags<enum_type> (e1) & e2; + using underlying = typename enum_flags<enum_type>::underlying_type; + return (enum_type) ~underlying (e); } -template <typename enum_type> -typename enum_flags_type<enum_type>::type -operator| (enum_type e1, enum_type e2) +template <typename enum_type, + typename = is_enum_flags_enum_type_t<enum_type>, + typename = gdb::Requires<enum_flags_detail::EnumIsSigned<enum_type>>> +constexpr void operator~ (enum_type e) = delete; + +template <typename enum_type, + typename = is_enum_flags_enum_type_t<enum_type>, + typename + = gdb::Requires<enum_flags_detail::EnumIsUnsigned<enum_type>>> +constexpr enum_flags<enum_type> +operator~ (enum_flags<enum_type> e) { - return enum_flags<enum_type> (e1) | e2; + using underlying = typename enum_flags<enum_type>::underlying_type; + return (enum_type) ~underlying (e); } -template <typename enum_type> -typename enum_flags_type<enum_type>::type -operator^ (enum_type e1, enum_type e2) -{ - return enum_flags<enum_type> (e1) ^ e2; -} +template <typename enum_type, + typename = is_enum_flags_enum_type_t<enum_type>, + typename = gdb::Requires<enum_flags_detail::EnumIsSigned<enum_type>>> +constexpr void operator~ (enum_flags<enum_type> e) = delete; -template <typename enum_type> -typename enum_flags_type<enum_type>::type -operator~ (enum_type e) -{ - return ~enum_flags<enum_type> (e); -} +/* Delete operator<< and operator>>. */ + +template <typename enum_type, typename any_type, + typename = is_enum_flags_enum_type_t<enum_type>> +void operator<< (const enum_type &, const any_type &) = delete; + +template <typename enum_type, typename any_type, + typename = is_enum_flags_enum_type_t<enum_type>> +void operator<< (const enum_flags<enum_type> &, const any_type &) = delete; + +template <typename enum_type, typename any_type, + typename = is_enum_flags_enum_type_t<enum_type>> +void operator>> (const enum_type &, const any_type &) = delete; + +template <typename enum_type, typename any_type, + typename = is_enum_flags_enum_type_t<enum_type>> +void operator>> (const enum_flags<enum_type> &, const any_type &) = delete; #else /* __cplusplus */ diff --git a/gdbsupport/valid-expr.h b/gdbsupport/valid-expr.h index a22fa61..459de17 100644 --- a/gdbsupport/valid-expr.h +++ b/gdbsupport/valid-expr.h @@ -91,4 +91,19 @@ ESC_PARENS (T1, T2, T3, T4), \ VALID, EXPR_TYPE, EXPR) +#define CHECK_VALID_EXPR_5(T1, T2, T3, T4, T5, VALID, EXPR_TYPE, EXPR) \ + CHECK_VALID_EXPR_INT (ESC_PARENS (typename T1, typename T2, \ + typename T3, typename T4, \ + typename T5), \ + ESC_PARENS (T1, T2, T3, T4, T5), \ + VALID, EXPR_TYPE, EXPR) + +#define CHECK_VALID_EXPR_6(T1, T2, T3, T4, T5, T6, \ + VALID, EXPR_TYPE, EXPR) \ + CHECK_VALID_EXPR_INT (ESC_PARENS (typename T1, typename T2, \ + typename T3, typename T4, \ + typename T5, typename T6), \ + ESC_PARENS (T1, T2, T3, T4, T5, T6), \ + VALID, EXPR_TYPE, EXPR) + #endif /* COMMON_VALID_EXPR_H */ |