// Copyright 2015 the V8 project 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 #include "src/base/logging.h" #include "src/objects/objects.h" #include "src/objects/smi.h" #include "testing/gtest-support.h" namespace v8 { namespace base { namespace logging_unittest { namespace { #define CHECK_SUCCEED(NAME, lhs, rhs) \ { \ std::string* error_message = \ Check##NAME##Impl((lhs), (rhs), ""); \ EXPECT_EQ(nullptr, error_message); \ } #define CHECK_FAIL(NAME, lhs, rhs) \ { \ std::string* error_message = \ Check##NAME##Impl((lhs), (rhs), ""); \ EXPECT_NE(nullptr, error_message); \ delete error_message; \ } } // namespace TEST(LoggingTest, CheckEQImpl) { CHECK_SUCCEED(EQ, 0.0, 0.0); CHECK_SUCCEED(EQ, 0.0, -0.0); CHECK_SUCCEED(EQ, -0.0, 0.0); CHECK_SUCCEED(EQ, -0.0, -0.0); } TEST(LoggingTest, CompareSignedMismatch) { CHECK_SUCCEED(EQ, static_cast(14), static_cast(14)); CHECK_FAIL(EQ, static_cast(14), static_cast(15)); CHECK_FAIL(EQ, static_cast(-1), static_cast(-1)); CHECK_SUCCEED(LT, static_cast(-1), static_cast(0)); CHECK_SUCCEED(LT, static_cast(-1), static_cast(-1)); CHECK_SUCCEED(LE, static_cast(-1), static_cast(0)); CHECK_SUCCEED(LE, static_cast(55), static_cast(55)); CHECK_SUCCEED(LT, static_cast(55), static_cast(0x7FFFFF00)); CHECK_SUCCEED(LE, static_cast(55), static_cast(0x7FFFFF00)); CHECK_SUCCEED(GE, static_cast(0x7FFFFF00), static_cast(55)); CHECK_SUCCEED(GT, static_cast(0x7FFFFF00), static_cast(55)); CHECK_SUCCEED(GT, static_cast(-1), static_cast(-1)); CHECK_SUCCEED(GE, static_cast(0), static_cast(-1)); CHECK_SUCCEED(LT, static_cast(-1), static_cast(0)); CHECK_SUCCEED(GT, static_cast(0x7F01010101010101), 0); CHECK_SUCCEED(LE, static_cast(0xFF01010101010101), static_cast(13)); } TEST(LoggingTest, CompareAgainstStaticConstPointer) { // These used to produce link errors before http://crrev.com/2524093002. CHECK_FAIL(EQ, v8::internal::Smi::zero(), v8::internal::Smi::FromInt(17)); CHECK_SUCCEED(GT, 0, v8::internal::Smi::kMinValue); } #define CHECK_BOTH(name, lhs, rhs) \ CHECK_##name(lhs, rhs); \ DCHECK_##name(lhs, rhs) namespace { std::string SanitizeRegexp(std::string msg) { size_t last_pos = 0; do { size_t pos = msg.find_first_of("(){}+*", last_pos); if (pos == std::string::npos) break; msg.insert(pos, "\\"); last_pos = pos + 2; } while (true); return msg; } std::string FailureMessage(const char* msg, const char* lhs, const char* rhs) { std::string full_msg(msg); #ifdef DEBUG full_msg.append(" (").append(lhs).append(" vs. ").append(rhs); #endif return SanitizeRegexp(std::move(full_msg)); } std::string LongFailureMessage(const char* msg, const char* lhs, const char* rhs) { std::string full_msg(msg); #ifdef DEBUG full_msg.append("\n ").append(lhs).append("\n vs.\n ").append(rhs); #endif return SanitizeRegexp(std::move(full_msg)); } } // namespace TEST(LoggingTest, CompareWithDifferentSignedness) { int32_t i32 = 10; uint32_t u32 = 20; int64_t i64 = 30; uint64_t u64 = 40; // All these checks should compile (!) and succeed. CHECK_BOTH(EQ, i32 + 10, u32); CHECK_BOTH(LT, i32, u64); CHECK_BOTH(LE, u32, i64); 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", "40")); } TEST(LoggingTest, CompareWithReferenceType) { int32_t i32 = 10; uint32_t u32 = 20; int64_t i64 = 30; uint64_t u64 = 40; // All these checks should compile (!) and succeed. CHECK_BOTH(EQ, i32 + 10, *&u32); 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", "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{}", "", "")); ASSERT_DEATH_IF_SUPPORTED( ([&] { CHECK_LT(TestClass2{4}, TestClass2{3}); })(), FailureMessage("Check failed: TestClass2{4} < TestClass2{3}", "TestClass2(4)", "TestClass2(3)")); } TEST(LoggingDeathTest, OutputEnumValues) { ASSERT_DEATH_IF_SUPPORTED( ([&] { CHECK_EQ(ONE, TWO); })(), FailureMessage("Check failed: ONE == TWO", "0", "1")); ASSERT_DEATH_IF_SUPPORTED( ([&] { CHECK_NE(BAR, 2 + 3); })(), FailureMessage("Check failed: BAR != 2 + 3", "5", "5")); ASSERT_DEATH_IF_SUPPORTED( ([&] { CHECK_EQ(TestEnum3::A, TestEnum3::B); })(), FailureMessage("Check failed: TestEnum3::A == TestEnum3::B", "0", "1")); ASSERT_DEATH_IF_SUPPORTED( ([&] { CHECK_GE(TestEnum4::FIRST, TestEnum4::SECOND); })(), FailureMessage("Check failed: TestEnum4::FIRST >= TestEnum4::SECOND", "0", "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", "B")); ASSERT_DEATH_IF_SUPPORTED( ([&] { CHECK_GE(TestEnum6::TEST_C, TestEnum6::TEST_D); })(), FailureMessage("Check failed: TestEnum6::TEST_C >= TestEnum6::TEST_D", "C", "D")); } TEST(LoggingDeathTest, OutputLongValues) { constexpr size_t kMaxInlineLength = 50; // see logging.h std::string str1; while (str1.length() < kMaxInlineLength) { str1.push_back('a' + (str1.length() % 26)); } std::string str2("abc"); ASSERT_DEATH_IF_SUPPORTED( ([&] { CHECK_EQ(str1, str2); })(), FailureMessage("Check failed: str1 == str2", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx", "abc")); str1.push_back('X'); ASSERT_DEATH_IF_SUPPORTED( ([&] { CHECK_EQ(str1, str2); })(), LongFailureMessage("Check failed: str1 == str2", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxX", "abc")); } TEST(LoggingDeathTest, FatalKills) { ASSERT_DEATH_IF_SUPPORTED(FATAL("Dread pirate"), "Dread pirate"); } TEST(LoggingDeathTest, DcheckIsOnlyFatalInDebug) { #ifdef DEBUG ASSERT_DEATH_IF_SUPPORTED(DCHECK(false && "Dread pirate"), "Dread pirate"); #else // DCHECK should be non-fatal if DEBUG is undefined. DCHECK(false && "I'm a benign teapot"); #endif } namespace { void DcheckOverrideFunction(const char*, int, const char*) {} } // namespace TEST(LoggingDeathTest, V8_DcheckCanBeOverridden) { // Default DCHECK state should be fatal. ASSERT_DEATH_IF_SUPPORTED(V8_Dcheck(__FILE__, __LINE__, "Dread pirate"), "Dread pirate"); ASSERT_DEATH_IF_SUPPORTED( { v8::base::SetDcheckFunction(&DcheckOverrideFunction); // This should be non-fatal. V8_Dcheck(__FILE__, __LINE__, "I'm a benign teapot."); // Restore default behavior, and assert on lethality. v8::base::SetDcheckFunction(nullptr); V8_Dcheck(__FILE__, __LINE__, "Dread pirate"); }, "Dread pirate"); } #if defined(DEBUG) namespace { int g_log_sink_call_count = 0; void DcheckCountFunction(const char* file, int line, const char* message) { ++g_log_sink_call_count; } void DcheckEmptyFunction1() { // Provide a body so that Release builds do not cause the compiler to // optimize DcheckEmptyFunction1 and DcheckEmptyFunction2 as a single // function, which breaks the Dcheck tests below. // Note that this function is never actually called. g_log_sink_call_count += 42; } void DcheckEmptyFunction2() {} } // namespace TEST(LoggingTest, LogFunctionPointers) { v8::base::SetDcheckFunction(&DcheckCountFunction); g_log_sink_call_count = 0; void (*fp1)() = DcheckEmptyFunction1; void (*fp2)() = DcheckEmptyFunction2; void (*fp3)() = DcheckEmptyFunction1; DCHECK_EQ(fp1, DcheckEmptyFunction1); DCHECK_EQ(fp1, fp3); EXPECT_EQ(0, g_log_sink_call_count); DCHECK_EQ(fp1, fp2); EXPECT_EQ(1, g_log_sink_call_count); std::string* error_message = CheckEQImpl(fp1, fp2, ""); EXPECT_NE(*error_message, "(1 vs 1)"); delete error_message; } #endif // defined(DEBUG) } // namespace logging_unittest } // namespace base } // namespace v8