[base] Allow comparing enums in (D)CHECKs
In the current implementation, compilation would fail because operator<< is not defined for enum classes. For others, the compiler finds more than one operator<<, so it fails because it's ambiguous. This CL fixes this by printing the integer value for enums, uses the operator<< for all values that support it, and prints "<unprintable>" otherwise. Also, lots of unit tests. R=ishell@chromium.org Bug: v8:6837 Change-Id: I895ed226672aa07213f9605e094b87af186ec2e4 Reviewed-on: https://chromium-review.googlesource.com/671016 Commit-Queue: Clemens Hammacher <clemensh@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Cr-Commit-Position: refs/heads/master@{#48110}
This commit is contained in:
parent
008d7b2ad2
commit
3a06391166
@ -114,9 +114,33 @@ V8_BASE_EXPORT void SetDcheckFunction(void (*dcheck_Function)(const char*, int,
|
||||
|
||||
#endif
|
||||
|
||||
template <typename Op>
|
||||
void PrintCheckOperand(std::ostream& os, Op op) {
|
||||
os << op;
|
||||
// Define PrintCheckOperand<T> for each T which defines operator<< for ostream.
|
||||
template <typename T>
|
||||
typename std::enable_if<has_output_operator<T>::value>::type PrintCheckOperand(
|
||||
std::ostream& os, T val) {
|
||||
os << std::forward<T>(val);
|
||||
}
|
||||
|
||||
// Define PrintCheckOperand<T> for enums which have no operator<<.
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_enum<T>::value &&
|
||||
!has_output_operator<T>::value>::type
|
||||
PrintCheckOperand(std::ostream& os, T val) {
|
||||
using underlying_t = typename std::underlying_type<T>::type;
|
||||
// 8-bit types are not printed as number, so extend them to 16 bit.
|
||||
using int_t = typename std::conditional<
|
||||
std::is_same<underlying_t, uint8_t>::value, uint16_t,
|
||||
typename std::conditional<std::is_same<underlying_t, int8_t>::value,
|
||||
int16_t, underlying_t>::type>::type;
|
||||
PrintCheckOperand(os, static_cast<int_t>(static_cast<underlying_t>(val)));
|
||||
}
|
||||
|
||||
// Define default PrintCheckOperand<T> for non-printable types.
|
||||
template <typename T>
|
||||
typename std::enable_if<!has_output_operator<T>::value &&
|
||||
!std::is_enum<T>::value>::type
|
||||
PrintCheckOperand(std::ostream& os, T val) {
|
||||
os << "<unprintable>";
|
||||
}
|
||||
|
||||
// Define specializations for character types, defined in logging.cc.
|
||||
@ -143,9 +167,9 @@ template <typename Lhs, typename Rhs>
|
||||
std::string* MakeCheckOpString(Lhs lhs, Rhs rhs, char const* msg) {
|
||||
std::ostringstream ss;
|
||||
ss << msg << " (";
|
||||
PrintCheckOperand(ss, lhs);
|
||||
PrintCheckOperand<Lhs>(ss, lhs);
|
||||
ss << " vs. ";
|
||||
PrintCheckOperand(ss, rhs);
|
||||
PrintCheckOperand<Rhs>(ss, rhs);
|
||||
ss << ")";
|
||||
return new std::string(ss.str());
|
||||
}
|
||||
|
@ -78,6 +78,21 @@ struct pass_value_or_ref {
|
||||
decay_t, const decay_t&>::type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct has_output_operator {
|
||||
// This template is only instantiable if U provides operator<< with ostream.
|
||||
// Its return type is uint8_t.
|
||||
template <typename U>
|
||||
static auto __check_operator(U u)
|
||||
-> decltype(*(std::ostream*)nullptr << *u, uint8_t{0});
|
||||
// This is a fallback implementation, returning uint16_t. If the template
|
||||
// above is instantiable, is has precedence over this varargs function.
|
||||
static uint16_t __check_operator(...);
|
||||
|
||||
using ptr_t = typename std::add_pointer<T>::type;
|
||||
static constexpr bool value = sizeof(__check_operator(ptr_t{nullptr})) == 1;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
} // namespace v8
|
||||
|
||||
|
@ -67,6 +67,23 @@ TEST(LoggingTest, CompareAgainstStaticConstPointer) {
|
||||
CHECK_##name(lhs, rhs); \
|
||||
DCHECK_##name(lhs, rhs)
|
||||
|
||||
namespace {
|
||||
std::string FailureMessage(const char* msg, const char* debug_msg) {
|
||||
std::string regexp(msg);
|
||||
#ifdef DEBUG
|
||||
regexp.append(" (").append(debug_msg).append(")");
|
||||
#endif
|
||||
size_t last_pos = 0;
|
||||
do {
|
||||
size_t pos = regexp.find_first_of("(){}+*", last_pos);
|
||||
if (pos == std::string::npos) break;
|
||||
regexp.insert(pos, "\\");
|
||||
last_pos = pos + 2;
|
||||
} while (true);
|
||||
return regexp;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(LoggingTest, CompareWithDifferentSignedness) {
|
||||
int32_t i32 = 10;
|
||||
uint32_t u32 = 20;
|
||||
@ -80,6 +97,11 @@ TEST(LoggingTest, CompareWithDifferentSignedness) {
|
||||
CHECK_BOTH(IMPLIES, i32, i64);
|
||||
CHECK_BOTH(IMPLIES, u32, i64);
|
||||
CHECK_BOTH(IMPLIES, !u32, !i64);
|
||||
|
||||
// Check that the values are output correctly on error.
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_GT(i32, u64); })(),
|
||||
FailureMessage("Check failed: i32 > u64", "10 vs. 40"));
|
||||
}
|
||||
|
||||
TEST(LoggingTest, CompareWithReferenceType) {
|
||||
@ -93,6 +115,97 @@ TEST(LoggingTest, CompareWithReferenceType) {
|
||||
CHECK_BOTH(LT, *&i32, u64);
|
||||
CHECK_BOTH(IMPLIES, *&i32, i64);
|
||||
CHECK_BOTH(IMPLIES, *&i32, u64);
|
||||
|
||||
// Check that the values are output correctly on error.
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_GT(*&i32, u64); })(),
|
||||
FailureMessage("Check failed: *&i32 > u64", "10 vs. 40"));
|
||||
}
|
||||
|
||||
enum TestEnum1 { ONE, TWO };
|
||||
enum TestEnum2 : uint16_t { FOO = 14, BAR = 5 };
|
||||
enum class TestEnum3 { A, B };
|
||||
enum class TestEnum4 : uint8_t { FIRST, SECOND };
|
||||
|
||||
TEST(LoggingTest, CompareEnumTypes) {
|
||||
// All these checks should compile (!) and succeed.
|
||||
CHECK_BOTH(EQ, ONE, ONE);
|
||||
CHECK_BOTH(LT, ONE, TWO);
|
||||
CHECK_BOTH(EQ, BAR, 5);
|
||||
CHECK_BOTH(LT, BAR, FOO);
|
||||
CHECK_BOTH(EQ, TestEnum3::A, TestEnum3::A);
|
||||
CHECK_BOTH(LT, TestEnum3::A, TestEnum3::B);
|
||||
CHECK_BOTH(EQ, TestEnum4::FIRST, TestEnum4::FIRST);
|
||||
CHECK_BOTH(LT, TestEnum4::FIRST, TestEnum4::SECOND);
|
||||
}
|
||||
|
||||
class TestClass1 {
|
||||
public:
|
||||
bool operator==(const TestClass1&) const { return true; }
|
||||
bool operator!=(const TestClass1&) const { return false; }
|
||||
};
|
||||
class TestClass2 {
|
||||
public:
|
||||
explicit TestClass2(int val) : val_(val) {}
|
||||
bool operator<(const TestClass2& other) const { return val_ < other.val_; }
|
||||
int val() const { return val_; }
|
||||
|
||||
private:
|
||||
int val_;
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& str, const TestClass2& val) {
|
||||
return str << "TestClass2(" << val.val() << ")";
|
||||
}
|
||||
|
||||
TEST(LoggingTest, CompareClassTypes) {
|
||||
// All these checks should compile (!) and succeed.
|
||||
CHECK_BOTH(EQ, TestClass1{}, TestClass1{});
|
||||
CHECK_BOTH(LT, TestClass2{2}, TestClass2{7});
|
||||
|
||||
// Check that the values are output correctly on error.
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_NE(TestClass1{}, TestClass1{}); })(),
|
||||
FailureMessage("Check failed: TestClass1{} != TestClass1{}",
|
||||
"<unprintable> vs. <unprintable>"));
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_LT(TestClass2{4}, TestClass2{3}); })(),
|
||||
FailureMessage("Check failed: TestClass2{4} < TestClass2{3}",
|
||||
"TestClass2(4) vs. TestClass2(3)"));
|
||||
}
|
||||
|
||||
TEST(LoggingDeathTest, OutputEnumValues) {
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_EQ(ONE, TWO); })(),
|
||||
FailureMessage("Check failed: ONE == TWO", "0 vs. 1"));
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_NE(BAR, 2 + 3); })(),
|
||||
FailureMessage("Check failed: BAR != 2 + 3", "5 vs. 5"));
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_EQ(TestEnum3::A, TestEnum3::B); })(),
|
||||
FailureMessage("Check failed: TestEnum3::A == TestEnum3::B", "0 vs. 1"));
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_GE(TestEnum4::FIRST, TestEnum4::SECOND); })(),
|
||||
FailureMessage("Check failed: TestEnum4::FIRST >= TestEnum4::SECOND",
|
||||
"0 vs. 1"));
|
||||
}
|
||||
|
||||
enum TestEnum5 { TEST_A, TEST_B };
|
||||
enum class TestEnum6 { TEST_C, TEST_D };
|
||||
std::ostream& operator<<(std::ostream& str, TestEnum5 val) {
|
||||
return str << (val == TEST_A ? "A" : "B");
|
||||
}
|
||||
void operator<<(std::ostream& str, TestEnum6 val) {
|
||||
str << (val == TestEnum6::TEST_C ? "C" : "D");
|
||||
}
|
||||
|
||||
TEST(LoggingDeathTest, OutputEnumWithOutputOperator) {
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_EQ(TEST_A, TEST_B); })(),
|
||||
FailureMessage("Check failed: TEST_A == TEST_B", "A vs. B"));
|
||||
ASSERT_DEATH_IF_SUPPORTED(
|
||||
([&] { CHECK_GE(TestEnum6::TEST_C, TestEnum6::TEST_D); })(),
|
||||
FailureMessage("Check failed: TestEnum6::TEST_C >= TestEnum6::TEST_D",
|
||||
"C vs. D"));
|
||||
}
|
||||
|
||||
TEST(LoggingDeathTest, FatalKills) {
|
||||
|
@ -80,5 +80,25 @@ TEST_PASS_VALUE_OR_REF0(false, const std::string&, const std::string&);
|
||||
TEST_PASS_VALUE_OR_REF0(false, int, const int);
|
||||
TEST_PASS_VALUE_OR_REF0(false, int, const int&);
|
||||
|
||||
//////////////////////////////
|
||||
// Test has_output_operator.
|
||||
//////////////////////////////
|
||||
|
||||
// Intrinsic types:
|
||||
static_assert(has_output_operator<int>::value, "int can be output");
|
||||
static_assert(has_output_operator<void*>::value, "void* can be output");
|
||||
static_assert(has_output_operator<uint64_t>::value, "int can be output");
|
||||
|
||||
// Classes:
|
||||
class TestClass1 {};
|
||||
class TestClass2 {};
|
||||
extern std::ostream& operator<<(std::ostream& str, TestClass2&);
|
||||
static_assert(!has_output_operator<TestClass1>::value,
|
||||
"TestClass1 can not be output");
|
||||
static_assert(has_output_operator<TestClass2>::value,
|
||||
"non-const TestClass2 can be output");
|
||||
static_assert(!has_output_operator<const TestClass2>::value,
|
||||
"const TestClass2 can not be output");
|
||||
|
||||
} // namespace base
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user