/* 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/TypedEnum.h" #include "mozilla/TypedEnumBits.h" #include // 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() # define MOZ_HAVE_IS_LITERAL # endif # elif defined(__GNUC__) # if defined(__GXX_EXPERIMENTAL_CXX0X__) # if MOZ_GCC_VERSION_AT_LEAST(4, 6, 0) # define MOZ_HAVE_IS_LITERAL # endif # endif # elif defined(_MSC_VER) # if _MSC_VER >= 1700 # define MOZ_HAVE_IS_LITERAL # endif # endif #endif #ifdef MOZ_HAVE_IS_LITERAL #include template void RequireLiteralType() { static_assert(std::is_literal_type::value, "Expected a literal type"); } #else // not MOZ_HAVE_IS_LITERAL template void RequireLiteralType() { } #endif template void RequireLiteralType(const T&) { RequireLiteralType(); } MOZ_BEGIN_ENUM_CLASS(AutoEnum) A, B = -3, C MOZ_END_ENUM_CLASS(AutoEnum) MOZ_BEGIN_ENUM_CLASS(CharEnum, char) A, B = 3, C MOZ_END_ENUM_CLASS(CharEnum) MOZ_BEGIN_ENUM_CLASS(AutoEnumBitField) A = 0x10, B = 0x20, C MOZ_END_ENUM_CLASS(AutoEnumBitField) MOZ_BEGIN_ENUM_CLASS(CharEnumBitField, char) A = 0x10, B, C = 0x40 MOZ_END_ENUM_CLASS(CharEnumBitField) struct Nested { MOZ_BEGIN_NESTED_ENUM_CLASS(AutoEnum) A, B, C = -1 MOZ_END_NESTED_ENUM_CLASS(AutoEnum) MOZ_BEGIN_NESTED_ENUM_CLASS(CharEnum, char) A = 4, B, C = 1 MOZ_END_NESTED_ENUM_CLASS(CharEnum) MOZ_BEGIN_NESTED_ENUM_CLASS(AutoEnumBitField) A, B = 0x20, C MOZ_END_NESTED_ENUM_CLASS(AutoEnumBitField) MOZ_BEGIN_NESTED_ENUM_CLASS(CharEnumBitField, char) A = 1, B = 1, C = 1 MOZ_END_NESTED_ENUM_CLASS(CharEnumBitField) }; 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) \ MOZ_BEGIN_ENUM_CLASS(BitFieldFor_##IntType, IntType) \ A = 1, \ B = 2, \ C = 4, \ MOZ_END_ENUM_CLASS(BitFieldFor_##IntType) \ 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 void TestNonConvertibilityForOneType() { using mozilla::IsConvertible; #ifdef MOZ_HAVE_CXX11_STRONG_ENUMS static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); #endif static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); } template void TestTypedEnumBasics() { const TypedEnum a = TypedEnum::A; int unused = int(a); (void) unused; RequireLiteralType(TypedEnum::A); RequireLiteralType(a); TestNonConvertibilityForOneType(); } // Op wraps a bitwise binary operator, passed as a char template parameter, // and applies it to its arguments (t1, t2). For example, // // Op<'|'>(t1, t2) // // is the same as // // t1 | t2. // template auto Op(const T1& t1, const T2& t2) -> decltype(t1 | t2) // 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::value, "binary ops should have the same result type"); static_assert(IsSame::value, "binary ops should have the same result type"); static_assert(o == '|' || o == '&' || o == '^', "unexpected operator character"); return o == '|' ? t1 | t2 : o == '&' ? t1 & t2 : t1 ^ t2; } // OpAssign wraps a bitwise binary operator, passed as a char template // parameter, and applies the corresponding compound-assignment operator to its // arguments (t1, t2). For example, // // OpAssign<'|'>(t1, t2) // // is the same as // // t1 |= t2. // template T1& OpAssign(T1& t1, const T2& t2) { static_assert(o == '|' || o == '&' || o == '^', "unexpected operator character"); switch (o) { case '|': return t1 |= t2; case '&': return t1 &= t2; case '^': return t1 ^= t2; default: MOZ_CRASH(); } } // Tests a single binary bitwise operator, using a single set of three operands. // The operations tested are: // // result = t1 Op t2; // result Op= t3; // // Where Op is the operator specified by the char template parameter 'o' and can // be any of '|', '&', '^'. // // Note that the operands t1, t2, t3 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 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 void TestBinOp(const T1& t1, const T2& t2, const T3& t3) { typedef typename mozilla::detail::UnsignedIntegerTypeForEnum::Type UnsignedIntegerType; // Part 1: // Test the bitwise binary operator i.e. // result = t1 Op t2; auto result = Op(t1, t2); typedef decltype(result) ResultType; RequireLiteralType(); TestNonConvertibilityForOneType(); UnsignedIntegerType unsignedIntegerResult = Op(UnsignedIntegerType(t1), UnsignedIntegerType(t2)); MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(result)); MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(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= t3; TypedEnum newResult = result; OpAssign(newResult, t3); UnsignedIntegerType unsignedIntegerNewResult = unsignedIntegerResult; OpAssign(unsignedIntegerNewResult, UnsignedIntegerType(t3)); MOZ_RELEASE_ASSERT(unsignedIntegerNewResult == UnsignedIntegerType(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 void TestTilde(const T& t) { typedef typename mozilla::detail::UnsignedIntegerTypeForEnum::Type UnsignedIntegerType; auto result = ~t; typedef decltype(result) ResultType; RequireLiteralType(); TestNonConvertibilityForOneType(); UnsignedIntegerType unsignedIntegerResult = ~(UnsignedIntegerType(t)); MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(result)); MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(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 void TestAllOpsForGivenOperands(const T1& t1, const T2& t2, const T3& t3) { TestBinOp(t1, t2, t3); TestBinOp(t1, t2, t3); TestBinOp(t1, t2, t3); TestTilde(t1); } // Helper building various triples of operands using a given operator, // and testing all operators with them. template 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(a_plain, b_plain); auto bc_plain = Op(b_plain, c_plain); auto ab_auto = Op(a_auto, b_auto); auto bc_auto = Op(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 (t1, t2, t3) and the actual tests performed // will be of the form // // result = t1 Op t2; // result Op= t3; // // For this reason, we carefully ensure that the values of (t1, t2) // systematically cover all types of such pairs; to limit complexity, // we are not so careful with t3, and we just try to pass t3's // that may lead to nontrivial bitwise operations. TestAllOpsForGivenOperands(a_plain, b_plain, c_plain); TestAllOpsForGivenOperands(a_plain, bc_plain, b_auto); TestAllOpsForGivenOperands(ab_plain, c_plain, a_plain); TestAllOpsForGivenOperands(ab_plain, bc_plain, a_auto); TestAllOpsForGivenOperands(a_plain, b_auto, c_plain); TestAllOpsForGivenOperands(a_plain, bc_auto, b_auto); TestAllOpsForGivenOperands(ab_plain, c_auto, a_plain); TestAllOpsForGivenOperands(ab_plain, bc_auto, a_auto); TestAllOpsForGivenOperands(a_auto, b_plain, c_plain); TestAllOpsForGivenOperands(a_auto, bc_plain, b_auto); TestAllOpsForGivenOperands(ab_auto, c_plain, a_plain); TestAllOpsForGivenOperands(ab_auto, bc_plain, a_auto); TestAllOpsForGivenOperands(a_auto, b_auto, c_plain); TestAllOpsForGivenOperands(a_auto, bc_auto, b_auto); TestAllOpsForGivenOperands(ab_auto, c_auto, a_plain); TestAllOpsForGivenOperands(ab_auto, bc_auto, a_auto); } // Tests all bitwise operations on a given TypedEnum bitfield. template void TestTypedEnumBitField() { TestTypedEnumBasics(); TestAllOpsForOperandsBuiltUsingGivenOp(); TestAllOpsForOperandsBuiltUsingGivenOp(); TestAllOpsForOperandsBuiltUsingGivenOp(); } // 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::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); // The following are #ifdef MOZ_HAVE_EXPLICIT_CONVERSION because // without support for explicit conversion operators, we can't easily have these // bad conversions completely removed. They still do fail to compile in practice, // but not in a way that we can static_assert on. #ifdef MOZ_HAVE_EXPLICIT_CONVERSION static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); static_assert(!IsConvertible::value, "should not be convertible"); #endif } MOZ_BEGIN_ENUM_CLASS(Int8EnumWithHighBits, int8_t) A = 0x20, B = 0x40 MOZ_END_ENUM_CLASS(Int8EnumWithHighBits) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int8EnumWithHighBits) MOZ_BEGIN_ENUM_CLASS(Uint8EnumWithHighBits, uint8_t) A = 0x40, B = 0x80 MOZ_END_ENUM_CLASS(Uint8EnumWithHighBits) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint8EnumWithHighBits) MOZ_BEGIN_ENUM_CLASS(Int16EnumWithHighBits, int16_t) A = 0x2000, B = 0x4000 MOZ_END_ENUM_CLASS(Int16EnumWithHighBits) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int16EnumWithHighBits) MOZ_BEGIN_ENUM_CLASS(Uint16EnumWithHighBits, uint16_t) A = 0x4000, B = 0x8000 MOZ_END_ENUM_CLASS(Uint16EnumWithHighBits) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint16EnumWithHighBits) MOZ_BEGIN_ENUM_CLASS(Int32EnumWithHighBits, int32_t) A = 0x20000000, B = 0x40000000 MOZ_END_ENUM_CLASS(Int32EnumWithHighBits) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int32EnumWithHighBits) MOZ_BEGIN_ENUM_CLASS(Uint32EnumWithHighBits, uint32_t) A = 0x40000000u, B = 0x80000000u MOZ_END_ENUM_CLASS(Uint32EnumWithHighBits) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint32EnumWithHighBits) MOZ_BEGIN_ENUM_CLASS(Int64EnumWithHighBits, int64_t) A = 0x2000000000000000ll, B = 0x4000000000000000ll MOZ_END_ENUM_CLASS(Int64EnumWithHighBits) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int64EnumWithHighBits) MOZ_BEGIN_ENUM_CLASS(Uint64EnumWithHighBits, uint64_t) A = 0x4000000000000000ull, B = 0x8000000000000000ull MOZ_END_ENUM_CLASS(Uint64EnumWithHighBits) 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 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(); TestTypedEnumBasics(); TestTypedEnumBasics(); TestTypedEnumBasics(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestTypedEnumBitField(); TestNoConversionsBetweenUnrelatedTypes(); TestIsNotTruncated(); TestIsNotTruncated(); TestIsNotTruncated(); TestIsNotTruncated(); TestIsNotTruncated(); TestIsNotTruncated(); TestIsNotTruncated(); TestIsNotTruncated(); return 0; }