/* * Copyright (C) 2015 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 "unstarted_runtime.h" #include #include #include "base/casts.h" #include "base/enums.h" #include "base/memory_tool.h" #include "class_linker.h" #include "class_root.h" #include "common_runtime_test.h" #include "dex/descriptors_names.h" #include "dex/dex_instruction.h" #include "handle.h" #include "handle_scope-inl.h" #include "interpreter/interpreter_common.h" #include "mirror/array-alloc-inl.h" #include "mirror/class-alloc-inl.h" #include "mirror/class_loader.h" #include "mirror/object-inl.h" #include "mirror/object_array-alloc-inl.h" #include "mirror/object_array-inl.h" #include "mirror/string-inl.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" #include "shadow_frame-inl.h" #include "thread.h" #include "transaction.h" namespace art { namespace interpreter { // Deleter to be used with ShadowFrame::CreateDeoptimizedFrame objects. struct DeoptShadowFrameDelete { // NOTE: Deleting a const object is valid but free() takes a non-const pointer. void operator()(ShadowFrame* ptr) const { if (ptr != nullptr) { ShadowFrame::DeleteDeoptimizedFrame(ptr); } } }; // Alias for std::unique_ptr<> that uses the above deleter. using UniqueDeoptShadowFramePtr = std::unique_ptr; class UnstartedRuntimeTest : public CommonRuntimeTest { protected: // Re-expose all UnstartedRuntime implementations so we don't need to declare a million // test friends. // Methods that intercept available libcore implementations. #define UNSTARTED_DIRECT(Name, SigIgnored) \ static void Unstarted ## Name(Thread* self, \ ShadowFrame* shadow_frame, \ JValue* result, \ size_t arg_offset) \ REQUIRES_SHARED(Locks::mutator_lock_) { \ interpreter::UnstartedRuntime::Unstarted ## Name(self, shadow_frame, result, arg_offset); \ } #include "unstarted_runtime_list.h" UNSTARTED_RUNTIME_DIRECT_LIST(UNSTARTED_DIRECT) #undef UNSTARTED_RUNTIME_DIRECT_LIST #undef UNSTARTED_RUNTIME_JNI_LIST #undef UNSTARTED_DIRECT // Methods that are native. #define UNSTARTED_JNI(Name, SigIgnored) \ static void UnstartedJNI ## Name(Thread* self, \ ArtMethod* method, \ mirror::Object* receiver, \ uint32_t* args, \ JValue* result) \ REQUIRES_SHARED(Locks::mutator_lock_) { \ interpreter::UnstartedRuntime::UnstartedJNI ## Name(self, method, receiver, args, result); \ } #include "unstarted_runtime_list.h" UNSTARTED_RUNTIME_JNI_LIST(UNSTARTED_JNI) #undef UNSTARTED_RUNTIME_DIRECT_LIST #undef UNSTARTED_RUNTIME_JNI_LIST #undef UNSTARTED_JNI UniqueDeoptShadowFramePtr CreateShadowFrame(uint32_t num_vregs, ShadowFrame* link, ArtMethod* method, uint32_t dex_pc) { return UniqueDeoptShadowFramePtr( ShadowFrame::CreateDeoptimizedFrame(num_vregs, link, method, dex_pc)); } // Helpers for ArrayCopy. // // Note: as we have to use handles, we use StackHandleScope to transfer data. Hardcode a size // of three everywhere. That is enough to test all cases. static ObjPtr> CreateObjectArray( Thread* self, ObjPtr component_type, const StackHandleScope<3>& data) REQUIRES_SHARED(Locks::mutator_lock_) { Runtime* runtime = Runtime::Current(); ObjPtr array_type = runtime->GetClassLinker()->FindArrayClass(self, component_type); CHECK(array_type != nullptr); ObjPtr> result = mirror::ObjectArray::Alloc(self, array_type, 3); CHECK(result != nullptr); for (size_t i = 0; i < 3; ++i) { result->Set(static_cast(i), data.GetReference(i)); CHECK(!self->IsExceptionPending()); } return result; } static void CheckObjectArray(ObjPtr> array, const StackHandleScope<3>& data) REQUIRES_SHARED(Locks::mutator_lock_) { CHECK_EQ(array->GetLength(), 3); CHECK_EQ(data.NumberOfReferences(), 3U); for (size_t i = 0; i < 3; ++i) { EXPECT_OBJ_PTR_EQ(data.GetReference(i), array->Get(static_cast(i))) << i; } } void RunArrayCopy(Thread* self, ShadowFrame* tmp, bool expect_exception, ObjPtr> src, int32_t src_pos, ObjPtr> dst, int32_t dst_pos, int32_t length) REQUIRES_SHARED(Locks::mutator_lock_) { JValue result; tmp->SetVRegReference(0, src); tmp->SetVReg(1, src_pos); tmp->SetVRegReference(2, dst); tmp->SetVReg(3, dst_pos); tmp->SetVReg(4, length); UnstartedSystemArraycopy(self, tmp, &result, 0); bool exception_pending = self->IsExceptionPending(); EXPECT_EQ(exception_pending, expect_exception); if (exception_pending) { self->ClearException(); } } void RunArrayCopy(Thread* self, ShadowFrame* tmp, bool expect_exception, ObjPtr src_component_class, ObjPtr dst_component_class, const StackHandleScope<3>& src_data, int32_t src_pos, const StackHandleScope<3>& dst_data, int32_t dst_pos, int32_t length, const StackHandleScope<3>& expected_result) REQUIRES_SHARED(Locks::mutator_lock_) { StackHandleScope<3> hs_misc(self); Handle dst_component_handle(hs_misc.NewHandle(dst_component_class)); Handle> src_handle( hs_misc.NewHandle(CreateObjectArray(self, src_component_class, src_data))); Handle> dst_handle( hs_misc.NewHandle(CreateObjectArray(self, dst_component_handle.Get(), dst_data))); RunArrayCopy(self, tmp, expect_exception, src_handle.Get(), src_pos, dst_handle.Get(), dst_pos, length); CheckObjectArray(dst_handle.Get(), expected_result); } void TestCeilFloor(bool ceil, Thread* self, ShadowFrame* tmp, double const test_pairs[][2], size_t num_pairs) REQUIRES_SHARED(Locks::mutator_lock_) { for (size_t i = 0; i < num_pairs; ++i) { tmp->SetVRegDouble(0, test_pairs[i][0]); JValue result; if (ceil) { UnstartedMathCeil(self, tmp, &result, 0); } else { UnstartedMathFloor(self, tmp, &result, 0); } ASSERT_FALSE(self->IsExceptionPending()); // We want precise results. int64_t result_int64t = bit_cast(result.GetD()); int64_t expect_int64t = bit_cast(test_pairs[i][1]); EXPECT_EQ(expect_int64t, result_int64t) << result.GetD() << " vs " << test_pairs[i][1]; } } // Prepare for aborts. Aborts assume that the exception class is already resolved, as the // loading code doesn't work under transactions. void PrepareForAborts() REQUIRES_SHARED(Locks::mutator_lock_) { ObjPtr result = Runtime::Current()->GetClassLinker()->FindClass( Thread::Current(), Transaction::kAbortExceptionSignature, ScopedNullHandle()); CHECK(result != nullptr); } }; TEST_F(UnstartedRuntimeTest, MemoryPeekByte) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); constexpr const uint8_t base_array[] = "abcdefghijklmnop"; constexpr int32_t kBaseLen = sizeof(base_array) / sizeof(uint8_t); const uint8_t* base_ptr = base_array; JValue result; UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); for (int32_t i = 0; i < kBaseLen; ++i) { tmp->SetVRegLong(0, static_cast(reinterpret_cast(base_ptr + i))); UnstartedMemoryPeekByte(self, tmp.get(), &result, 0); EXPECT_EQ(result.GetB(), static_cast(base_array[i])); } } TEST_F(UnstartedRuntimeTest, MemoryPeekShort) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); constexpr const uint8_t base_array[] = "abcdefghijklmnop"; constexpr int32_t kBaseLen = sizeof(base_array) / sizeof(uint8_t); const uint8_t* base_ptr = base_array; JValue result; UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); int32_t adjusted_length = kBaseLen - sizeof(int16_t); for (int32_t i = 0; i < adjusted_length; ++i) { tmp->SetVRegLong(0, static_cast(reinterpret_cast(base_ptr + i))); UnstartedMemoryPeekShort(self, tmp.get(), &result, 0); using unaligned_short __attribute__((__aligned__(1))) = int16_t; const unaligned_short* short_ptr = reinterpret_cast(base_ptr + i); EXPECT_EQ(result.GetS(), *short_ptr); } } TEST_F(UnstartedRuntimeTest, MemoryPeekInt) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); constexpr const uint8_t base_array[] = "abcdefghijklmnop"; constexpr int32_t kBaseLen = sizeof(base_array) / sizeof(uint8_t); const uint8_t* base_ptr = base_array; JValue result; UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); int32_t adjusted_length = kBaseLen - sizeof(int32_t); for (int32_t i = 0; i < adjusted_length; ++i) { tmp->SetVRegLong(0, static_cast(reinterpret_cast(base_ptr + i))); UnstartedMemoryPeekInt(self, tmp.get(), &result, 0); using unaligned_int __attribute__((__aligned__(1))) = int32_t; const unaligned_int* int_ptr = reinterpret_cast(base_ptr + i); EXPECT_EQ(result.GetI(), *int_ptr); } } TEST_F(UnstartedRuntimeTest, MemoryPeekLong) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); constexpr const uint8_t base_array[] = "abcdefghijklmnop"; constexpr int32_t kBaseLen = sizeof(base_array) / sizeof(uint8_t); const uint8_t* base_ptr = base_array; JValue result; UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); int32_t adjusted_length = kBaseLen - sizeof(int64_t); for (int32_t i = 0; i < adjusted_length; ++i) { tmp->SetVRegLong(0, static_cast(reinterpret_cast(base_ptr + i))); UnstartedMemoryPeekLong(self, tmp.get(), &result, 0); using unaligned_long __attribute__((__aligned__(1))) = int64_t; const unaligned_long* long_ptr = reinterpret_cast(base_ptr + i); EXPECT_EQ(result.GetJ(), *long_ptr); } } TEST_F(UnstartedRuntimeTest, StringGetCharsNoCheck) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<2> hs(self); // TODO: Actual UTF. constexpr const char base_string[] = "abcdefghijklmnop"; Handle h_test_string(hs.NewHandle( mirror::String::AllocFromModifiedUtf8(self, base_string))); constexpr int32_t kBaseLen = sizeof(base_string) / sizeof(char) - 1; Handle h_char_array(hs.NewHandle( mirror::CharArray::Alloc(self, kBaseLen))); // A buffer so we can make sure we only modify the elements targetted. uint16_t buf[kBaseLen]; JValue result; UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); for (int32_t start_index = 0; start_index < kBaseLen; ++start_index) { for (int32_t count = 0; count <= kBaseLen; ++count) { for (int32_t trg_offset = 0; trg_offset < kBaseLen; ++trg_offset) { // Only do it when in bounds. if (start_index + count <= kBaseLen && trg_offset + count <= kBaseLen) { tmp->SetVRegReference(0, h_test_string.Get()); tmp->SetVReg(1, start_index); tmp->SetVReg(2, count); tmp->SetVRegReference(3, h_char_array.Get()); tmp->SetVReg(3, trg_offset); // Copy the char_array into buf. memcpy(buf, h_char_array->GetData(), kBaseLen * sizeof(uint16_t)); UnstartedStringCharAt(self, tmp.get(), &result, 0); uint16_t* data = h_char_array->GetData(); bool success = true; // First segment should be unchanged. for (int32_t i = 0; i < trg_offset; ++i) { success = success && (data[i] == buf[i]); } // Second segment should be a copy. for (int32_t i = trg_offset; i < trg_offset + count; ++i) { success = success && (data[i] == buf[i - trg_offset + start_index]); } // Third segment should be unchanged. for (int32_t i = trg_offset + count; i < kBaseLen; ++i) { success = success && (data[i] == buf[i]); } EXPECT_TRUE(success); } } } } } TEST_F(UnstartedRuntimeTest, StringCharAt) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); // TODO: Actual UTF. constexpr const char* base_string = "abcdefghijklmnop"; int32_t base_len = static_cast(strlen(base_string)); ObjPtr test_string = mirror::String::AllocFromModifiedUtf8(self, base_string); JValue result; UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); for (int32_t i = 0; i < base_len; ++i) { tmp->SetVRegReference(0, test_string); tmp->SetVReg(1, i); UnstartedStringCharAt(self, tmp.get(), &result, 0); EXPECT_EQ(result.GetI(), base_string[i]); } } TEST_F(UnstartedRuntimeTest, StringInit) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); ObjPtr klass = GetClassRoot(); ArtMethod* method = klass->FindConstructor("(Ljava/lang/String;)V", Runtime::Current()->GetClassLinker()->GetImagePointerSize()); ASSERT_TRUE(method != nullptr); // create instruction data for invoke-direct {v0, v1} of method with fake index uint16_t inst_data[3] = { 0x2070, 0x0000, 0x0010 }; JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, method, 0); const char* base_string = "hello_world"; StackHandleScope<2> hs(self); Handle string_arg = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, base_string)); Handle reference_empty_string = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "")); shadow_frame->SetVRegReference(0, reference_empty_string.Get()); shadow_frame->SetVRegReference(1, string_arg.Get()); interpreter::DoCall(method, self, *shadow_frame, Instruction::At(inst_data), inst_data[0], &result); ObjPtr string_result = down_cast(result.GetL()); EXPECT_EQ(string_arg->GetLength(), string_result->GetLength()); if (string_arg->IsCompressed() && string_result->IsCompressed()) { EXPECT_EQ(memcmp(string_arg->GetValueCompressed(), string_result->GetValueCompressed(), string_arg->GetLength() * sizeof(uint8_t)), 0); } else if (!string_arg->IsCompressed() && !string_result->IsCompressed()) { EXPECT_EQ(memcmp(string_arg->GetValue(), string_result->GetValue(), string_arg->GetLength() * sizeof(uint16_t)), 0); } else { bool equal = true; for (int i = 0; i < string_arg->GetLength(); ++i) { if (string_arg->CharAt(i) != string_result->CharAt(i)) { equal = false; break; } } EXPECT_EQ(equal, true); } } // Tests the exceptions that should be checked before modifying the destination. // (Doesn't check the object vs primitive case ATM.) TEST_F(UnstartedRuntimeTest, SystemArrayCopyObjectArrayTestExceptions) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); JValue result; UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); // Note: all tests are not GC safe. Assume there's no GC running here with the few objects we // allocate. StackHandleScope<3> hs_misc(self); Handle object_class(hs_misc.NewHandle(GetClassRoot())); StackHandleScope<3> hs_data(self); hs_data.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "1")); hs_data.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "2")); hs_data.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "3")); Handle> array( hs_misc.NewHandle(CreateObjectArray(self, object_class.Get(), hs_data))); RunArrayCopy(self, tmp.get(), true, array.Get(), -1, array.Get(), 0, 0); RunArrayCopy(self, tmp.get(), true, array.Get(), 0, array.Get(), -1, 0); RunArrayCopy(self, tmp.get(), true, array.Get(), 0, array.Get(), 0, -1); RunArrayCopy(self, tmp.get(), true, array.Get(), 0, array.Get(), 0, 4); RunArrayCopy(self, tmp.get(), true, array.Get(), 0, array.Get(), 1, 3); RunArrayCopy(self, tmp.get(), true, array.Get(), 1, array.Get(), 0, 3); Handle> class_as_array = hs_misc.NewHandle(reinterpret_cast*>(object_class.Get())); RunArrayCopy(self, tmp.get(), true, class_as_array.Get(), 0, array.Get(), 0, 0); RunArrayCopy(self, tmp.get(), true, array.Get(), 0, class_as_array.Get(), 0, 0); } TEST_F(UnstartedRuntimeTest, SystemArrayCopyObjectArrayTest) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); JValue result; UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); StackHandleScope<1> hs_object(self); Handle object_class(hs_object.NewHandle(GetClassRoot())); // Simple test: // [1,2,3]{1 @ 2} into [4,5,6] = [4,2,6] { StackHandleScope<3> hs_src(self); hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "1")); hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "2")); hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "3")); StackHandleScope<3> hs_dst(self); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "4")); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "5")); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "6")); StackHandleScope<3> hs_expected(self); hs_expected.NewHandle(hs_dst.GetReference(0)); hs_expected.NewHandle(hs_dst.GetReference(1)); hs_expected.NewHandle(hs_src.GetReference(1)); RunArrayCopy(self, tmp.get(), false, object_class.Get(), object_class.Get(), hs_src, 1, hs_dst, 2, 1, hs_expected); } // Simple test: // [1,2,3]{1 @ 1} into [4,5,6] = [4,2,6] (with dst String[]) { StackHandleScope<3> hs_src(self); hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "1")); hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "2")); hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "3")); StackHandleScope<3> hs_dst(self); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "4")); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "5")); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "6")); StackHandleScope<3> hs_expected(self); hs_expected.NewHandle(hs_dst.GetReference(0)); hs_expected.NewHandle(hs_src.GetReference(1)); hs_expected.NewHandle(hs_dst.GetReference(2)); RunArrayCopy(self, tmp.get(), false, object_class.Get(), GetClassRoot(), hs_src, 1, hs_dst, 1, 1, hs_expected); } // Simple test: // [1,*,3] into [4,5,6] = [1,5,6] + exc { StackHandleScope<3> hs_src(self); hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "1")); hs_src.NewHandle(GetClassRoot()); hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "3")); StackHandleScope<3> hs_dst(self); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "4")); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "5")); hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "6")); StackHandleScope<3> hs_expected(self); hs_expected.NewHandle(hs_src.GetReference(0)); hs_expected.NewHandle(hs_dst.GetReference(1)); hs_expected.NewHandle(hs_dst.GetReference(2)); RunArrayCopy(self, tmp.get(), true, object_class.Get(), GetClassRoot(), hs_src, 0, hs_dst, 0, 3, hs_expected); } } TEST_F(UnstartedRuntimeTest, IntegerParseIntTest) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); // Test string. Should be valid, and between minimal values of LONG_MIN and LONG_MAX (for all // suffixes). constexpr const char* test_string = "-2147483646"; constexpr int32_t test_values[] = { 6, 46, 646, 3646, 83646, 483646, 7483646, 47483646, 147483646, 2147483646, -2147483646 }; static_assert(arraysize(test_values) == 11U, "test_values"); CHECK_EQ(strlen(test_string), 11U); for (size_t i = 0; i <= 10; ++i) { const char* test_value = &test_string[10 - i]; StackHandleScope<1> hs_str(self); Handle h_str( hs_str.NewHandle(mirror::String::AllocFromModifiedUtf8(self, test_value))); ASSERT_NE(h_str.Get(), nullptr); ASSERT_FALSE(self->IsExceptionPending()); tmp->SetVRegReference(0, h_str.Get()); JValue result; UnstartedIntegerParseInt(self, tmp.get(), &result, 0); ASSERT_FALSE(self->IsExceptionPending()); EXPECT_EQ(result.GetI(), test_values[i]); } } // Right now the same as Integer.Parse TEST_F(UnstartedRuntimeTest, LongParseLongTest) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); // Test string. Should be valid, and between minimal values of LONG_MIN and LONG_MAX (for all // suffixes). constexpr const char* test_string = "-2147483646"; constexpr int64_t test_values[] = { 6, 46, 646, 3646, 83646, 483646, 7483646, 47483646, 147483646, 2147483646, -2147483646 }; static_assert(arraysize(test_values) == 11U, "test_values"); CHECK_EQ(strlen(test_string), 11U); for (size_t i = 0; i <= 10; ++i) { const char* test_value = &test_string[10 - i]; StackHandleScope<1> hs_str(self); Handle h_str( hs_str.NewHandle(mirror::String::AllocFromModifiedUtf8(self, test_value))); ASSERT_NE(h_str.Get(), nullptr); ASSERT_FALSE(self->IsExceptionPending()); tmp->SetVRegReference(0, h_str.Get()); JValue result; UnstartedLongParseLong(self, tmp.get(), &result, 0); ASSERT_FALSE(self->IsExceptionPending()); EXPECT_EQ(result.GetJ(), test_values[i]); } } TEST_F(UnstartedRuntimeTest, Ceil) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); constexpr double nan = std::numeric_limits::quiet_NaN(); constexpr double inf = std::numeric_limits::infinity(); constexpr double ld1 = static_cast((UINT64_C(1) << 53) - 1); constexpr double ld2 = static_cast(UINT64_C(1) << 55); constexpr double test_pairs[][2] = { { -0.0, -0.0 }, { 0.0, 0.0 }, { -0.5, -0.0 }, { -1.0, -1.0 }, { 0.5, 1.0 }, { 1.0, 1.0 }, { nan, nan }, { inf, inf }, { -inf, -inf }, { ld1, ld1 }, { ld2, ld2 } }; TestCeilFloor(/* ceil= */ true, self, tmp.get(), test_pairs, arraysize(test_pairs)); } TEST_F(UnstartedRuntimeTest, Floor) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); constexpr double nan = std::numeric_limits::quiet_NaN(); constexpr double inf = std::numeric_limits::infinity(); constexpr double ld1 = static_cast((UINT64_C(1) << 53) - 1); constexpr double ld2 = static_cast(UINT64_C(1) << 55); constexpr double test_pairs[][2] = { { -0.0, -0.0 }, { 0.0, 0.0 }, { -0.5, -1.0 }, { -1.0, -1.0 }, { 0.5, 0.0 }, { 1.0, 1.0 }, { nan, nan }, { inf, inf }, { -inf, -inf }, { ld1, ld1 }, { ld2, ld2 } }; TestCeilFloor(/* ceil= */ false, self, tmp.get(), test_pairs, arraysize(test_pairs)); } TEST_F(UnstartedRuntimeTest, ToLowerUpper) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); std::locale c_locale("C"); // Check ASCII. for (uint32_t i = 0; i < 128; ++i) { bool c_upper = std::isupper(static_cast(i), c_locale); bool c_lower = std::islower(static_cast(i), c_locale); EXPECT_FALSE(c_upper && c_lower) << i; // Check toLowerCase. { JValue result; tmp->SetVReg(0, static_cast(i)); UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0); ASSERT_FALSE(self->IsExceptionPending()); uint32_t lower_result = static_cast(result.GetI()); if (c_lower) { EXPECT_EQ(i, lower_result); } else if (c_upper) { EXPECT_EQ(static_cast(std::tolower(static_cast(i), c_locale)), lower_result); } else { EXPECT_EQ(i, lower_result); } } // Check toUpperCase. { JValue result2; tmp->SetVReg(0, static_cast(i)); UnstartedCharacterToUpperCase(self, tmp.get(), &result2, 0); ASSERT_FALSE(self->IsExceptionPending()); uint32_t upper_result = static_cast(result2.GetI()); if (c_upper) { EXPECT_EQ(i, upper_result); } else if (c_lower) { EXPECT_EQ(static_cast(std::toupper(static_cast(i), c_locale)), upper_result); } else { EXPECT_EQ(i, upper_result); } } } // Check abort for other things. Can't test all. PrepareForAborts(); for (uint32_t i = 128; i < 256; ++i) { { JValue result; tmp->SetVReg(0, static_cast(i)); EnterTransactionMode(); UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0); ASSERT_TRUE(IsTransactionAborted()); ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); } { JValue result; tmp->SetVReg(0, static_cast(i)); EnterTransactionMode(); UnstartedCharacterToUpperCase(self, tmp.get(), &result, 0); ASSERT_TRUE(IsTransactionAborted()); ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); } } for (uint64_t i = 256; i <= std::numeric_limits::max(); i <<= 1) { { JValue result; tmp->SetVReg(0, static_cast(i)); EnterTransactionMode(); UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0); ASSERT_TRUE(IsTransactionAborted()); ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); } { JValue result; tmp->SetVReg(0, static_cast(i)); EnterTransactionMode(); UnstartedCharacterToUpperCase(self, tmp.get(), &result, 0); ASSERT_TRUE(IsTransactionAborted()); ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); } } } TEST_F(UnstartedRuntimeTest, Sin) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); // Test an important value, PI/6. That's the one we see in practice. constexpr uint64_t lvalue = UINT64_C(0x3fe0c152382d7365); tmp->SetVRegLong(0, static_cast(lvalue)); JValue result; UnstartedMathSin(self, tmp.get(), &result, 0); const uint64_t lresult = static_cast(result.GetJ()); EXPECT_EQ(UINT64_C(0x3fdfffffffffffff), lresult); } TEST_F(UnstartedRuntimeTest, Cos) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); // Test an important value, PI/6. That's the one we see in practice. constexpr uint64_t lvalue = UINT64_C(0x3fe0c152382d7365); tmp->SetVRegLong(0, static_cast(lvalue)); JValue result; UnstartedMathCos(self, tmp.get(), &result, 0); const uint64_t lresult = static_cast(result.GetJ()); EXPECT_EQ(UINT64_C(0x3febb67ae8584cab), lresult); } TEST_F(UnstartedRuntimeTest, Pow) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); // Test an important pair. constexpr uint64_t lvalue1 = UINT64_C(0x4079000000000000); constexpr uint64_t lvalue2 = UINT64_C(0xbfe6db6dc0000000); tmp->SetVRegLong(0, static_cast(lvalue1)); tmp->SetVRegLong(2, static_cast(lvalue2)); JValue result; UnstartedMathPow(self, tmp.get(), &result, 0); const uint64_t lresult = static_cast(result.GetJ()); EXPECT_EQ(UINT64_C(0x3f8c5c51326aa7ee), lresult); } TEST_F(UnstartedRuntimeTest, IsAnonymousClass) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0); ObjPtr class_klass = GetClassRoot(); shadow_frame->SetVRegReference(0, class_klass); UnstartedClassIsAnonymousClass(self, shadow_frame.get(), &result, 0); EXPECT_EQ(result.GetZ(), 0); jobject class_loader = LoadDex("Nested"); StackHandleScope<1> hs(soa.Self()); Handle loader( hs.NewHandle(soa.Decode(class_loader))); ObjPtr c = class_linker_->FindClass(soa.Self(), "LNested$1;", loader); ASSERT_TRUE(c != nullptr); shadow_frame->SetVRegReference(0, c); UnstartedClassIsAnonymousClass(self, shadow_frame.get(), &result, 0); EXPECT_EQ(result.GetZ(), 1); } TEST_F(UnstartedRuntimeTest, GetDeclaringClass) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0); jobject class_loader = LoadDex("Nested"); StackHandleScope<4> hs(self); Handle loader( hs.NewHandle(soa.Decode(class_loader))); Handle nested_klass(hs.NewHandle( class_linker_->FindClass(soa.Self(), "LNested;", loader))); Handle inner_klass(hs.NewHandle( class_linker_->FindClass(soa.Self(), "LNested$Inner;", loader))); Handle anon_klass(hs.NewHandle( class_linker_->FindClass(soa.Self(), "LNested$1;", loader))); shadow_frame->SetVRegReference(0, nested_klass.Get()); UnstartedClassGetDeclaringClass(self, shadow_frame.get(), &result, 0); EXPECT_EQ(result.GetL(), nullptr); shadow_frame->SetVRegReference(0, inner_klass.Get()); UnstartedClassGetDeclaringClass(self, shadow_frame.get(), &result, 0); EXPECT_EQ(result.GetL(), nested_klass.Get()); shadow_frame->SetVRegReference(0, anon_klass.Get()); UnstartedClassGetDeclaringClass(self, shadow_frame.get(), &result, 0); EXPECT_EQ(result.GetL(), nullptr); } TEST_F(UnstartedRuntimeTest, ThreadLocalGet) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0); StackHandleScope<1> hs(self); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); // Positive test. See that We get something for float conversion. { Handle floating_decimal = hs.NewHandle( class_linker->FindClass(self, "Lsun/misc/FloatingDecimal;", ScopedNullHandle())); ASSERT_TRUE(floating_decimal != nullptr); ASSERT_TRUE(class_linker->EnsureInitialized(self, floating_decimal, true, true)); ArtMethod* caller_method = floating_decimal->FindClassMethod( "getBinaryToASCIIBuffer", "()Lsun/misc/FloatingDecimal$BinaryToASCIIBuffer;", class_linker->GetImagePointerSize()); // floating_decimal->DumpClass(LOG_STREAM(ERROR), mirror::Class::kDumpClassFullDetail); ASSERT_TRUE(caller_method != nullptr); ASSERT_TRUE(caller_method->IsDirect()); ASSERT_TRUE(caller_method->GetDeclaringClass() == floating_decimal.Get()); UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, nullptr, caller_method, 0); shadow_frame->SetLink(caller_frame.get()); UnstartedThreadLocalGet(self, shadow_frame.get(), &result, 0); EXPECT_TRUE(result.GetL() != nullptr); EXPECT_FALSE(self->IsExceptionPending()); shadow_frame->SetLink(nullptr); } // Negative test. PrepareForAborts(); { // Just use a method in Class. ObjPtr class_class = GetClassRoot(); ArtMethod* caller_method = &*class_class->GetDeclaredMethods(class_linker->GetImagePointerSize()).begin(); UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, nullptr, caller_method, 0); shadow_frame->SetLink(caller_frame.get()); EnterTransactionMode(); UnstartedThreadLocalGet(self, shadow_frame.get(), &result, 0); ASSERT_TRUE(IsTransactionAborted()); ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); self->ClearException(); shadow_frame->SetLink(nullptr); } } TEST_F(UnstartedRuntimeTest, FloatConversion) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); Handle double_class = hs.NewHandle( class_linker->FindClass(self, "Ljava/lang/Double;", ScopedNullHandle())); ASSERT_TRUE(double_class != nullptr); ASSERT_TRUE(class_linker->EnsureInitialized(self, double_class, true, true)); ArtMethod* method = double_class->FindClassMethod("toString", "(D)Ljava/lang/String;", class_linker->GetImagePointerSize()); ASSERT_TRUE(method != nullptr); ASSERT_TRUE(method->IsDirect()); ASSERT_TRUE(method->GetDeclaringClass() == double_class.Get()); // create instruction data for invoke-direct {v0, v1} of method with fake index uint16_t inst_data[3] = { 0x2070, 0x0000, 0x0010 }; JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, method, 0); shadow_frame->SetVRegDouble(0, 1.23); interpreter::DoCall(method, self, *shadow_frame, Instruction::At(inst_data), inst_data[0], &result); ObjPtr string_result = down_cast(result.GetL()); ASSERT_TRUE(string_result != nullptr); std::string mod_utf = string_result->ToModifiedUtf8(); EXPECT_EQ("1.23", mod_utf); } TEST_F(UnstartedRuntimeTest, ThreadCurrentThread) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0); StackHandleScope<1> hs(self); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); Handle thread_class = hs.NewHandle( class_linker->FindClass(self, "Ljava/lang/Thread;", ScopedNullHandle())); ASSERT_TRUE(thread_class.Get() != nullptr); ASSERT_TRUE(class_linker->EnsureInitialized(self, thread_class, true, true)); // Negative test. In general, currentThread should fail (as we should not leak a peer that will // be recreated at runtime). PrepareForAborts(); { EnterTransactionMode(); UnstartedThreadCurrentThread(self, shadow_frame.get(), &result, 0); ASSERT_TRUE(IsTransactionAborted()); ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); self->ClearException(); } } TEST_F(UnstartedRuntimeTest, LogManager) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); Handle log_manager_class = hs.NewHandle( class_linker->FindClass(self, "Ljava/util/logging/LogManager;", ScopedNullHandle())); ASSERT_TRUE(log_manager_class.Get() != nullptr); ASSERT_TRUE(class_linker->EnsureInitialized(self, log_manager_class, true, true)); } class UnstartedClassForNameTest : public UnstartedRuntimeTest { public: template void RunTest(T& runner, bool in_transaction, bool should_succeed) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); // Ensure that Class is initialized. { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); StackHandleScope<1> hs(self); Handle h_class = hs.NewHandle(GetClassRoot()); CHECK(class_linker->EnsureInitialized(self, h_class, true, true)); } // A selection of classes from different core classpath components. constexpr const char* kTestCases[] = { "java.net.CookieManager", // From libcore. "dalvik.system.ClassExt", // From libart. }; if (in_transaction) { // For transaction mode, we cannot load any classes, as the pre-fence initialization of // classes isn't transactional. Load them ahead of time. ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); for (const char* name : kTestCases) { class_linker->FindClass(self, DotToDescriptor(name).c_str(), ScopedNullHandle()); CHECK(!self->IsExceptionPending()) << self->GetException()->Dump(); } } if (!should_succeed) { // Negative test. In general, currentThread should fail (as we should not leak a peer that will // be recreated at runtime). PrepareForAborts(); } JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0); for (const char* name : kTestCases) { ObjPtr name_string = mirror::String::AllocFromModifiedUtf8(self, name); CHECK(name_string != nullptr); if (in_transaction) { EnterTransactionMode(); } CHECK(!self->IsExceptionPending()); runner(self, shadow_frame.get(), name_string, &result); if (should_succeed) { CHECK(!self->IsExceptionPending()) << name << " " << self->GetException()->Dump(); CHECK(result.GetL() != nullptr) << name; } else { CHECK(self->IsExceptionPending()) << name; if (in_transaction) { ASSERT_TRUE(IsTransactionAborted()); } self->ClearException(); } if (in_transaction) { ExitTransactionMode(); } } } mirror::ClassLoader* GetBootClassLoader() REQUIRES_SHARED(Locks::mutator_lock_) { Thread* self = Thread::Current(); StackHandleScope<2> hs(self); MutableHandle boot_cp = hs.NewHandle(nullptr); { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); // Create the fake boot classloader. Any instance is fine, they are technically interchangeable. Handle boot_cp_class = hs.NewHandle( class_linker->FindClass(self, "Ljava/lang/BootClassLoader;", ScopedNullHandle())); CHECK(boot_cp_class != nullptr); CHECK(class_linker->EnsureInitialized(self, boot_cp_class, true, true)); boot_cp.Assign(boot_cp_class->AllocObject(self)->AsClassLoader()); CHECK(boot_cp != nullptr); ArtMethod* boot_cp_init = boot_cp_class->FindConstructor( "()V", class_linker->GetImagePointerSize()); CHECK(boot_cp_init != nullptr); JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, boot_cp_init, 0); shadow_frame->SetVRegReference(0, boot_cp.Get()); // create instruction data for invoke-direct {v0} of method with fake index uint16_t inst_data[3] = { 0x1070, 0x0000, 0x0010 }; interpreter::DoCall(boot_cp_init, self, *shadow_frame, Instruction::At(inst_data), inst_data[0], &result); CHECK(!self->IsExceptionPending()); } return boot_cp.Get(); } }; TEST_F(UnstartedClassForNameTest, ClassForName) { auto runner = [](Thread* self, ShadowFrame* shadow_frame, ObjPtr name, JValue* result) REQUIRES_SHARED(Locks::mutator_lock_) { shadow_frame->SetVRegReference(0, name); UnstartedClassForName(self, shadow_frame, result, 0); }; RunTest(runner, false, true); } TEST_F(UnstartedClassForNameTest, ClassForNameLong) { auto runner = [](Thread* self, ShadowFrame* shadow_frame, ObjPtr name, JValue* result) REQUIRES_SHARED(Locks::mutator_lock_) { shadow_frame->SetVRegReference(0, name); shadow_frame->SetVReg(1, 0); shadow_frame->SetVRegReference(2, nullptr); UnstartedClassForNameLong(self, shadow_frame, result, 0); }; RunTest(runner, false, true); } TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoader) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); Handle boot_cp = hs.NewHandle(GetBootClassLoader()); auto runner = [&](Thread* th, ShadowFrame* shadow_frame, ObjPtr name, JValue* result) REQUIRES_SHARED(Locks::mutator_lock_) { shadow_frame->SetVRegReference(0, name); shadow_frame->SetVReg(1, 0); shadow_frame->SetVRegReference(2, boot_cp.Get()); UnstartedClassForNameLong(th, shadow_frame, result, 0); }; RunTest(runner, false, true); } TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoaderTransaction) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); Handle boot_cp = hs.NewHandle(GetBootClassLoader()); auto runner = [&](Thread* th, ShadowFrame* shadow_frame, ObjPtr name, JValue* result) REQUIRES_SHARED(Locks::mutator_lock_) { shadow_frame->SetVRegReference(0, name); shadow_frame->SetVReg(1, 0); shadow_frame->SetVRegReference(2, boot_cp.Get()); UnstartedClassForNameLong(th, shadow_frame, result, 0); }; RunTest(runner, true, true); } TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoaderFail) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<2> hs(self); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); jobject path_jobj = class_linker->CreatePathClassLoader(self, {}); ASSERT_TRUE(path_jobj != nullptr); Handle path_cp = hs.NewHandle( self->DecodeJObject(path_jobj)->AsClassLoader()); auto runner = [&](Thread* th, ShadowFrame* shadow_frame, ObjPtr name, JValue* result) REQUIRES_SHARED(Locks::mutator_lock_) { shadow_frame->SetVRegReference(0, name); shadow_frame->SetVReg(1, 0); shadow_frame->SetVRegReference(2, path_cp.Get()); UnstartedClassForNameLong(th, shadow_frame, result, 0); }; RunTest(runner, true, false); } TEST_F(UnstartedRuntimeTest, ClassGetSignatureAnnotation) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); Handle list_class = hs.NewHandle( class_linker->FindClass(self, "Ljava/util/List;", ScopedNullHandle())); ASSERT_TRUE(list_class.Get() != nullptr); ASSERT_TRUE(class_linker->EnsureInitialized(self, list_class, true, true)); JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0); shadow_frame->SetVRegReference(0, list_class.Get()); UnstartedClassGetSignatureAnnotation(self, shadow_frame.get(), &result, 0); ASSERT_TRUE(result.GetL() != nullptr); ASSERT_FALSE(self->IsExceptionPending()); ASSERT_TRUE(result.GetL()->IsObjectArray()); ObjPtr> array = result.GetL()->AsObjectArray(); std::ostringstream oss; for (int32_t i = 0; i != array->GetLength(); ++i) { ObjPtr elem = array->Get(i); ASSERT_TRUE(elem != nullptr); ASSERT_TRUE(elem->IsString()); oss << elem->AsString()->ToModifiedUtf8(); } std::string output_string = oss.str(); ASSERT_EQ(output_string, "Ljava/lang/Object;Ljava/util/Collection;"); } TEST_F(UnstartedRuntimeTest, ConstructorNewInstance0) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<4> hs(self); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); // Get Throwable. Handle throw_class = hs.NewHandle(GetClassRoot()); ASSERT_TRUE(class_linker->EnsureInitialized(self, throw_class, true, true)); // Get an input object. Handle input = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "abd")); // Find the constructor. ArtMethod* throw_cons = throw_class->FindConstructor( "(Ljava/lang/String;)V", class_linker->GetImagePointerSize()); ASSERT_TRUE(throw_cons != nullptr); Handle cons; if (class_linker->GetImagePointerSize() == PointerSize::k64) { cons = hs.NewHandle( mirror::Constructor::CreateFromArtMethod(self, throw_cons)); ASSERT_TRUE(cons != nullptr); } else { cons = hs.NewHandle( mirror::Constructor::CreateFromArtMethod(self, throw_cons)); ASSERT_TRUE(cons != nullptr); } Handle> args = hs.NewHandle( mirror::ObjectArray::Alloc( self, GetClassRoot>(class_linker_), 1)); ASSERT_TRUE(args != nullptr); args->Set(0, input.Get()); // OK, we're ready now. JValue result; UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0); shadow_frame->SetVRegReference(0, cons.Get()); shadow_frame->SetVRegReference(1, args.Get()); UnstartedConstructorNewInstance0(self, shadow_frame.get(), &result, 0); ASSERT_TRUE(result.GetL() != nullptr); ASSERT_FALSE(self->IsExceptionPending()); // Should be a new object. ASSERT_NE(result.GetL(), input.Get()); // Should be of type Throwable. ASSERT_OBJ_PTR_EQ(GetClassRoot(), result.GetL()->GetClass()); // Should have the right string. ObjPtr result_msg = reinterpret_cast(result.GetL())->GetDetailMessage(); EXPECT_OBJ_PTR_EQ(input.Get(), result_msg); } TEST_F(UnstartedRuntimeTest, IdentityHashCode) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0); JValue result; UnstartedSystemIdentityHashCode(self, tmp.get(), &result, 0); EXPECT_EQ(0, result.GetI()); ASSERT_FALSE(self->IsExceptionPending()); ObjPtr str = mirror::String::AllocFromModifiedUtf8(self, "abd"); tmp->SetVRegReference(0, str); UnstartedSystemIdentityHashCode(self, tmp.get(), &result, 0); EXPECT_NE(0, result.GetI()); EXPECT_EQ(str->IdentityHashCode(), result.GetI()); ASSERT_FALSE(self->IsExceptionPending()); } } // namespace interpreter } // namespace art