blob: 44a2a6933eac7fc1ee3f98585cf6669c8c8811b6 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "subtype_check_info.h"
#include "gtest/gtest.h"
#include "android-base/logging.h"
namespace art {
constexpr size_t BitString::kBitSizeAtPosition[BitString::kCapacity];
constexpr size_t BitString::kCapacity;
}; // namespace art
using namespace art; // NOLINT
// These helper functions are only used by the test,
// so they are not in the main BitString class.
std::string Stringify(BitString bit_string) {
std::stringstream ss;
ss << bit_string;
return ss.str();
}
BitStringChar MakeBitStringChar(size_t idx, size_t val) {
return BitStringChar(val, BitString::MaybeGetBitLengthAtPosition(idx));
}
BitStringChar MakeBitStringChar(size_t val) {
return BitStringChar(val, MinimumBitsToStore(val));
}
BitString MakeBitString(std::initializer_list<size_t> values = {}) {
CHECK_GE(BitString::kCapacity, values.size());
BitString bs{};
size_t i = 0;
for (size_t val : values) {
bs.SetAt(i, MakeBitStringChar(i, val));
++i;
}
return bs;
}
template <typename T>
size_t AsUint(const T& value) {
size_t uint_value = 0;
memcpy(&uint_value, &value, sizeof(value));
return uint_value;
}
// Make max bistring, e.g. BitString[4095,15,2047] for {12,4,11}
template <size_t kCount = BitString::kCapacity>
BitString MakeBitStringMax() {
BitString bs{};
for (size_t i = 0; i < kCount; ++i) {
bs.SetAt(i,
MakeBitStringChar(i, MaxInt<BitStringChar::StorageType>(BitString::kBitSizeAtPosition[i])));
}
return bs;
}
BitString SetBitStringCharAt(BitString bit_string, size_t i, size_t val) {
BitString bs = bit_string;
bs.SetAt(i, MakeBitStringChar(i, val));
return bs;
}
struct SubtypeCheckInfoTest : public ::testing::Test {
protected:
void SetUp() override {
android::base::InitLogging(/*argv=*/nullptr);
}
void TearDown() override {
}
static SubtypeCheckInfo MakeSubtypeCheckInfo(BitString path_to_root = {},
BitStringChar next = {},
bool overflow = false,
size_t depth = 1u) {
// Depth=1 is good default because it will go through all state transitions,
// and its children will also go through all state transitions.
return SubtypeCheckInfo(path_to_root, next, overflow, depth);
}
static SubtypeCheckInfo MakeSubtypeCheckInfoInfused(BitString bs = {},
bool overflow = false,
size_t depth = 1u) {
// Depth=1 is good default because it will go through all state transitions,
// and its children will also go through all state transitions.
SubtypeCheckBits iod;
iod.bitstring_ = bs;
iod.overflow_ = overflow;
return SubtypeCheckInfo::Create(iod, depth);
}
static SubtypeCheckInfo MakeSubtypeCheckInfoUnchecked(BitString bs = {},
bool overflow = false,
size_t depth = 1u) {
// Depth=1 is good default because it will go through all state transitions,
// and its children will also go through all state transitions.
return SubtypeCheckInfo::MakeUnchecked(bs, overflow, depth);
}
static bool HasNext(const SubtypeCheckInfo& io) {
return io.HasNext();
}
static BitString GetPathToRoot(const SubtypeCheckInfo& io) {
return io.GetPathToRoot();
}
// Create an SubtypeCheckInfo with the same depth, but with everything else reset.
// Returns: SubtypeCheckInfo in the Uninitialized state.
static SubtypeCheckInfo CopyCleared(const SubtypeCheckInfo& sc) {
SubtypeCheckInfo cleared_copy{};
cleared_copy.depth_ = sc.depth_;
DCHECK_EQ(SubtypeCheckInfo::kUninitialized, cleared_copy.GetState());
return cleared_copy;
}
};
const char* GetExpectedMessageForDeathTest(const char* msg) {
#ifdef ART_TARGET_ANDROID
// On Android, dcheck failure messages go to logcat,
// which gtest death tests does not check, and thus the tests would fail with
// "unexpected message ''"
UNUSED(msg);
return ""; // Still ensures there was a bad return code, but match anything.
#else
return msg;
#endif
}
TEST_F(SubtypeCheckInfoTest, IllegalValues) {
// This test relies on BitString being at least 3 large.
// It will need to be updated otherwise.
ASSERT_LE(3u, BitString::kCapacity);
// Illegal values during construction would cause a Dcheck failure and crash.
ASSERT_DEATH(MakeSubtypeCheckInfo(MakeBitString({1u}),
/*next=*/MakeBitStringChar(0),
/*overflow=*/false,
/*depth=*/0u),
GetExpectedMessageForDeathTest("Path was too long for the depth"));
ASSERT_DEATH(MakeSubtypeCheckInfoInfused(MakeBitString({1u, 1u}),
/*overflow=*/false,
/*depth=*/0u),
GetExpectedMessageForDeathTest("Bitstring too long for depth"));
ASSERT_DEATH(MakeSubtypeCheckInfo(MakeBitString({1u}),
/*next=*/MakeBitStringChar(0),
/*overflow=*/false,
/*depth=*/1u),
GetExpectedMessageForDeathTest("Expected \\(Assigned\\|Initialized\\) "
"state to have >0 Next value"));
ASSERT_DEATH(MakeSubtypeCheckInfoInfused(MakeBitString({0u, 2u, 1u}),
/*overflow=*/false,
/*depth=*/2u),
GetExpectedMessageForDeathTest("Path to root had non-0s following 0s"));
ASSERT_DEATH(MakeSubtypeCheckInfo(MakeBitString({0u, 2u}),
/*next=*/MakeBitStringChar(1u),
/*overflow=*/false,
/*depth=*/2u),
GetExpectedMessageForDeathTest("Path to root had non-0s following 0s"));
ASSERT_DEATH(MakeSubtypeCheckInfo(MakeBitString({0u, 1u, 1u}),
/*next=*/MakeBitStringChar(0),
/*overflow=*/false,
/*depth=*/3u),
GetExpectedMessageForDeathTest("Path to root had non-0s following 0s"));
// These are really slow (~1sec per death test on host),
// keep them down to a minimum.
}
TEST_F(SubtypeCheckInfoTest, States) {
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, MakeSubtypeCheckInfo().GetState());
EXPECT_EQ(SubtypeCheckInfo::kInitialized,
MakeSubtypeCheckInfo(/*path_to_root=*/{}, /*next=*/MakeBitStringChar(1)).GetState());
EXPECT_EQ(SubtypeCheckInfo::kOverflowed,
MakeSubtypeCheckInfo(/*path_to_root=*/{},
/*next=*/MakeBitStringChar(1),
/*overflow=*/true,
/*depth=*/1u).GetState());
EXPECT_EQ(SubtypeCheckInfo::kAssigned,
MakeSubtypeCheckInfo(/*path_to_root=*/MakeBitString({1u}),
/*next=*/MakeBitStringChar(1),
/*overflow=*/false,
/*depth=*/1u).GetState());
// Test edge conditions: depth == BitString::kCapacity (No Next value).
EXPECT_EQ(SubtypeCheckInfo::kAssigned,
MakeSubtypeCheckInfo(/*path_to_root=*/MakeBitStringMax(),
/*next=*/MakeBitStringChar(0),
/*overflow=*/false,
/*depth=*/BitString::kCapacity).GetState());
EXPECT_EQ(SubtypeCheckInfo::kInitialized,
MakeSubtypeCheckInfo(/*path_to_root=*/MakeBitStringMax<BitString::kCapacity - 1u>(),
/*next=*/MakeBitStringChar(0),
/*overflow=*/false,
/*depth=*/BitString::kCapacity).GetState());
// Test edge conditions: depth > BitString::kCapacity (Must overflow).
EXPECT_EQ(SubtypeCheckInfo::kOverflowed,
MakeSubtypeCheckInfo(/*path_to_root=*/MakeBitStringMax(),
/*next=*/MakeBitStringChar(0),
/*overflow=*/true,
/*depth=*/BitString::kCapacity + 1u).GetState());
}
TEST_F(SubtypeCheckInfoTest, NextValue) {
// Validate "Next" is correctly aliased as the Bitstring[Depth] character.
EXPECT_EQ(MakeBitStringChar(1u), MakeSubtypeCheckInfoUnchecked(MakeBitString({1u, 2u, 3u}),
/*overflow=*/false,
/*depth=*/0u).GetNext());
EXPECT_EQ(MakeBitStringChar(2u), MakeSubtypeCheckInfoUnchecked(MakeBitString({1u, 2u, 3u}),
/*overflow=*/false,
/*depth=*/1u).GetNext());
EXPECT_EQ(MakeBitStringChar(3u), MakeSubtypeCheckInfoUnchecked(MakeBitString({1u, 2u, 3u}),
/*overflow=*/false,
/*depth=*/2u).GetNext());
EXPECT_EQ(MakeBitStringChar(1u), MakeSubtypeCheckInfoUnchecked(MakeBitString({0u, 2u, 1u}),
/*overflow=*/false,
/*depth=*/2u).GetNext());
// Test edge conditions: depth == BitString::kCapacity (No Next value).
EXPECT_FALSE(HasNext(MakeSubtypeCheckInfoUnchecked(MakeBitStringMax<BitString::kCapacity>(),
/*overflow=*/false,
/*depth=*/BitString::kCapacity)));
// Anything with depth >= BitString::kCapacity has no next value.
EXPECT_FALSE(HasNext(MakeSubtypeCheckInfoUnchecked(MakeBitStringMax<BitString::kCapacity>(),
/*overflow=*/false,
/*depth=*/BitString::kCapacity + 1u)));
EXPECT_FALSE(HasNext(MakeSubtypeCheckInfoUnchecked(MakeBitStringMax(),
/*overflow=*/false,
/*depth=*/std::numeric_limits<size_t>::max())));
}
template <size_t kPos = BitString::kCapacity>
size_t LenForPos() { return BitString::GetBitLengthTotalAtPosition(kPos); }
TEST_F(SubtypeCheckInfoTest, EncodedPathToRoot) {
using StorageType = BitString::StorageType;
SubtypeCheckInfo sci =
MakeSubtypeCheckInfo(/*path_to_root=*/MakeBitStringMax(),
/*next=*/BitStringChar{},
/*overflow=*/false,
/*depth=*/BitString::kCapacity);
// 0b000...111 where LSB == 1, and trailing 1s = the maximum bitstring representation.
EXPECT_EQ(MaxInt<StorageType>(LenForPos()), sci.GetEncodedPathToRoot());
// The rest of this test is written assuming kCapacity == 3 for convenience.
// Please update the test if this changes.
ASSERT_EQ(3u, BitString::kCapacity);
ASSERT_EQ(12u, BitString::kBitSizeAtPosition[0]);
ASSERT_EQ(4u, BitString::kBitSizeAtPosition[1]);
ASSERT_EQ(11u, BitString::kBitSizeAtPosition[2]);
SubtypeCheckInfo sci2 =
MakeSubtypeCheckInfoUnchecked(MakeBitStringMax<2u>(),
/*overflow=*/false,
/*depth=*/BitString::kCapacity);
#define MAKE_ENCODED_PATH(pos0, pos1, pos2) \
(((pos0) << 0) | \
((pos1) << BitString::kBitSizeAtPosition[0]) | \
((pos2) << (BitString::kBitSizeAtPosition[0] + BitString::kBitSizeAtPosition[1])))
EXPECT_EQ(MAKE_ENCODED_PATH(MaxInt<BitString::StorageType>(12), 0b1111, 0b0),
sci2.GetEncodedPathToRoot());
EXPECT_EQ(MAKE_ENCODED_PATH(MaxInt<BitString::StorageType>(12), 0b1111, 0b11111111111),
sci2.GetEncodedPathToRootMask());
SubtypeCheckInfo sci3 =
MakeSubtypeCheckInfoUnchecked(MakeBitStringMax<2u>(),
/*overflow=*/false,
/*depth=*/BitString::kCapacity - 1u);
EXPECT_EQ(MAKE_ENCODED_PATH(MaxInt<BitString::StorageType>(12), 0b1111, 0b0),
sci3.GetEncodedPathToRoot());
EXPECT_EQ(MAKE_ENCODED_PATH(MaxInt<BitString::StorageType>(12), 0b1111, 0b0),
sci3.GetEncodedPathToRootMask());
SubtypeCheckInfo sci4 =
MakeSubtypeCheckInfoUnchecked(MakeBitString({0b1010101u}),
/*overflow=*/false,
/*depth=*/BitString::kCapacity - 2u);
EXPECT_EQ(MAKE_ENCODED_PATH(0b1010101u, 0b0000, 0b0), sci4.GetEncodedPathToRoot());
EXPECT_EQ(MAKE_ENCODED_PATH(MaxInt<BitString::StorageType>(12), 0b0000, 0b0),
sci4.GetEncodedPathToRootMask());
}
TEST_F(SubtypeCheckInfoTest, NewForRoot) {
SubtypeCheckInfo sci = SubtypeCheckInfo::CreateRoot();
EXPECT_EQ(SubtypeCheckInfo::kAssigned, sci.GetState()); // Root is always assigned.
EXPECT_EQ(0u, GetPathToRoot(sci).Length()); // Root's path length is 0.
EXPECT_TRUE(HasNext(sci)); // Root always has a "Next".
EXPECT_EQ(MakeBitStringChar(1u), sci.GetNext()); // Next>=1 to disambiguate from Uninitialized.
}
TEST_F(SubtypeCheckInfoTest, CopyCleared) {
SubtypeCheckInfo root = SubtypeCheckInfo::CreateRoot();
EXPECT_EQ(MakeBitStringChar(1u), root.GetNext());
SubtypeCheckInfo childC = root.CreateChild(/*assign_next=*/true);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, childC.GetState());
EXPECT_EQ(MakeBitStringChar(2u), root.GetNext()); // Next incremented for Assign.
EXPECT_EQ(MakeBitString({1u}), GetPathToRoot(childC));
SubtypeCheckInfo cleared_copy = CopyCleared(childC);
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, cleared_copy.GetState());
EXPECT_EQ(MakeBitString({}), GetPathToRoot(cleared_copy));
// CopyCleared is just a thin wrapper around value-init and providing the depth.
SubtypeCheckInfo cleared_copy_value =
SubtypeCheckInfo::Create(SubtypeCheckBits{}, /*depth=*/1u);
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, cleared_copy_value.GetState());
EXPECT_EQ(MakeBitString({}), GetPathToRoot(cleared_copy_value));
}
TEST_F(SubtypeCheckInfoTest, NewForChild2) {
SubtypeCheckInfo root = SubtypeCheckInfo::CreateRoot();
EXPECT_EQ(MakeBitStringChar(1u), root.GetNext());
SubtypeCheckInfo childC = root.CreateChild(/*assign_next=*/true);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, childC.GetState());
EXPECT_EQ(MakeBitStringChar(2u), root.GetNext()); // Next incremented for Assign.
EXPECT_EQ(MakeBitString({1u}), GetPathToRoot(childC));
}
TEST_F(SubtypeCheckInfoTest, NewForChild) {
SubtypeCheckInfo root = SubtypeCheckInfo::CreateRoot();
EXPECT_EQ(MakeBitStringChar(1u), root.GetNext());
SubtypeCheckInfo childA = root.CreateChild(/*assign_next=*/false);
EXPECT_EQ(SubtypeCheckInfo::kInitialized, childA.GetState());
EXPECT_EQ(MakeBitStringChar(1u), root.GetNext()); // Next unchanged for Initialize.
EXPECT_EQ(MakeBitString({}), GetPathToRoot(childA));
SubtypeCheckInfo childB = root.CreateChild(/*assign_next=*/false);
EXPECT_EQ(SubtypeCheckInfo::kInitialized, childB.GetState());
EXPECT_EQ(MakeBitStringChar(1u), root.GetNext()); // Next unchanged for Initialize.
EXPECT_EQ(MakeBitString({}), GetPathToRoot(childB));
SubtypeCheckInfo childC = root.CreateChild(/*assign_next=*/true);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, childC.GetState());
EXPECT_EQ(MakeBitStringChar(2u), root.GetNext()); // Next incremented for Assign.
EXPECT_EQ(MakeBitString({1u}), GetPathToRoot(childC));
{
size_t cur_depth = 1u;
SubtypeCheckInfo latest_child = childC;
while (cur_depth != BitString::kCapacity) {
latest_child = latest_child.CreateChild(/*assign_next=*/true);
ASSERT_EQ(SubtypeCheckInfo::kAssigned, latest_child.GetState());
ASSERT_EQ(cur_depth + 1u, GetPathToRoot(latest_child).Length());
cur_depth++;
}
// Future assignments will result in a too-deep overflow.
SubtypeCheckInfo child_of_deep = latest_child.CreateChild(/*assign_next=*/true);
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, child_of_deep.GetState());
EXPECT_EQ(GetPathToRoot(latest_child), GetPathToRoot(child_of_deep));
// Assignment of too-deep overflow also causes overflow.
SubtypeCheckInfo child_of_deep_2 = child_of_deep.CreateChild(/*assign_next=*/true);
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, child_of_deep_2.GetState());
EXPECT_EQ(GetPathToRoot(child_of_deep), GetPathToRoot(child_of_deep_2));
}
{
size_t cur_next = 2u;
while (true) {
if (cur_next == MaxInt<BitString::StorageType>(BitString::kBitSizeAtPosition[0u])) {
break;
}
SubtypeCheckInfo child = root.CreateChild(/*assign_next=*/true);
ASSERT_EQ(SubtypeCheckInfo::kAssigned, child.GetState());
ASSERT_EQ(MakeBitStringChar(cur_next+1u), root.GetNext());
ASSERT_EQ(MakeBitString({cur_next}), GetPathToRoot(child));
cur_next++;
}
// Now the root will be in a state that further assigns will be too-wide overflow.
// Initialization still succeeds.
SubtypeCheckInfo child = root.CreateChild(/*assign_next=*/false);
EXPECT_EQ(SubtypeCheckInfo::kInitialized, child.GetState());
EXPECT_EQ(MakeBitStringChar(cur_next), root.GetNext());
EXPECT_EQ(MakeBitString({}), GetPathToRoot(child));
// Assignment goes to too-wide Overflow.
SubtypeCheckInfo child_of = root.CreateChild(/*assign_next=*/true);
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, child_of.GetState());
EXPECT_EQ(MakeBitStringChar(cur_next), root.GetNext());
EXPECT_EQ(MakeBitString({}), GetPathToRoot(child_of));
// Assignment of overflowed child still succeeds.
// The path to root is the same.
SubtypeCheckInfo child_of2 = child_of.CreateChild(/*assign_next=*/true);
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, child_of2.GetState());
EXPECT_EQ(GetPathToRoot(child_of), GetPathToRoot(child_of2));
}
}