diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /mfbt/tests/TestTypedEnum.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'mfbt/tests/TestTypedEnum.cpp')
-rw-r--r-- | mfbt/tests/TestTypedEnum.cpp | 556 |
1 files changed, 556 insertions, 0 deletions
diff --git a/mfbt/tests/TestTypedEnum.cpp b/mfbt/tests/TestTypedEnum.cpp new file mode 100644 index 000000000..43c36f2b8 --- /dev/null +++ b/mfbt/tests/TestTypedEnum.cpp @@ -0,0 +1,556 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/TypedEnumBits.h" + +#include <stdint.h> + +// A rough feature check for is_literal_type. Not very carefully checked. +// Feel free to amend as needed. +// We leave ANDROID out because it's using stlport which doesn't have std::is_literal_type. +#if __cplusplus >= 201103L && !defined(ANDROID) +# if defined(__clang__) + /* + * Per Clang documentation, "Note that marketing version numbers should not + * be used to check for language features, as different vendors use different + * numbering schemes. Instead, use the feature checking macros." + */ +# ifndef __has_extension +# define __has_extension __has_feature /* compatibility, for older versions of clang */ +# endif +# if __has_extension(is_literal) && __has_include(<type_traits>) +# define MOZ_HAVE_IS_LITERAL +# endif +# elif defined(__GNUC__) || defined(_MSC_VER) +# define MOZ_HAVE_IS_LITERAL +# endif +#endif + +#if defined(MOZ_HAVE_IS_LITERAL) && defined(MOZ_HAVE_CXX11_CONSTEXPR) +#include <type_traits> +template<typename T> +void +RequireLiteralType() +{ + static_assert(std::is_literal_type<T>::value, "Expected a literal type"); +} +#else // not MOZ_HAVE_IS_LITERAL +template<typename T> +void +RequireLiteralType() +{ +} +#endif + +template<typename T> +void +RequireLiteralType(const T&) +{ + RequireLiteralType<T>(); +} + +enum class AutoEnum { + A, + B = -3, + C +}; + +enum class CharEnum : char { + A, + B = 3, + C +}; + +enum class AutoEnumBitField { + A = 0x10, + B = 0x20, + C +}; + +enum class CharEnumBitField : char { + A = 0x10, + B, + C = 0x40 +}; + +struct Nested +{ + enum class AutoEnum { + A, + B, + C = -1 + }; + + enum class CharEnum : char { + A = 4, + B, + C = 1 + }; + + enum class AutoEnumBitField { + A, + B = 0x20, + C + }; + + enum class CharEnumBitField : char { + A = 1, + B = 1, + C = 1 + }; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AutoEnumBitField) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CharEnumBitField) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Nested::AutoEnumBitField) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Nested::CharEnumBitField) + +#define MAKE_STANDARD_BITFIELD_FOR_TYPE(IntType) \ + enum class BitFieldFor_##IntType : IntType { \ + A = 1, \ + B = 2, \ + C = 4, \ + }; \ + MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(BitFieldFor_##IntType) + +MAKE_STANDARD_BITFIELD_FOR_TYPE(int8_t) +MAKE_STANDARD_BITFIELD_FOR_TYPE(uint8_t) +MAKE_STANDARD_BITFIELD_FOR_TYPE(int16_t) +MAKE_STANDARD_BITFIELD_FOR_TYPE(uint16_t) +MAKE_STANDARD_BITFIELD_FOR_TYPE(int32_t) +MAKE_STANDARD_BITFIELD_FOR_TYPE(uint32_t) +MAKE_STANDARD_BITFIELD_FOR_TYPE(int64_t) +MAKE_STANDARD_BITFIELD_FOR_TYPE(uint64_t) +MAKE_STANDARD_BITFIELD_FOR_TYPE(char) +typedef signed char signed_char; +MAKE_STANDARD_BITFIELD_FOR_TYPE(signed_char) +typedef unsigned char unsigned_char; +MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_char) +MAKE_STANDARD_BITFIELD_FOR_TYPE(short) +typedef unsigned short unsigned_short; +MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_short) +MAKE_STANDARD_BITFIELD_FOR_TYPE(int) +typedef unsigned int unsigned_int; +MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_int) +MAKE_STANDARD_BITFIELD_FOR_TYPE(long) +typedef unsigned long unsigned_long; +MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_long) +typedef long long long_long; +MAKE_STANDARD_BITFIELD_FOR_TYPE(long_long) +typedef unsigned long long unsigned_long_long; +MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_long_long) + +#undef MAKE_STANDARD_BITFIELD_FOR_TYPE + +template<typename T> +void +TestNonConvertibilityForOneType() +{ + using mozilla::IsConvertible; + + static_assert(!IsConvertible<T, bool>::value, "should not be convertible"); + static_assert(!IsConvertible<T, int>::value, "should not be convertible"); + static_assert(!IsConvertible<T, uint64_t>::value, "should not be convertible"); + + static_assert(!IsConvertible<bool, T>::value, "should not be convertible"); + static_assert(!IsConvertible<int, T>::value, "should not be convertible"); + static_assert(!IsConvertible<uint64_t, T>::value, "should not be convertible"); +} + +template<typename TypedEnum> +void +TestTypedEnumBasics() +{ + const TypedEnum a = TypedEnum::A; + int unused = int(a); + (void) unused; + RequireLiteralType(TypedEnum::A); + RequireLiteralType(a); + TestNonConvertibilityForOneType<TypedEnum>(); +} + +// Op wraps a bitwise binary operator, passed as a char template parameter, +// and applies it to its arguments (aT1, aT2). For example, +// +// Op<'|'>(aT1, aT2) +// +// is the same as +// +// aT1 | aT2. +// +template<char o, typename T1, typename T2> +auto Op(const T1& aT1, const T2& aT2) + -> decltype(aT1 | aT2) // See the static_assert's below --- the return type + // depends solely on the operands type, not on the + // choice of operation. +{ + using mozilla::IsSame; + static_assert(IsSame<decltype(aT1 | aT2), decltype(aT1 & aT2)>::value, + "binary ops should have the same result type"); + static_assert(IsSame<decltype(aT1 | aT2), decltype(aT1 ^ aT2)>::value, + "binary ops should have the same result type"); + + static_assert(o == '|' || + o == '&' || + o == '^', "unexpected operator character"); + + return o == '|' ? aT1 | aT2 + : o == '&' ? aT1 & aT2 + : aT1 ^ aT2; +} + +// OpAssign wraps a bitwise binary operator, passed as a char template +// parameter, and applies the corresponding compound-assignment operator to its +// arguments (aT1, aT2). For example, +// +// OpAssign<'|'>(aT1, aT2) +// +// is the same as +// +// aT1 |= aT2. +// +template<char o, typename T1, typename T2> +T1& OpAssign(T1& aT1, const T2& aT2) +{ + static_assert(o == '|' || + o == '&' || + o == '^', "unexpected operator character"); + + switch (o) { + case '|': return aT1 |= aT2; + case '&': return aT1 &= aT2; + case '^': return aT1 ^= aT2; + default: MOZ_CRASH(); + } +} + +// Tests a single binary bitwise operator, using a single set of three operands. +// The operations tested are: +// +// result = aT1 Op aT2; +// result Op= aT3; +// +// Where Op is the operator specified by the char template parameter 'o' and +// can be any of '|', '&', '^'. +// +// Note that the operands aT1, aT2, aT3 are intentionally passed with free +// types (separate template parameters for each) because their type may +// actually be different from TypedEnum: +// +// 1) Their type could be CastableTypedEnumResult<TypedEnum> if they are +// the result of a bitwise operation themselves; +// 2) In the non-c++11 legacy path, the type of enum values is also +// different from TypedEnum. +// +template<typename TypedEnum, char o, typename T1, typename T2, typename T3> +void TestBinOp(const T1& aT1, const T2& aT2, const T3& aT3) +{ + typedef typename mozilla::detail::UnsignedIntegerTypeForEnum<TypedEnum>::Type + UnsignedIntegerType; + + // Part 1: + // Test the bitwise binary operator i.e. + // result = aT1 Op aT2; + auto result = Op<o>(aT1, aT2); + + typedef decltype(result) ResultType; + + RequireLiteralType<ResultType>(); + TestNonConvertibilityForOneType<ResultType>(); + + UnsignedIntegerType unsignedIntegerResult = + Op<o>(UnsignedIntegerType(aT1), UnsignedIntegerType(aT2)); + + MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(result)); + MOZ_RELEASE_ASSERT(TypedEnum(unsignedIntegerResult) == TypedEnum(result)); + MOZ_RELEASE_ASSERT((!unsignedIntegerResult) == (!result)); + MOZ_RELEASE_ASSERT((!!unsignedIntegerResult) == (!!result)); + MOZ_RELEASE_ASSERT(bool(unsignedIntegerResult) == bool(result)); + + // Part 2: + // Test the compound-assignment operator, i.e. + // result Op= aT3; + TypedEnum newResult = result; + OpAssign<o>(newResult, aT3); + UnsignedIntegerType unsignedIntegerNewResult = unsignedIntegerResult; + OpAssign<o>(unsignedIntegerNewResult, UnsignedIntegerType(aT3)); + MOZ_RELEASE_ASSERT(TypedEnum(unsignedIntegerNewResult) == newResult); + + // Part 3: + // Test additional boolean operators that we unfortunately had to add to + // CastableTypedEnumResult at some point to please some compiler, + // even though bool convertibility should have been enough. + MOZ_RELEASE_ASSERT(result == TypedEnum(result)); + MOZ_RELEASE_ASSERT(!(result != TypedEnum(result))); + MOZ_RELEASE_ASSERT((result && true) == bool(result)); + MOZ_RELEASE_ASSERT((result && false) == false); + MOZ_RELEASE_ASSERT((true && result) == bool(result)); + MOZ_RELEASE_ASSERT((false && result && false) == false); + MOZ_RELEASE_ASSERT((result || false) == bool(result)); + MOZ_RELEASE_ASSERT((result || true) == true); + MOZ_RELEASE_ASSERT((false || result) == bool(result)); + MOZ_RELEASE_ASSERT((true || result) == true); +} + +// Similar to TestBinOp but testing the unary ~ operator. +template<typename TypedEnum, typename T> +void TestTilde(const T& aT) +{ + typedef typename mozilla::detail::UnsignedIntegerTypeForEnum<TypedEnum>::Type + UnsignedIntegerType; + + auto result = ~aT; + + typedef decltype(result) ResultType; + + RequireLiteralType<ResultType>(); + TestNonConvertibilityForOneType<ResultType>(); + + UnsignedIntegerType unsignedIntegerResult = ~(UnsignedIntegerType(aT)); + + MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(result)); + MOZ_RELEASE_ASSERT(TypedEnum(unsignedIntegerResult) == TypedEnum(result)); + MOZ_RELEASE_ASSERT((!unsignedIntegerResult) == (!result)); + MOZ_RELEASE_ASSERT((!!unsignedIntegerResult) == (!!result)); + MOZ_RELEASE_ASSERT(bool(unsignedIntegerResult) == bool(result)); +} + +// Helper dispatching a given triple of operands to all operator-specific +// testing functions. +template<typename TypedEnum, typename T1, typename T2, typename T3> +void TestAllOpsForGivenOperands(const T1& aT1, const T2& aT2, const T3& aT3) +{ + TestBinOp<TypedEnum, '|'>(aT1, aT2, aT3); + TestBinOp<TypedEnum, '&'>(aT1, aT2, aT3); + TestBinOp<TypedEnum, '^'>(aT1, aT2, aT3); + TestTilde<TypedEnum>(aT1); +} + +// Helper building various triples of operands using a given operator, +// and testing all operators with them. +template<typename TypedEnum, char o> +void TestAllOpsForOperandsBuiltUsingGivenOp() +{ + // The type of enum values like TypedEnum::A may be different from + // TypedEnum. That is the case in the legacy non-C++11 path. We want to + // ensure good test coverage even when these two types are distinct. + // To that effect, we have both 'auto' typed variables, preserving the + // original type of enum values, and 'plain' typed variables, that + // are plain TypedEnum's. + + const TypedEnum a_plain = TypedEnum::A; + const TypedEnum b_plain = TypedEnum::B; + const TypedEnum c_plain = TypedEnum::C; + + auto a_auto = TypedEnum::A; + auto b_auto = TypedEnum::B; + auto c_auto = TypedEnum::C; + + auto ab_plain = Op<o>(a_plain, b_plain); + auto bc_plain = Op<o>(b_plain, c_plain); + auto ab_auto = Op<o>(a_auto, b_auto); + auto bc_auto = Op<o>(b_auto, c_auto); + + // On each row below, we pass a triple of operands. Keep in mind that this + // is going to be received as (aT1, aT2, aT3) and the actual tests performed + // will be of the form + // + // result = aT1 Op aT2; + // result Op= aT3; + // + // For this reason, we carefully ensure that the values of (aT1, aT2) + // systematically cover all types of such pairs; to limit complexity, + // we are not so careful with aT3, and we just try to pass aT3's + // that may lead to nontrivial bitwise operations. + TestAllOpsForGivenOperands<TypedEnum>(a_plain, b_plain, c_plain); + TestAllOpsForGivenOperands<TypedEnum>(a_plain, bc_plain, b_auto); + TestAllOpsForGivenOperands<TypedEnum>(ab_plain, c_plain, a_plain); + TestAllOpsForGivenOperands<TypedEnum>(ab_plain, bc_plain, a_auto); + + TestAllOpsForGivenOperands<TypedEnum>(a_plain, b_auto, c_plain); + TestAllOpsForGivenOperands<TypedEnum>(a_plain, bc_auto, b_auto); + TestAllOpsForGivenOperands<TypedEnum>(ab_plain, c_auto, a_plain); + TestAllOpsForGivenOperands<TypedEnum>(ab_plain, bc_auto, a_auto); + + TestAllOpsForGivenOperands<TypedEnum>(a_auto, b_plain, c_plain); + TestAllOpsForGivenOperands<TypedEnum>(a_auto, bc_plain, b_auto); + TestAllOpsForGivenOperands<TypedEnum>(ab_auto, c_plain, a_plain); + TestAllOpsForGivenOperands<TypedEnum>(ab_auto, bc_plain, a_auto); + + TestAllOpsForGivenOperands<TypedEnum>(a_auto, b_auto, c_plain); + TestAllOpsForGivenOperands<TypedEnum>(a_auto, bc_auto, b_auto); + TestAllOpsForGivenOperands<TypedEnum>(ab_auto, c_auto, a_plain); + TestAllOpsForGivenOperands<TypedEnum>(ab_auto, bc_auto, a_auto); +} + +// Tests all bitwise operations on a given TypedEnum bitfield. +template<typename TypedEnum> +void +TestTypedEnumBitField() +{ + TestTypedEnumBasics<TypedEnum>(); + + TestAllOpsForOperandsBuiltUsingGivenOp<TypedEnum, '|'>(); + TestAllOpsForOperandsBuiltUsingGivenOp<TypedEnum, '&'>(); + TestAllOpsForOperandsBuiltUsingGivenOp<TypedEnum, '^'>(); +} + +// Checks that enum bitwise expressions have the same non-convertibility +// properties as c++11 enum classes do, i.e. not implicitly convertible to +// anything (though *explicitly* convertible). +void TestNoConversionsBetweenUnrelatedTypes() +{ + using mozilla::IsConvertible; + + // Two typed enum classes having the same underlying integer type, to ensure + // that we would catch bugs accidentally allowing conversions in that case. + typedef CharEnumBitField T1; + typedef Nested::CharEnumBitField T2; + + static_assert(!IsConvertible<T1, T2>::value, + "should not be convertible"); + static_assert(!IsConvertible<T1, decltype(T2::A)>::value, + "should not be convertible"); + static_assert(!IsConvertible<T1, decltype(T2::A | T2::B)>::value, + "should not be convertible"); + + static_assert(!IsConvertible<decltype(T1::A), T2>::value, + "should not be convertible"); + static_assert(!IsConvertible<decltype(T1::A), decltype(T2::A)>::value, + "should not be convertible"); + static_assert(!IsConvertible<decltype(T1::A), decltype(T2::A | T2::B)>::value, + "should not be convertible"); + + static_assert(!IsConvertible<decltype(T1::A | T1::B), T2>::value, + "should not be convertible"); + static_assert(!IsConvertible<decltype(T1::A | T1::B), decltype(T2::A)>::value, + "should not be convertible"); + static_assert(!IsConvertible<decltype(T1::A | T1::B), decltype(T2::A | T2::B)>::value, + "should not be convertible"); +} + +enum class Int8EnumWithHighBits : int8_t { + A = 0x20, + B = 0x40 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int8EnumWithHighBits) + +enum class Uint8EnumWithHighBits : uint8_t { + A = 0x40, + B = 0x80 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint8EnumWithHighBits) + +enum class Int16EnumWithHighBits : int16_t { + A = 0x2000, + B = 0x4000 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int16EnumWithHighBits) + +enum class Uint16EnumWithHighBits : uint16_t { + A = 0x4000, + B = 0x8000 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint16EnumWithHighBits) + +enum class Int32EnumWithHighBits : int32_t { + A = 0x20000000, + B = 0x40000000 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int32EnumWithHighBits) + +enum class Uint32EnumWithHighBits : uint32_t { + A = 0x40000000u, + B = 0x80000000u +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint32EnumWithHighBits) + +enum class Int64EnumWithHighBits : int64_t { + A = 0x2000000000000000ll, + B = 0x4000000000000000ll +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int64EnumWithHighBits) + +enum class Uint64EnumWithHighBits : uint64_t { + A = 0x4000000000000000ull, + B = 0x8000000000000000ull +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint64EnumWithHighBits) + +// Checks that we don't accidentally truncate high bits by coercing to the wrong +// integer type internally when implementing bitwise ops. +template<typename EnumType, typename IntType> +void TestIsNotTruncated() +{ + EnumType a = EnumType::A; + EnumType b = EnumType::B; + MOZ_RELEASE_ASSERT(IntType(a)); + MOZ_RELEASE_ASSERT(IntType(b)); + MOZ_RELEASE_ASSERT(a | EnumType::B); + MOZ_RELEASE_ASSERT(a | b); + MOZ_RELEASE_ASSERT(EnumType::A | EnumType::B); + EnumType c = EnumType::A | EnumType::B; + MOZ_RELEASE_ASSERT(IntType(c)); + MOZ_RELEASE_ASSERT(c & c); + MOZ_RELEASE_ASSERT(c | c); + MOZ_RELEASE_ASSERT(c == (EnumType::A | EnumType::B)); + MOZ_RELEASE_ASSERT(a != (EnumType::A | EnumType::B)); + MOZ_RELEASE_ASSERT(b != (EnumType::A | EnumType::B)); + MOZ_RELEASE_ASSERT(c & EnumType::A); + MOZ_RELEASE_ASSERT(c & EnumType::B); + EnumType d = EnumType::A; + d |= EnumType::B; + MOZ_RELEASE_ASSERT(d == c); +} + +int +main() +{ + TestTypedEnumBasics<AutoEnum>(); + TestTypedEnumBasics<CharEnum>(); + TestTypedEnumBasics<Nested::AutoEnum>(); + TestTypedEnumBasics<Nested::CharEnum>(); + + TestTypedEnumBitField<AutoEnumBitField>(); + TestTypedEnumBitField<CharEnumBitField>(); + TestTypedEnumBitField<Nested::AutoEnumBitField>(); + TestTypedEnumBitField<Nested::CharEnumBitField>(); + + TestTypedEnumBitField<BitFieldFor_uint8_t>(); + TestTypedEnumBitField<BitFieldFor_int8_t>(); + TestTypedEnumBitField<BitFieldFor_uint16_t>(); + TestTypedEnumBitField<BitFieldFor_int16_t>(); + TestTypedEnumBitField<BitFieldFor_uint32_t>(); + TestTypedEnumBitField<BitFieldFor_int32_t>(); + TestTypedEnumBitField<BitFieldFor_uint64_t>(); + TestTypedEnumBitField<BitFieldFor_int64_t>(); + TestTypedEnumBitField<BitFieldFor_char>(); + TestTypedEnumBitField<BitFieldFor_signed_char>(); + TestTypedEnumBitField<BitFieldFor_unsigned_char>(); + TestTypedEnumBitField<BitFieldFor_short>(); + TestTypedEnumBitField<BitFieldFor_unsigned_short>(); + TestTypedEnumBitField<BitFieldFor_int>(); + TestTypedEnumBitField<BitFieldFor_unsigned_int>(); + TestTypedEnumBitField<BitFieldFor_long>(); + TestTypedEnumBitField<BitFieldFor_unsigned_long>(); + TestTypedEnumBitField<BitFieldFor_long_long>(); + TestTypedEnumBitField<BitFieldFor_unsigned_long_long>(); + + TestNoConversionsBetweenUnrelatedTypes(); + + TestIsNotTruncated<Int8EnumWithHighBits, int8_t>(); + TestIsNotTruncated<Int16EnumWithHighBits, int16_t>(); + TestIsNotTruncated<Int32EnumWithHighBits, int32_t>(); + TestIsNotTruncated<Int64EnumWithHighBits, int64_t>(); + TestIsNotTruncated<Uint8EnumWithHighBits, uint8_t>(); + TestIsNotTruncated<Uint16EnumWithHighBits, uint16_t>(); + TestIsNotTruncated<Uint32EnumWithHighBits, uint32_t>(); + TestIsNotTruncated<Uint64EnumWithHighBits, uint64_t>(); + + return 0; +} |