aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Wood <aaronwood@google.com>2017-10-31 10:49:01 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-03-02 13:25:19 -0800
commitbdfc8281d8e94e47bddf2f9f7401bcdb3beb80ad (patch)
tree150a94353cb564a7abb3aec21ee0058859aebeec
parent037e08dae3b6c1b288ac35b35245c7d8bed84a58 (diff)
downloadplatform_external_libbrillo-bdfc8281d8e94e47bddf2f9f7401bcdb3beb80ad.tar.gz
platform_external_libbrillo-bdfc8281d8e94e47bddf2f9f7401bcdb3beb80ad.tar.bz2
platform_external_libbrillo-bdfc8281d8e94e47bddf2f9f7401bcdb3beb80ad.zip
libbrillo: Bitwise operator templates for enums
When an enum is used to define bitwise flags that are meant to be combined (FLAG_ONE | FLAG_TWO), the resultant value needs to be cast back to the enum type in order to be stored in a variable of the same type as the enum (as the result is a value of underlying_type of the enum). This adds a macro that allows templated bitwise operators to be defined so that the enumerators can be used more cleanly in code, while preserving type-safety. BUG=b:37434548 BUG=chromium:815356 TEST=unittests (cherry picked from commit 1d20c7da0b8833ffda7dfff2066205ea1af7e771) Change-Id: I35b2a5b99fe87d63f1d0a0c01fba03e7b2f9a58b Reviewed-on: https://chromium-review.googlesource.com/941982 Commit-Ready: Amin Hassani <ahassani@chromium.org> Tested-by: Amin Hassani <ahassani@chromium.org> Reviewed-by: Amin Hassani <ahassani@chromium.org>
-rw-r--r--Android.mk1
-rw-r--r--brillo/enum_flags.h127
-rw-r--r--brillo/enum_flags_unittest.cc241
-rw-r--r--libbrillo.gypi1
4 files changed, 370 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
index 00190db..0c03883 100644
--- a/Android.mk
+++ b/Android.mk
@@ -101,6 +101,7 @@ libbrillo_test_sources := \
brillo/asynchronous_signal_handler_unittest.cc \
brillo/backoff_entry_unittest.cc \
brillo/data_encoding_unittest.cc \
+ brillo/enum_flags_unittest.cc \
brillo/errors/error_codes_unittest.cc \
brillo/errors/error_unittest.cc \
brillo/file_utils_unittest.cc \
diff --git a/brillo/enum_flags.h b/brillo/enum_flags.h
new file mode 100644
index 0000000..9630dd0
--- /dev/null
+++ b/brillo/enum_flags.h
@@ -0,0 +1,127 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_ENUM_FLAGS_H_
+#define LIBBRILLO_BRILLO_ENUM_FLAGS_H_
+
+#include <type_traits>
+
+// This is a helper for generating type-safe bitwise operators for flags that
+// are defined by an enumeration. By default, when a bitwise operation is
+// performed on two enumerators of an enumeration, the result is the base type
+// (int), not a value of the enumeration:
+//
+// enum SomeEnumOfFlags {
+// ONE = 1,
+// TWO = 2,
+// THREE = 4,
+// // etc.
+// };
+//
+// SomeEnumOfFlags flags = static_cast<SomeEnumOfFlags>(ONE | TWO);
+//
+// By enabling these operators for an enum type:
+//
+// DECLARE_FLAGS_ENUM(SomeEnumOfFlags);
+//
+// The syntax is simplified to:
+//
+// SomeEnumOfFlags flags = ONE | TWO;
+//
+// But the following still does not compile without using a cast (as is
+// expected):
+//
+// SomeEnumOfFlags flags = ONE | 2;
+
+// This is the macro used to declare that an enum type |ENUM| should have bit-
+// wise operators defined for it.
+#define DECLARE_FLAGS_ENUM(ENUM) \
+template <typename> struct EnumFlagTraitType; \
+template <> struct EnumFlagTraitType<ENUM> { using EnumFlagType = ENUM; }; \
+EnumFlagTraitType<ENUM> GetEnumFlagTraitType(ENUM) __attribute__((used));
+
+
+// Setup the templates used to declare that the operators should exist for a
+// given type T.
+
+namespace enum_details {
+
+template <typename T>
+using FlagEnumTraits = decltype(GetEnumFlagTraitType(std::declval<T>()));
+
+template <typename T>
+using Void = void;
+
+template <typename T, typename = void>
+struct IsFlagEnum : std::false_type {};
+
+template <typename T>
+struct IsFlagEnum<T, Void<typename FlagEnumTraits<T>::EnumFlagType>> : std::true_type {};
+
+} // namespace enum_details
+
+// The operators themselves, conditional on having been declared that they are
+// flag-style enums.
+
+// T operator~(T&)
+template <typename T>
+constexpr typename std::enable_if<enum_details::IsFlagEnum<T>::value, T>::type
+operator~(const T& l) {
+ return static_cast<T>( ~static_cast<typename std::underlying_type<T>::type>(l));
+}
+
+// T operator|(T&, T&)
+template <typename T>
+constexpr typename std::enable_if<enum_details::IsFlagEnum<T>::value, T>::type
+operator|(const T& l, const T& r) {
+ return static_cast<T>(
+ static_cast<typename std::underlying_type<T>::type>(l) |
+ static_cast<typename std::underlying_type<T>::type>(r));
+}
+
+// T operator&(T&, T&)
+template <typename T>
+constexpr typename std::enable_if<enum_details::IsFlagEnum<T>::value, T>::type
+operator&(const T& l, const T& r) {
+ return static_cast<T>(
+ static_cast<typename std::underlying_type<T>::type>(l) &
+ static_cast<typename std::underlying_type<T>::type>(r));
+}
+
+// T operator^(T&, T&)
+template <typename T>
+constexpr typename std::enable_if<enum_details::IsFlagEnum<T>::value, T>::type operator^(
+ const T& l, const T& r) {
+ return static_cast<T>(static_cast<typename std::underlying_type<T>::type>(l) ^
+ static_cast<typename std::underlying_type<T>::type>(r));
+};
+
+// T operator|=(T&, T&)
+template <typename T>
+constexpr typename std::enable_if<enum_details::IsFlagEnum<T>::value, T>::type operator|=(
+ T& l, const T& r) {
+ return l = static_cast<T>(
+ static_cast<typename std::underlying_type<T>::type>(l) |
+ static_cast<typename std::underlying_type<T>::type>(r));
+};
+
+// T operator&=(T&, T&)
+template <typename T>
+constexpr typename std::enable_if<enum_details::IsFlagEnum<T>::value, T>::type operator&=(
+ T& l, const T& r) {
+ return l = static_cast<T>(
+ static_cast<typename std::underlying_type<T>::type>(l) &
+ static_cast<typename std::underlying_type<T>::type>(r));
+};
+
+// T operator^=(T&, T&)
+template <typename T>
+constexpr typename std::enable_if<enum_details::IsFlagEnum<T>::value, T>::type operator^=(
+ T& l, const T& r) {
+ return l = static_cast<T>(
+ static_cast<typename std::underlying_type<T>::type>(l) ^
+ static_cast<typename std::underlying_type<T>::type>(r));
+};
+
+#endif // LIBBRILLO_BRILLO_ENUM_FLAGS_H_
diff --git a/brillo/enum_flags_unittest.cc b/brillo/enum_flags_unittest.cc
new file mode 100644
index 0000000..e57b4ad
--- /dev/null
+++ b/brillo/enum_flags_unittest.cc
@@ -0,0 +1,241 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "brillo/enum_flags.h"
+
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+class EnumFlagsTest : public testing::Test {};
+
+enum SomeFlagsEnum /* : int */ {
+ FLAG_NONE = 0,
+ FLAG_ONE = 1,
+ FLAG_TWO = 2,
+ FLAG_THREE = 4,
+};
+
+enum class SomeFlagsEnumClass /* : int */ {
+ NONE = 0,
+ ONE = 1,
+ TWO = 2,
+ THREE = 4,
+};
+
+enum SomeBigFlagsEnum : int64_t {
+ BIG_FLAG_NONE = 0,
+ BIG_FLAG_ONE = 1,
+ BIG_FLAG_TWO = 2,
+ BIG_FLAG_THREE = 4,
+ BIG_FLAG_FOUR = 8,
+};
+
+DECLARE_FLAGS_ENUM(SomeFlagsEnum);
+DECLARE_FLAGS_ENUM(SomeFlagsEnumClass);
+DECLARE_FLAGS_ENUM(SomeBigFlagsEnum);
+
+// These first tests show how these operators are meant to be used.
+
+TEST_F(EnumFlagsTest, SampleUsage) {
+ SomeFlagsEnum value = FLAG_NONE;
+
+ // Set a flag.
+ value |= FLAG_ONE;
+ EXPECT_EQ(FLAG_ONE, value);
+
+ // Set another
+ value |= FLAG_THREE;
+ EXPECT_EQ(FLAG_ONE | FLAG_THREE, value);
+
+ // Clear a flag
+ value &= ~FLAG_ONE;
+ EXPECT_EQ(FLAG_THREE, value);
+
+ // Toggle a flag
+ value ^= FLAG_TWO;
+ EXPECT_EQ(FLAG_THREE | FLAG_TWO, value);
+}
+
+TEST_F(EnumFlagsTest, SampleUsageOfMasks) {
+ SomeFlagsEnum flags = FLAG_ONE | FLAG_THREE;
+
+ EXPECT_TRUE(flags & FLAG_ONE);
+ EXPECT_TRUE(flags & FLAG_THREE);
+ EXPECT_FALSE(flags & FLAG_TWO);
+ EXPECT_TRUE(flags & ~FLAG_TWO);
+}
+
+TEST_F(EnumFlagsTest, SampleUsageWithEnumClass) {
+ SomeFlagsEnumClass value = SomeFlagsEnumClass::NONE;
+
+ // Set a flag.
+ value |= SomeFlagsEnumClass::ONE;
+ EXPECT_EQ(SomeFlagsEnumClass::ONE, value);
+
+ // Set another
+ value |= SomeFlagsEnumClass::THREE;
+ EXPECT_EQ(SomeFlagsEnumClass::ONE | SomeFlagsEnumClass::THREE, value);
+
+ // Clear a flag
+ value &= ~SomeFlagsEnumClass::ONE;
+ EXPECT_EQ(SomeFlagsEnumClass::THREE, value);
+
+ // Toggle a flag
+ value ^= SomeFlagsEnumClass::TWO;
+ EXPECT_EQ(SomeFlagsEnumClass::THREE | SomeFlagsEnumClass::TWO, value);
+}
+
+TEST_F(EnumFlagsTest, SampleUsageWithBigEnumType) {
+ SomeBigFlagsEnum value = BIG_FLAG_NONE;
+
+ // Set a flag.
+ value |= BIG_FLAG_ONE;
+ EXPECT_EQ(BIG_FLAG_ONE, value);
+
+ // Set another
+ value |= BIG_FLAG_THREE;
+ EXPECT_EQ(FLAG_ONE | BIG_FLAG_THREE, value);
+
+ // Clear a flag
+ value &= ~BIG_FLAG_ONE;
+ EXPECT_EQ(BIG_FLAG_THREE, value);
+
+ // Toggle a flag
+ value ^= BIG_FLAG_TWO;
+ EXPECT_EQ(BIG_FLAG_THREE | BIG_FLAG_TWO, value);
+}
+
+// These following tests verify the binary behavior of the operators. They do
+// not demonstrate standard usage.
+
+TEST_F(EnumFlagsTest, BinaryBehaviorOfAssignmentOperators) {
+ SomeFlagsEnum value = FLAG_NONE;
+
+ // Set a flag.
+ value |= FLAG_ONE;
+ EXPECT_EQ(1, value);
+
+ // Set another
+ value |= FLAG_THREE;
+ EXPECT_EQ(5, value);
+
+ // Clear a flag
+ value &= ~FLAG_ONE;
+ EXPECT_EQ(4, value);
+
+ // Toggle a flag
+ value ^= FLAG_TWO;
+ EXPECT_EQ(6, value);
+}
+
+TEST_F(EnumFlagsTest, BinaryBehaviorOfAssignmentOperatorsWithEnumClass) {
+ SomeFlagsEnumClass value = SomeFlagsEnumClass::NONE;
+
+ // Set a flag.
+ value |= SomeFlagsEnumClass::ONE;
+ EXPECT_EQ(1, static_cast<int>(value)); //
+
+ // Set another
+ value |= SomeFlagsEnumClass::THREE;
+ EXPECT_EQ(5, static_cast<int>(value));
+
+ // Clear a flag
+ value &= ~SomeFlagsEnumClass::ONE;
+ EXPECT_EQ(4, static_cast<int>(value));
+
+ // Toggle a flag
+ value ^= SomeFlagsEnumClass::TWO;
+ EXPECT_EQ(6, static_cast<int>(value));
+}
+
+TEST_F(EnumFlagsTest, BinaryBehaviorOfSimpleOperations) {
+ // These values are set directly with a cast for clarity.
+ const int all_bits_int = -1;
+ const SomeFlagsEnum all_bits = static_cast<SomeFlagsEnum>(all_bits_int);
+ const SomeFlagsEnum just_2_bits = static_cast<SomeFlagsEnum>(3);
+
+ // Inverting a flag should result in all bits set in the base type but that
+ // one.
+ EXPECT_EQ(-2, ~FLAG_ONE);
+ EXPECT_EQ(-3, ~FLAG_TWO);
+
+ // OR'ing two flags should result in both being set.
+ EXPECT_EQ(3, FLAG_ONE | FLAG_TWO);
+
+ // AND'ing two flags should result in 0.
+ EXPECT_EQ(FLAG_NONE, FLAG_ONE & FLAG_TWO);
+
+ // AND'ing a mask with a flag should result in that flag.
+ EXPECT_EQ(FLAG_ONE, all_bits & FLAG_ONE);
+
+ // XOR'ing two flags should result in both being set.
+ EXPECT_EQ(3, FLAG_ONE ^ FLAG_TWO);
+
+ // XOR'ing a mask with a flag should toggle that flag in the mask.
+ EXPECT_EQ(FLAG_ONE, FLAG_NONE ^ FLAG_ONE);
+ EXPECT_EQ(FLAG_TWO, just_2_bits ^ FLAG_ONE);
+}
+
+TEST_F(EnumFlagsTest, BinaryBehaviorOfSimpleOperationsOnEnumClass) {
+ // These values are set directly with a cast for clarity.
+ const int all_bits_int = -1;
+ const SomeFlagsEnumClass all_bits =
+ static_cast<SomeFlagsEnumClass>(all_bits_int);
+ const SomeFlagsEnumClass just_2_bits = static_cast<SomeFlagsEnumClass>(3);
+
+ // Inverting a flag should result in all bits set in the base type but that
+ // one.
+ EXPECT_EQ(-2, static_cast<int>(~SomeFlagsEnumClass::ONE));
+ EXPECT_EQ(-3, static_cast<int>(~SomeFlagsEnumClass::TWO));
+
+ // OR'ing two flags should result in both being set.
+ EXPECT_EQ(
+ 3, static_cast<int>(SomeFlagsEnumClass::ONE | SomeFlagsEnumClass::TWO));
+
+ // AND'ing two flags should result in 0.
+ EXPECT_EQ(SomeFlagsEnumClass::NONE,
+ SomeFlagsEnumClass::ONE & SomeFlagsEnumClass::TWO);
+
+ // AND'ing a mask with a flag should result in that flag.
+ EXPECT_EQ(SomeFlagsEnumClass::ONE, all_bits & SomeFlagsEnumClass::ONE);
+
+ // XOR'ing two flags should result in both being set.
+ EXPECT_EQ(
+ 3, static_cast<int>(SomeFlagsEnumClass::ONE ^ SomeFlagsEnumClass::TWO));
+
+ // XOR'ing a mask with a flag should toggle that flag in the mask.
+ EXPECT_EQ(SomeFlagsEnumClass::ONE,
+ SomeFlagsEnumClass::NONE ^ SomeFlagsEnumClass::ONE);
+ EXPECT_EQ(SomeFlagsEnumClass::TWO, just_2_bits ^ SomeFlagsEnumClass::ONE);
+}
+
+TEST_F(EnumFlagsTest, BinaryBehaviorOfSimpleOperationsWithBaseType) {
+ // These values are set directly with a cast for clarity.
+ const int64_t all_bits_int = -1;
+ const SomeBigFlagsEnum all_bits = static_cast<SomeBigFlagsEnum>(all_bits_int);
+ const SomeBigFlagsEnum just_2_bits = static_cast<SomeBigFlagsEnum>(3);
+
+ // Inverting a flag should result in all bits set in the base type but that
+ // one.
+ EXPECT_EQ(all_bits ^ BIG_FLAG_ONE, ~BIG_FLAG_ONE);
+
+ // OR'ing two flags should result in both being set.
+ EXPECT_EQ(3, BIG_FLAG_ONE | BIG_FLAG_TWO);
+
+ // AND'ing two flags should result in 0.
+ EXPECT_EQ(BIG_FLAG_NONE, BIG_FLAG_ONE & BIG_FLAG_TWO);
+
+ // AND'ing a mask with a flag should result in that flag.
+ EXPECT_EQ(BIG_FLAG_ONE, all_bits & BIG_FLAG_ONE);
+
+ // XOR'ing two flags should result in both being set.
+ EXPECT_EQ(3, BIG_FLAG_ONE ^ BIG_FLAG_TWO);
+
+ // XOR'ing a mask with a flag should toggle that flag in the mask.
+ EXPECT_EQ(BIG_FLAG_ONE, BIG_FLAG_NONE ^ BIG_FLAG_ONE);
+ EXPECT_EQ(BIG_FLAG_TWO, just_2_bits ^ BIG_FLAG_ONE);
+}
+
+} // namespace brillo
diff --git a/libbrillo.gypi b/libbrillo.gypi
index 822e2d5..7691c82 100644
--- a/libbrillo.gypi
+++ b/libbrillo.gypi
@@ -373,6 +373,7 @@
'brillo/asynchronous_signal_handler_unittest.cc',
'brillo/backoff_entry_unittest.cc',
'brillo/data_encoding_unittest.cc',
+ 'brillo/enum_flags_unittest.cc',
'brillo/errors/error_codes_unittest.cc',
'brillo/errors/error_unittest.cc',
'brillo/file_utils_unittest.cc',