mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
b050b744be
I think that `using` is easier to read than `typedef`, and it's the modern C++ thing anyway. Change-Id: Iccb62dc3869cddfb6a684ef3023dcd5b799f3ab2
486 lines
16 KiB
C++
486 lines
16 KiB
C++
/* Copyright (C) 2015-2024 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/>. */
|
|
|
|
#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.
|
|
|
|
This allows writing code like the below, while with raw enums this
|
|
would fail to compile without casts to enum type at the assignments
|
|
to 'f':
|
|
|
|
enum some_flag
|
|
{
|
|
flag_val1 = 1 << 1,
|
|
flag_val2 = 1 << 2,
|
|
flag_val3 = 1 << 3,
|
|
flag_val4 = 1 << 4,
|
|
};
|
|
DEF_ENUM_FLAGS_TYPE(enum some_flag, some_flags);
|
|
|
|
some_flags f = flag_val1 | flag_val2;
|
|
f |= flag_val3;
|
|
|
|
It's also possible to assign literal zero to an enum flags variable
|
|
(meaning, no flags), dispensing adding an awkward explicit "no
|
|
value" value to the enumeration. For example:
|
|
|
|
some_flags f = 0;
|
|
f |= flag_val3 | flag_val4;
|
|
|
|
Note that literal integers other than zero fail to compile:
|
|
|
|
some_flags f = 1; // error
|
|
*/
|
|
|
|
/* 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) \
|
|
using flags_type = enum_flags<enum_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. */
|
|
|
|
/* 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 { using type = void; };
|
|
template<> struct integer_for_size<1, 0> { using type = uint8_t; };
|
|
template<> struct integer_for_size<2, 0> { using type = uint16_t; };
|
|
template<> struct integer_for_size<4, 0> { using type = uint32_t; };
|
|
template<> struct integer_for_size<8, 0> { using type = uint64_t; };
|
|
template<> struct integer_for_size<1, 1> { using type = int8_t; };
|
|
template<> struct integer_for_size<2, 1> { using type = int16_t; };
|
|
template<> struct integer_for_size<4, 1> { using type = int32_t; };
|
|
template<> struct integer_for_size<8, 1> { using type = int64_t; };
|
|
|
|
template<typename T>
|
|
struct enum_underlying_type
|
|
{
|
|
DIAGNOSTIC_PUSH
|
|
DIAGNOSTIC_IGNORE_ENUM_CONSTEXPR_CONVERSION
|
|
using type
|
|
= typename integer_for_size<sizeof (T),
|
|
static_cast<bool>(T (-1) < T (0))>::type;
|
|
DIAGNOSTIC_POP
|
|
};
|
|
|
|
namespace enum_flags_detail
|
|
{
|
|
|
|
/* Private type used to support initializing flag types with zero:
|
|
|
|
foo_flags f = 0;
|
|
|
|
but not other integers:
|
|
|
|
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;
|
|
|
|
/* 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:
|
|
using enum_type = E;
|
|
using underlying_type = typename enum_underlying_type<enum_type>::type;
|
|
|
|
/* For to_string. Maps one enumerator of E to a string. */
|
|
struct string_mapping
|
|
{
|
|
E flag;
|
|
const char *str;
|
|
};
|
|
|
|
/* Convenience for to_string implementations, to build a
|
|
string_mapping array. */
|
|
#define MAP_ENUM_FLAG(ENUM_FLAG) { ENUM_FLAG, #ENUM_FLAG }
|
|
|
|
public:
|
|
/* Allow default construction. */
|
|
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. */
|
|
constexpr enum_flags (enum_type e)
|
|
: m_enum_value (e)
|
|
{}
|
|
constexpr enum_flags (enum_flags_detail::zero_type *zero)
|
|
: m_enum_value ((enum_type) 0)
|
|
{}
|
|
|
|
enum_flags &operator&= (enum_flags e) &
|
|
{
|
|
m_enum_value = (enum_type) (m_enum_value & e.m_enum_value);
|
|
return *this;
|
|
}
|
|
enum_flags &operator|= (enum_flags e) &
|
|
{
|
|
m_enum_value = (enum_type) (m_enum_value | e.m_enum_value);
|
|
return *this;
|
|
}
|
|
enum_flags &operator^= (enum_flags e) &
|
|
{
|
|
m_enum_value = (enum_type) (m_enum_value ^ e.m_enum_value);
|
|
return *this;
|
|
}
|
|
|
|
/* Delete rval versions. */
|
|
void operator&= (enum_flags e) && = delete;
|
|
void operator|= (enum_flags e) && = delete;
|
|
void operator^= (enum_flags e) && = delete;
|
|
|
|
/* Like raw enums, allow conversion to the underlying type. */
|
|
constexpr operator underlying_type () const
|
|
{
|
|
return m_enum_value;
|
|
}
|
|
|
|
/* Get the underlying value as a raw enum. */
|
|
constexpr enum_type raw () const
|
|
{
|
|
return m_enum_value;
|
|
}
|
|
|
|
/* Binary operations involving some unrelated type (which would be a
|
|
bug) are implemented as non-members, and deleted. */
|
|
|
|
/* Convert this object to a std::string, using MAPPING as
|
|
enumerator-to-string mapping array. This is not meant to be
|
|
called directly. Instead, enum_flags specializations should have
|
|
their own to_string function wrapping this one, thus hiding the
|
|
mapping array from callers.
|
|
|
|
Note: this is defined outside the template class so it can use
|
|
the global operators for enum_type, which are only defined after
|
|
the template class. */
|
|
template<size_t N>
|
|
std::string to_string (const string_mapping (&mapping)[N]) const;
|
|
|
|
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. */
|
|
|
|
/* 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>> \
|
|
void \
|
|
OPERATOR_OP (enum_type &&e1, enum_type e2) = delete; \
|
|
\
|
|
/* 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>> \
|
|
void \
|
|
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
|
|
conversion 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)
|
|
{
|
|
using underlying = typename enum_flags<enum_type>::underlying_type;
|
|
return (enum_type) ~underlying (e);
|
|
}
|
|
|
|
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)
|
|
{
|
|
using underlying = typename enum_flags<enum_type>::underlying_type;
|
|
return (enum_type) ~underlying (e);
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
|
|
template<typename E>
|
|
template<size_t N>
|
|
std::string
|
|
enum_flags<E>::to_string (const string_mapping (&mapping)[N]) const
|
|
{
|
|
enum_type flags = raw ();
|
|
std::string res = hex_string (flags);
|
|
res += " [";
|
|
|
|
bool need_space = false;
|
|
for (const auto &entry : mapping)
|
|
{
|
|
if ((flags & entry.flag) != 0)
|
|
{
|
|
/* Work with an unsigned version of the underlying type,
|
|
because if enum_type's underlying type is signed, op~
|
|
won't be defined for it, and, bitwise operations on
|
|
signed types are implementation defined. */
|
|
using uns = typename std::make_unsigned<underlying_type>::type;
|
|
flags &= (enum_type) ~(uns) entry.flag;
|
|
|
|
if (need_space)
|
|
res += " ";
|
|
res += entry.str;
|
|
|
|
need_space = true;
|
|
}
|
|
}
|
|
|
|
/* If there were flags not included in the mapping, print them as
|
|
a hex number. */
|
|
if (flags != 0)
|
|
{
|
|
if (need_space)
|
|
res += " ";
|
|
res += hex_string (flags);
|
|
}
|
|
|
|
res += "]";
|
|
|
|
return res;
|
|
}
|
|
|
|
#endif /* COMMON_ENUM_FLAGS_H */
|