blob: e297d0beb42304c3c43f82aa3b0fce7447989b90 [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.h"
#include "gtest/gtest.h"
#include "android-base/logging.h"
namespace art {
constexpr size_t BitString::kBitSizeAtPosition[BitString::kCapacity];
constexpr size_t BitString::kCapacity;
struct MockClass {
explicit MockClass(MockClass* parent, size_t x = 0, size_t y = 0) {
parent_ = parent;
memset(&subtype_check_info_and_status_, 0u, sizeof(subtype_check_info_and_status_));
// Start the numbering at '1' to match the bitstring numbering.
// A bitstring numbering never starts at '0' which just means 'no value'.
x_ = 1;
if (parent_ != nullptr) {
if (parent_->GetMaxChild() != nullptr) {
x_ = parent_->GetMaxChild()->x_ + 1u;
}
parent_->children_.push_back(this);
if (parent_->path_to_root_ != "") {
path_to_root_ = parent->path_to_root_ + ",";
}
path_to_root_ += std::to_string(x_);
} else {
path_to_root_ = ""; // root has no path.
}
y_ = y;
UNUSED(x);
}
MockClass() : MockClass(nullptr) {
}
///////////////////////////////////////////////////////////////
// Implementation of the SubtypeCheck::KlassT static interface.
///////////////////////////////////////////////////////////////
MockClass* GetSuperClass() const {
return parent_;
}
bool HasSuperClass() const {
return GetSuperClass() != nullptr;
}
size_t Depth() const {
if (parent_ == nullptr) {
return 0u;
} else {
return parent_->Depth() + 1u;
}
}
std::string PrettyClass() const
REQUIRES_SHARED(Locks::mutator_lock_) {
return path_to_root_;
}
int32_t GetField32Volatile(art::MemberOffset offset = art::MemberOffset(0u)) const
REQUIRES_SHARED(Locks::mutator_lock_) {
UNUSED(offset);
int32_t field_32 = 0;
memcpy(&field_32, &subtype_check_info_and_status_, sizeof(int32_t));
return field_32;
}
template <bool kTransactionActive>
bool CasFieldWeakSequentiallyConsistent32(art::MemberOffset offset,
int32_t old_value,
int32_t new_value)
REQUIRES_SHARED(Locks::mutator_lock_) {
UNUSED(offset);
if (old_value == GetField32Volatile(offset)) {
memcpy(&subtype_check_info_and_status_, &new_value, sizeof(int32_t));
return true;
}
return false;
}
MemberOffset StatusOffset() const {
return MemberOffset(0); // Doesn't matter. We ignore offset.
}
///////////////////////////////////////////////////////////////
// Convenience functions to make the testing easier
///////////////////////////////////////////////////////////////
size_t GetNumberOfChildren() const {
return children_.size();
}
MockClass* GetParent() const {
return parent_;
}
MockClass* GetMaxChild() const {
if (GetNumberOfChildren() > 0u) {
return GetChild(GetNumberOfChildren() - 1);
}
return nullptr;
}
MockClass* GetChild(size_t idx) const {
if (idx >= GetNumberOfChildren()) {
return nullptr;
}
return children_[idx];
}
// Traverse the sibling at "X" at each level.
// Once we get to level==depth, return yourself.
MockClass* FindChildAt(size_t x, size_t depth) {
if (Depth() == depth) {
return this;
} else if (GetNumberOfChildren() > 0) {
return GetChild(x)->FindChildAt(x, depth);
}
return nullptr;
}
template <typename T>
MockClass* Visit(T visitor, bool recursive = true) {
if (!visitor(this)) {
return this;
}
if (!recursive) {
return this;
}
for (MockClass* child : children_) {
MockClass* visit_res = child->Visit(visitor);
if (visit_res != nullptr) {
return visit_res;
}
}
return nullptr;
}
size_t GetX() const {
return x_;
}
bool SlowIsSubtypeOf(const MockClass* target) const {
DCHECK(target != nullptr);
const MockClass* kls = this;
while (kls != nullptr) {
if (kls == target) {
return true;
}
kls = kls->GetSuperClass();
}
return false;
}
std::string ToDotGraph() const {
std::stringstream ss;
ss << std::endl;
ss << "digraph MockClass {" << std::endl;
ss << " node [fontname=\"Arial\"];" << std::endl;
ToDotGraphImpl(ss);
ss << "}" << std::endl;
return ss.str();
}
void ToDotGraphImpl(std::ostream& os) const {
for (MockClass* child : children_) {
os << " '" << path_to_root_ << "' -> '" << child->path_to_root_ << "';" << std::endl;
child->ToDotGraphImpl(os);
}
}
std::vector<MockClass*> children_;
MockClass* parent_;
SubtypeCheckBitsAndStatus subtype_check_info_and_status_;
size_t x_;
size_t y_;
std::string path_to_root_;
};
std::ostream& operator<<(std::ostream& os, const MockClass& kls) {
SubtypeCheckBits iod = kls.subtype_check_info_and_status_.subtype_check_info_;
os << "MClass{D:" << kls.Depth() << ",W:" << kls.x_
<< ", OF:"
<< (iod.overflow_ ? "true" : "false")
<< ", bitstring: " << iod.bitstring_
<< ", mock_path: " << kls.path_to_root_
<< "}";
return os;
}
struct MockSubtypeCheck {
using SC = SubtypeCheck<MockClass*>;
static MockSubtypeCheck Lookup(MockClass* klass) {
MockSubtypeCheck mock;
mock.klass_ = klass;
return mock;
}
// Convenience functions to avoid using statics everywhere.
// static(class, args...) -> instance.method(args...)
SubtypeCheckInfo::State EnsureInitialized()
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return SC::EnsureInitialized(klass_);
}
SubtypeCheckInfo::State EnsureAssigned()
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return SC::EnsureAssigned(klass_);
}
SubtypeCheckInfo::State ForceUninitialize()
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return SC::ForceUninitialize(klass_);
}
BitString::StorageType GetEncodedPathToRootForSource() const
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return SC::GetEncodedPathToRootForSource(klass_);
}
BitString::StorageType GetEncodedPathToRootForTarget() const
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return SC::GetEncodedPathToRootForTarget(klass_);
}
BitString::StorageType GetEncodedPathToRootMask() const
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return SC::GetEncodedPathToRootMask(klass_);
}
SubtypeCheckInfo::Result IsSubtypeOf(const MockSubtypeCheck& target)
REQUIRES_SHARED(Locks::mutator_lock_) {
return SC::IsSubtypeOf(klass_, target.klass_);
}
friend std::ostream& operator<<(std::ostream& os, const MockSubtypeCheck& tree)
NO_THREAD_SAFETY_ANALYSIS {
os << "(MockSubtypeCheck io:";
SC::Dump(tree.klass_, os);
os << ", class: " << tree.klass_->PrettyClass() << ")";
return os;
}
// Additional convenience functions.
SubtypeCheckInfo::State GetState() const
REQUIRES(Locks::subtype_check_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
return SC::GetSubtypeCheckInfo(klass_).GetState();
}
MockClass& GetClass() const {
return *klass_;
}
private:
MockClass* klass_;
};
struct MockScopedLockSubtypeCheck {
MockScopedLockSubtypeCheck() ACQUIRE(*Locks::subtype_check_lock_) {}
~MockScopedLockSubtypeCheck() RELEASE(*Locks::subtype_check_lock_) {}
};
struct MockScopedLockMutator {
MockScopedLockMutator() ACQUIRE_SHARED(*Locks::mutator_lock_) {}
~MockScopedLockMutator() RELEASE_SHARED(*Locks::mutator_lock_) {}
};
struct SubtypeCheckTest : public ::testing::Test {
protected:
virtual void SetUp() {
android::base::InitLogging(/*argv*/nullptr);
CreateRootedTree(BitString::kCapacity + 2u, BitString::kCapacity + 2u);
}
virtual void TearDown() {
}
void CreateRootedTree(size_t width, size_t height) {
all_classes_.clear();
root_ = CreateClassFor(/*parent*/nullptr, /*x*/0, /*y*/0);
CreateTreeFor(root_, /*width*/width, /*depth*/height);
}
MockClass* CreateClassFor(MockClass* parent, size_t x, size_t y) {
MockClass* kls = new MockClass(parent, x, y);
all_classes_.push_back(std::unique_ptr<MockClass>(kls));
return kls;
}
void CreateTreeFor(MockClass* parent, size_t width, size_t levels) {
DCHECK(parent != nullptr);
if (levels == 0) {
return;
}
for (size_t i = 0; i < width; ++i) {
MockClass* child = CreateClassFor(parent, i, parent->y_ + 1);
CreateTreeFor(child, width, levels - 1);
}
}
MockClass* root_ = nullptr;
std::vector<std::unique_ptr<MockClass>> all_classes_;
};
TEST_F(SubtypeCheckTest, LookupAllChildren) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
root_->Visit([&](MockClass* kls) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, SCTree::Lookup(kls).GetState());
return true; // Keep visiting.
});
}
TEST_F(SubtypeCheckTest, LookupRoot) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
SCTree root = SCTree::Lookup(root_);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, root.EnsureInitialized());
EXPECT_EQ(SubtypeCheckInfo::kSubtypeOf, root.IsSubtypeOf(root)) << root;
}
TEST_F(SubtypeCheckTest, EnsureInitializedFirstLevel) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
SCTree root = SCTree::Lookup(root_);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, root.EnsureInitialized());
ASSERT_LT(0u, root_->GetNumberOfChildren());
// Initialize root's children only.
for (size_t i = 0; i < root_->GetNumberOfChildren(); ++i) {
MockClass* child = root_->GetChild(i);
SCTree child_tree = SCTree::Lookup(child);
// Before: all unknown.
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, root.IsSubtypeOf(child_tree)) << child_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child_tree.IsSubtypeOf(root)) << child_tree;
// Transition.
EXPECT_EQ(SubtypeCheckInfo::kInitialized, child_tree.EnsureInitialized());
// After: "src instanceof target" known, but "target instanceof src" unknown.
EXPECT_EQ(SubtypeCheckInfo::kSubtypeOf, child_tree.IsSubtypeOf(root)) << child_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, root.IsSubtypeOf(child_tree)) << child_tree;
}
}
TEST_F(SubtypeCheckTest, EnsureAssignedFirstLevel) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
SCTree root = SCTree::Lookup(root_);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, root.EnsureInitialized());
ASSERT_LT(0u, root_->GetNumberOfChildren());
// Initialize root's children only.
for (size_t i = 0; i < root_->GetNumberOfChildren(); ++i) {
MockClass* child = root_->GetChild(i);
SCTree child_tree = SCTree::Lookup(child);
// Before: all unknown.
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, root.IsSubtypeOf(child_tree)) << child_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child_tree.IsSubtypeOf(root)) << child_tree;
// Transition.
EXPECT_EQ(SubtypeCheckInfo::kAssigned, child_tree.EnsureAssigned());
// After: "src instanceof target" known, and "target instanceof src" known.
EXPECT_EQ(SubtypeCheckInfo::kSubtypeOf, child_tree.IsSubtypeOf(root)) << child_tree;
EXPECT_EQ(SubtypeCheckInfo::kNotSubtypeOf, root.IsSubtypeOf(child_tree)) << child_tree;
}
}
TEST_F(SubtypeCheckTest, EnsureInitializedSecondLevelWithPreassign) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
SCTree root = SCTree::Lookup(root_);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, root.EnsureInitialized());
ASSERT_LT(0u, root_->GetNumberOfChildren());
// Initialize root's children.
for (size_t i = 0; i < root_->GetNumberOfChildren(); ++i) {
MockClass* child = root_->GetChild(i);
SCTree child_tree = SCTree::Lookup(child);
ASSERT_EQ(1u, child->Depth());
EXPECT_EQ(SubtypeCheckInfo::kInitialized, child_tree.EnsureInitialized()) << *child;
EXPECT_EQ(SubtypeCheckInfo::kAssigned, child_tree.EnsureAssigned())
<< *child << ", root:" << *root_;
for (size_t j = 0; j < child->GetNumberOfChildren(); ++j) {
MockClass* child2 = child->GetChild(j);
ASSERT_EQ(2u, child2->Depth());
SCTree child2_tree = SCTree::Lookup(child2);
// Before: all unknown.
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, root.IsSubtypeOf(child2_tree)) << child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child_tree.IsSubtypeOf(child2_tree))
<< child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child2_tree.IsSubtypeOf(root))
<< child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child2_tree.IsSubtypeOf(child_tree))
<< child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, child2_tree.GetState()) << *child2;
EXPECT_EQ(SubtypeCheckInfo::kInitialized, child2_tree.EnsureInitialized()) << *child2;
// After: src=child2_tree is known, otherwise unknown.
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, root.IsSubtypeOf(child2_tree)) << child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child_tree.IsSubtypeOf(child2_tree))
<< child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kSubtypeOf, child2_tree.IsSubtypeOf(root)) << child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kSubtypeOf, child2_tree.IsSubtypeOf(child_tree)) << child2_tree;
}
// The child is "assigned" as a side-effect of initializing sub-children.
EXPECT_EQ(SubtypeCheckInfo::kAssigned, child_tree.GetState());
}
}
TEST_F(SubtypeCheckTest, EnsureInitializedSecondLevelDontPreassign) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
SCTree root = SCTree::Lookup(root_);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, root.EnsureInitialized());
ASSERT_LT(0u, root_->GetNumberOfChildren());
// Initialize root's children only.
for (size_t i = 0; i < root_->GetNumberOfChildren(); ++i) {
MockClass* child = root_->GetChild(i);
SCTree child_tree = SCTree::Lookup(child);
ASSERT_EQ(1u, child->Depth());
for (size_t j = 0; j < child->GetNumberOfChildren(); ++j) {
MockClass* child2 = child->GetChild(j);
ASSERT_EQ(2u, child2->Depth());
SCTree child2_tree = SCTree::Lookup(child2);
// Before: all unknown.
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, root.IsSubtypeOf(child2_tree)) << child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child_tree.IsSubtypeOf(child2_tree))
<< child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child2_tree.IsSubtypeOf(root)) << child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child2_tree.IsSubtypeOf(child_tree))
<< child2_tree;
// Transition.
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, child2_tree.GetState()) << *child2;
EXPECT_EQ(SubtypeCheckInfo::kInitialized, child2_tree.EnsureInitialized()) << *child2;
// After: src=child2_tree is known, otherwise unknown.
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, root.IsSubtypeOf(child2_tree)) << child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, child_tree.IsSubtypeOf(child2_tree))
<< child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kSubtypeOf, child2_tree.IsSubtypeOf(root)) << child2_tree;
EXPECT_EQ(SubtypeCheckInfo::kSubtypeOf, child2_tree.IsSubtypeOf(child_tree)) << child2_tree;
}
// The child is "assigned" as a side-effect of initializing sub-children.
EXPECT_EQ(SubtypeCheckInfo::kAssigned, child_tree.GetState());
}
}
void ApplyTransition(MockSubtypeCheck sc_tree,
SubtypeCheckInfo::State transition,
SubtypeCheckInfo::State expected) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, sc_tree.GetState()) << sc_tree.GetClass();
if (transition == SubtypeCheckInfo::kUninitialized) {
EXPECT_EQ(expected, sc_tree.ForceUninitialize()) << sc_tree.GetClass();
} else if (transition == SubtypeCheckInfo::kInitialized) {
EXPECT_EQ(expected, sc_tree.EnsureInitialized()) << sc_tree.GetClass();
} else if (transition == SubtypeCheckInfo::kAssigned) {
EXPECT_EQ(expected, sc_tree.EnsureAssigned()) << sc_tree.GetClass();
}
}
enum MockSubtypeOfTransition {
kNone,
kUninitialized,
kInitialized,
kAssigned,
};
std::ostream& operator<<(std::ostream& os, const MockSubtypeOfTransition& transition) {
if (transition == MockSubtypeOfTransition::kUninitialized) {
os << "kUninitialized";
} else if (transition == MockSubtypeOfTransition::kInitialized) {
os << "kInitialized";
} else if (transition == MockSubtypeOfTransition::kAssigned) {
os << "kAssigned";
} else {
os << "kNone";
}
return os;
}
SubtypeCheckInfo::State ApplyTransition(MockSubtypeCheck sc_tree,
MockSubtypeOfTransition transition) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
if (transition == MockSubtypeOfTransition::kUninitialized) {
return sc_tree.ForceUninitialize();
} else if (transition == MockSubtypeOfTransition::kInitialized) {
return sc_tree.EnsureInitialized();
} else if (transition == MockSubtypeOfTransition::kAssigned) {
return sc_tree.EnsureAssigned();
}
return sc_tree.GetState();
}
enum {
kBeforeTransition = 0,
kAfterTransition = 1,
kAfterChildren = 2,
};
const char* StringifyTransition(int x) {
if (x == kBeforeTransition) {
return "kBeforeTransition";
} else if (x == kAfterTransition) {
return "kAfterTransition";
} else if (x == kAfterChildren) {
return "kAfterChildren";
}
return "<<Unknown>>";
}
struct TransitionHistory {
void Record(int transition_label, MockClass* kls) {
ss_ << "<<<" << StringifyTransition(transition_label) << ">>>";
ss_ << "{Self}: " << *kls;
if (kls->HasSuperClass()) {
ss_ << "{Parent}: " << *(kls->GetSuperClass());
}
ss_ << "================== ";
}
friend std::ostream& operator<<(std::ostream& os, const TransitionHistory& t) {
os << t.ss_.str();
return os;
}
std::stringstream ss_;
};
template <typename T, typename T2>
void EnsureStateChangedTestRecursiveGeneric(MockClass* klass,
size_t cur_depth,
size_t total_depth,
T2 transition_func,
T expect_checks) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
SCTree sc_tree = SCTree::Lookup(klass);
MockSubtypeOfTransition requested_transition = transition_func(klass);
// FIXME: need to print before(self, parent) and after(self, parent)
// to make any sense of what's going on.
auto do_expect_checks = [&](int transition_label, TransitionHistory& transition_details) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
transition_details.Record(transition_label, klass);
SCOPED_TRACE(transition_details);
ASSERT_EQ(cur_depth, klass->Depth());
ASSERT_NO_FATAL_FAILURE(expect_checks(klass,
transition_label,
sc_tree.GetState(),
requested_transition));
};
TransitionHistory transition_history;
do_expect_checks(kBeforeTransition, transition_history);
SubtypeCheckInfo::State state = ApplyTransition(sc_tree, requested_transition);
UNUSED(state);
do_expect_checks(kAfterTransition, transition_history);
if (total_depth == cur_depth) {
return;
}
// Initialize root's children only.
for (size_t i = 0; i < klass->GetNumberOfChildren(); ++i) {
MockClass* child = klass->GetChild(i);
EnsureStateChangedTestRecursiveGeneric(child,
cur_depth + 1u,
total_depth,
transition_func,
expect_checks);
}
do_expect_checks(kAfterChildren, transition_history);
}
void EnsureStateChangedTestRecursive(
MockClass* klass,
size_t cur_depth,
size_t total_depth,
std::vector<std::pair<SubtypeCheckInfo::State, SubtypeCheckInfo::State>> transitions) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
ASSERT_EQ(cur_depth, klass->Depth());
ApplyTransition(SCTree::Lookup(klass), transitions[cur_depth].first, transitions[cur_depth].second);
if (total_depth == cur_depth + 1) {
return;
}
// Initialize root's children only.
for (size_t i = 0; i < klass->GetNumberOfChildren(); ++i) {
MockClass* child = klass->GetChild(i);
EnsureStateChangedTestRecursive(child, cur_depth + 1u, total_depth, transitions);
}
}
void EnsureStateChangedTest(
MockClass* root,
size_t depth,
std::vector<std::pair<SubtypeCheckInfo::State, SubtypeCheckInfo::State>> transitions) {
ASSERT_EQ(depth, transitions.size());
EnsureStateChangedTestRecursive(root, /*cur_depth*/0u, depth, transitions);
}
TEST_F(SubtypeCheckTest, EnsureInitialized_NoOverflow) {
auto transitions = [](MockClass* kls) {
UNUSED(kls);
return MockSubtypeOfTransition::kInitialized;
};
constexpr size_t kMaxDepthForThisTest = BitString::kCapacity;
auto expected = [=](MockClass* kls,
int expect_when,
SubtypeCheckInfo::State actual_state,
MockSubtypeOfTransition transition) {
if (expect_when == kBeforeTransition) {
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, actual_state);
return;
}
if (expect_when == kAfterTransition) {
// After explicit transition has been completed.
switch (kls->Depth()) {
case 0:
if (transition >= MockSubtypeOfTransition::kInitialized) {
EXPECT_EQ(SubtypeCheckInfo::kAssigned, actual_state);
}
break;
default:
if (transition >= MockSubtypeOfTransition::kInitialized) {
if (transition == MockSubtypeOfTransition::kInitialized) {
EXPECT_EQ(SubtypeCheckInfo::kInitialized, actual_state);
} else if (transition == MockSubtypeOfTransition::kAssigned) {
EXPECT_EQ(SubtypeCheckInfo::kAssigned, actual_state);
}
}
break;
}
}
if (expect_when == kAfterChildren) {
if (transition >= MockSubtypeOfTransition::kInitialized) {
ASSERT_NE(kls->Depth(), kMaxDepthForThisTest);
EXPECT_EQ(SubtypeCheckInfo::kAssigned, actual_state);
}
}
};
// Initialize every level 0-3.
// Intermediate levels become "assigned", max levels become initialized.
EnsureStateChangedTestRecursiveGeneric(root_, 0u, kMaxDepthForThisTest, transitions, expected);
auto transitions_uninitialize = [](MockClass* kls) {
UNUSED(kls);
return MockSubtypeOfTransition::kUninitialized;
};
auto expected_uninitialize = [](MockClass* kls,
int expect_when,
SubtypeCheckInfo::State actual_state,
MockSubtypeOfTransition transition) {
UNUSED(kls);
UNUSED(transition);
if (expect_when >= kAfterTransition) {
EXPECT_EQ(SubtypeCheckInfo::kUninitialized, actual_state);
}
};
// Uninitialize the entire tree after it was assigned.
EnsureStateChangedTestRecursiveGeneric(root_,
0u,
kMaxDepthForThisTest,
transitions_uninitialize,
expected_uninitialize);
}
TEST_F(SubtypeCheckTest, EnsureAssigned_TooDeep) {
auto transitions = [](MockClass* kls) {
UNUSED(kls);
return MockSubtypeOfTransition::kAssigned;
};
constexpr size_t kMaxDepthForThisTest = BitString::kCapacity + 1u;
auto expected = [=](MockClass* kls,
int expect_when,
SubtypeCheckInfo::State actual_state,
MockSubtypeOfTransition transition) {
UNUSED(transition);
if (expect_when == kAfterTransition) {
if (kls->Depth() > BitString::kCapacity) {
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, actual_state);
}
}
};
// Assign every level 0-4.
// We cannot assign 4th level, so it will overflow instead.
EnsureStateChangedTestRecursiveGeneric(root_, 0u, kMaxDepthForThisTest, transitions, expected);
}
TEST_F(SubtypeCheckTest, EnsureAssigned_TooDeep_OfTooDeep) {
auto transitions = [](MockClass* kls) {
UNUSED(kls);
return MockSubtypeOfTransition::kAssigned;
};
constexpr size_t kMaxDepthForThisTest = BitString::kCapacity + 2u;
auto expected = [=](MockClass* kls,
int expect_when,
SubtypeCheckInfo::State actual_state,
MockSubtypeOfTransition transition) {
UNUSED(transition);
if (expect_when == kAfterTransition) {
if (kls->Depth() > BitString::kCapacity) {
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, actual_state);
}
}
};
// Assign every level 0-5.
// We cannot assign 4th level, so it will overflow instead.
// In addition, level 5th cannot be assigned (parent is overflowed), so it will also fail.
EnsureStateChangedTestRecursiveGeneric(root_, 0u, kMaxDepthForThisTest, transitions, expected);
}
constexpr size_t MaxWidthCutOff(size_t depth) {
if (depth == 0) {
return 1;
}
if (depth > BitString::kCapacity) {
return std::numeric_limits<size_t>::max();
}
return MaxInt<size_t>(BitString::kBitSizeAtPosition[depth - 1]);
}
// Either itself is too wide, or any of the parents were too wide.
bool IsTooWide(MockClass* kls) {
if (kls == nullptr || kls->Depth() == 0u) {
// Root is never too wide.
return false;
} else {
if (kls->GetX() >= MaxWidthCutOff(kls->Depth())) {
return true;
}
}
return IsTooWide(kls->GetParent());
}
// Either itself is too deep, or any of the parents were too deep.
bool IsTooDeep(MockClass* kls) {
if (kls == nullptr || kls->Depth() == 0u) {
// Root is never too deep.
return false;
} else {
if (kls->Depth() > BitString::kCapacity) {
return true;
}
}
return false;
}
TEST_F(SubtypeCheckTest, EnsureInitialized_TooWide) {
auto transitions = [](MockClass* kls) {
UNUSED(kls);
return MockSubtypeOfTransition::kAssigned;
};
// Pick the 2nd level because has the most narrow # of bits.
constexpr size_t kTargetDepth = 2;
constexpr size_t kMaxWidthCutOff = MaxWidthCutOff(kTargetDepth);
constexpr size_t kMaxDepthForThisTest = std::numeric_limits<size_t>::max();
auto expected = [=](MockClass* kls,
int expect_when,
SubtypeCheckInfo::State actual_state,
MockSubtypeOfTransition transition) {
UNUSED(transition);
// Note: purposefuly ignore the too-deep children in the premade tree.
if (expect_when == kAfterTransition && kls->Depth() <= BitString::kCapacity) {
if (IsTooWide(kls)) {
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, actual_state);
} else {
EXPECT_EQ(SubtypeCheckInfo::kAssigned, actual_state);
}
}
};
{
// Create too-wide siblings at the kTargetDepth level.
MockClass* child = root_->FindChildAt(/*x*/0, kTargetDepth - 1u);
CreateTreeFor(child, kMaxWidthCutOff*2, /*depth*/1);
ASSERT_LE(kMaxWidthCutOff*2, child->GetNumberOfChildren());
ASSERT_TRUE(IsTooWide(child->GetMaxChild())) << *(child->GetMaxChild());
// Leave the rest of the tree as the default.
}
// Try to assign every level
// It will fail once it gets to the "too wide" siblings and cause overflows.
EnsureStateChangedTestRecursiveGeneric(root_,
0u,
kMaxDepthForThisTest,
transitions,
expected);
}
TEST_F(SubtypeCheckTest, EnsureInitialized_TooWide_TooWide) {
auto transitions = [](MockClass* kls) {
UNUSED(kls);
return MockSubtypeOfTransition::kAssigned;
};
// Pick the 2nd level because has the most narrow # of bits.
constexpr size_t kTargetDepth = 2;
constexpr size_t kMaxWidthCutOff = MaxWidthCutOff(kTargetDepth);
constexpr size_t kMaxWidthCutOffSub = MaxWidthCutOff(kTargetDepth+1u);
constexpr size_t kMaxDepthForThisTest = std::numeric_limits<size_t>::max();
auto expected = [=](MockClass* kls,
int expect_when,
SubtypeCheckInfo::State actual_state,
MockSubtypeOfTransition transition) {
UNUSED(transition);
// Note: purposefuly ignore the too-deep children in the premade tree.
if (expect_when == kAfterTransition && kls->Depth() <= BitString::kCapacity) {
if (IsTooWide(kls)) {
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, actual_state);
} else {
EXPECT_EQ(SubtypeCheckInfo::kAssigned, actual_state);
}
}
};
{
// Create too-wide siblings at the kTargetDepth level.
MockClass* child = root_->FindChildAt(/*x*/0, kTargetDepth - 1);
CreateTreeFor(child, kMaxWidthCutOff*2, /*depth*/1);
ASSERT_LE(kMaxWidthCutOff*2, child->GetNumberOfChildren()) << *child;
ASSERT_TRUE(IsTooWide(child->GetMaxChild())) << *(child->GetMaxChild());
// Leave the rest of the tree as the default.
// Create too-wide children for a too-wide parent.
MockClass* child_subchild = child->FindChildAt(/*x*/0, kTargetDepth);
CreateTreeFor(child_subchild, kMaxWidthCutOffSub*2, /*depth*/1);
ASSERT_LE(kMaxWidthCutOffSub*2, child_subchild->GetNumberOfChildren()) << *child_subchild;
ASSERT_TRUE(IsTooWide(child_subchild->GetMaxChild())) << *(child_subchild->GetMaxChild());
}
// Try to assign every level
// It will fail once it gets to the "too wide" siblings and cause overflows.
// Furthermore, assigning any subtree whose ancestor is too wide will also fail.
EnsureStateChangedTestRecursiveGeneric(root_, 0u, kMaxDepthForThisTest, transitions, expected);
}
void EnsureSubtypeOfCorrect(MockClass* a, MockClass* b) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
using SCTree = MockSubtypeCheck;
auto IsAssigned = [](SCTree& tree) {
MockScopedLockSubtypeCheck lock_a;
MockScopedLockMutator lock_b;
// This assumes that MockClass is always called with EnsureAssigned.
EXPECT_NE(SubtypeCheckInfo::kInitialized, tree.GetState());
EXPECT_NE(SubtypeCheckInfo::kUninitialized, tree.GetState());
// Use our own test checks, so we are actually testing different logic than the impl.
return !(IsTooDeep(&tree.GetClass()) || IsTooWide(&tree.GetClass()));
};
SCTree src_tree = SCTree::Lookup(a);
SCTree target_tree = SCTree::Lookup(b);
SCOPED_TRACE("class A");
SCOPED_TRACE(*a);
SCOPED_TRACE("class B");
SCOPED_TRACE(*b);
SubtypeCheckInfo::Result slow_result =
a->SlowIsSubtypeOf(b) ? SubtypeCheckInfo::kSubtypeOf : SubtypeCheckInfo::kNotSubtypeOf;
SubtypeCheckInfo::Result fast_result = src_tree.IsSubtypeOf(target_tree);
// Target must be Assigned for this check to succeed.
// Source is either Overflowed | Assigned (in this case).
if (IsAssigned(src_tree) && IsAssigned(target_tree)) {
ASSERT_EQ(slow_result, fast_result);
} else if (IsAssigned(src_tree)) {
// A is assigned. B is >= initialized.
ASSERT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, fast_result);
} else if (IsAssigned(target_tree)) {
// B is assigned. A is >= initialized.
ASSERT_EQ(slow_result, fast_result);
} else {
// Neither A,B are assigned.
ASSERT_EQ(SubtypeCheckInfo::kUnknownSubtypeOf, fast_result);
}
// Use asserts, not expects to immediately fail.
// Otherwise the entire tree (very large) could potentially be broken.
}
void EnsureSubtypeOfRecursive(MockClass* kls_root) {
MockScopedLockMutator mutator_lock_fake_;
auto visit_func = [&](MockClass* kls) {
kls->Visit([&](MockClass* inner_class) {
EnsureSubtypeOfCorrect(kls, inner_class);
EnsureSubtypeOfCorrect(inner_class, kls);
if (::testing::Test::HasFatalFailure()) {
return false;
}
return true; // Keep visiting.
});
if (::testing::Test::HasFatalFailure()) {
return false;
}
return true; // Keep visiting.
};
ASSERT_NO_FATAL_FAILURE(kls_root->Visit(visit_func));
}
TEST_F(SubtypeCheckTest, EnsureInitialized_TooWide_TooDeep) {
auto transitions = [](MockClass* kls) {
UNUSED(kls);
return MockSubtypeOfTransition::kAssigned;
};
// Pick the 2nd level because has the most narrow # of bits.
constexpr size_t kTargetDepth = 2;
constexpr size_t kTooDeepTargetDepth = BitString::kCapacity + 1;
constexpr size_t kMaxWidthCutOff = MaxWidthCutOff(kTargetDepth);
constexpr size_t kMaxDepthForThisTest = std::numeric_limits<size_t>::max();
auto expected = [=](MockClass* kls,
int expect_when,
SubtypeCheckInfo::State actual_state,
MockSubtypeOfTransition transition) {
UNUSED(transition);
if (expect_when == kAfterTransition) {
if (IsTooDeep(kls)) {
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, actual_state);
} else if (IsTooWide(kls)) {
EXPECT_EQ(SubtypeCheckInfo::kOverflowed, actual_state);
} else {
EXPECT_EQ(SubtypeCheckInfo::kAssigned, actual_state);
}
}
};
{
// Create too-wide siblings at the kTargetDepth level.
MockClass* child = root_->FindChildAt(/*x*/0, kTargetDepth - 1u);
CreateTreeFor(child, kMaxWidthCutOff*2, /*depth*/1);
ASSERT_LE(kMaxWidthCutOff*2, child->GetNumberOfChildren());
ASSERT_TRUE(IsTooWide(child->GetMaxChild())) << *(child->GetMaxChild());
// Leave the rest of the tree as the default.
// Create too-deep children for a too-wide parent.
MockClass* child_subchild = child->GetMaxChild();
ASSERT_TRUE(child_subchild != nullptr);
ASSERT_EQ(0u, child_subchild->GetNumberOfChildren()) << *child_subchild;
CreateTreeFor(child_subchild, /*width*/1, /*levels*/kTooDeepTargetDepth);
MockClass* too_deep_child = child_subchild->FindChildAt(0, kTooDeepTargetDepth + 2);
ASSERT_TRUE(too_deep_child != nullptr) << child_subchild->ToDotGraph();
ASSERT_TRUE(IsTooWide(too_deep_child)) << *(too_deep_child);
ASSERT_TRUE(IsTooDeep(too_deep_child)) << *(too_deep_child);
}
// Try to assign every level
// It will fail once it gets to the "too wide" siblings and cause overflows.
EnsureStateChangedTestRecursiveGeneric(root_, 0u, kMaxDepthForThisTest, transitions, expected);
// Check every class against every class for "x instanceof y".
EnsureSubtypeOfRecursive(root_);
}
// TODO: add dcheck for child-parent invariants (e.g. child < parent.next) and death tests
} // namespace art