[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:
Clemens Hammacher 2017-09-21 14:58:36 +02:00 committed by Commit Bot
parent 008d7b2ad2
commit 3a06391166
4 changed files with 177 additions and 5 deletions

View File

@ -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());
}

View File

@ -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

View File

@ -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) {

View File

@ -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