diff options
-rw-r--r-- | runtime/indirect_reference_table-inl.h | 51 | ||||
-rw-r--r-- | runtime/indirect_reference_table.h | 9 | ||||
-rw-r--r-- | runtime/indirect_reference_table_test.cc | 26 | ||||
-rw-r--r-- | runtime/jni/check_jni.cc | 81 | ||||
-rw-r--r-- | runtime/jni/java_vm_ext.h | 4 | ||||
-rw-r--r-- | runtime/jni/jni_env_ext.h | 3 | ||||
-rw-r--r-- | runtime/jni/jni_internal_test.cc | 43 | ||||
-rw-r--r-- | runtime/thread.cc | 22 |
8 files changed, 134 insertions, 105 deletions
diff --git a/runtime/indirect_reference_table-inl.h b/runtime/indirect_reference_table-inl.h index 2128f8cde8..fb2dc1f879 100644 --- a/runtime/indirect_reference_table-inl.h +++ b/runtime/indirect_reference_table-inl.h @@ -33,36 +33,28 @@ class Object; // Verifies that the indirect table lookup is valid. // Returns "false" if something looks bad. -inline bool IndirectReferenceTable::GetChecked(IndirectRef iref) const { - if (UNLIKELY(iref == nullptr)) { - LOG(WARNING) << "Attempt to look up nullptr " << kind_; - return false; - } - if (UNLIKELY(GetIndirectRefKind(iref) == kHandleScopeOrInvalid)) { - AbortIfNoCheckJNI(android::base::StringPrintf("JNI ERROR (app bug): invalid %s %p", - GetIndirectRefKindString(kind_), - iref)); - return false; - } +inline bool IndirectReferenceTable::IsValidReference(IndirectRef iref, + /*out*/std::string* error_msg) const { + DCHECK(iref != nullptr); + DCHECK_EQ(GetIndirectRefKind(iref), kind_); const uint32_t top_index = segment_state_.top_index; uint32_t idx = ExtractIndex(iref); if (UNLIKELY(idx >= top_index)) { - std::string msg = android::base::StringPrintf( - "JNI ERROR (app bug): accessed stale %s %p (index %d in a table of size %d)", - GetIndirectRefKindString(kind_), - iref, - idx, - top_index); - AbortIfNoCheckJNI(msg); + *error_msg = android::base::StringPrintf("deleted reference at index %u in a table of size %u", + idx, + top_index); return false; } if (UNLIKELY(table_[idx].GetReference()->IsNull())) { - AbortIfNoCheckJNI(android::base::StringPrintf("JNI ERROR (app bug): accessed deleted %s %p", - GetIndirectRefKindString(kind_), - iref)); + *error_msg = android::base::StringPrintf("deleted reference at index %u", idx); return false; } - if (UNLIKELY(!CheckEntry("use", iref, idx))) { + uint32_t iref_serial = DecodeSerial(reinterpret_cast<uintptr_t>(iref)); + uint32_t entry_serial = table_[idx].GetSerial(); + if (UNLIKELY(iref_serial != entry_serial)) { + *error_msg = android::base::StringPrintf("stale reference with serial number %u v. current %u", + iref_serial, + entry_serial); return false; } return true; @@ -88,21 +80,22 @@ inline bool IndirectReferenceTable::CheckEntry(const char* what, template<ReadBarrierOption kReadBarrierOption> inline ObjPtr<mirror::Object> IndirectReferenceTable::Get(IndirectRef iref) const { - if (!GetChecked(iref)) { - return nullptr; - } + DCHECK_EQ(GetIndirectRefKind(iref), kind_); uint32_t idx = ExtractIndex(iref); + DCHECK_LT(idx, segment_state_.top_index); + DCHECK_EQ(DecodeSerial(reinterpret_cast<uintptr_t>(iref)), table_[idx].GetSerial()); + DCHECK(!table_[idx].GetReference()->IsNull()); ObjPtr<mirror::Object> obj = table_[idx].GetReference()->Read<kReadBarrierOption>(); VerifyObject(obj); return obj; } inline void IndirectReferenceTable::Update(IndirectRef iref, ObjPtr<mirror::Object> obj) { - if (!GetChecked(iref)) { - LOG(WARNING) << "IndirectReferenceTable Update failed to find reference " << iref; - return; - } + DCHECK_EQ(GetIndirectRefKind(iref), kind_); uint32_t idx = ExtractIndex(iref); + DCHECK_LT(idx, segment_state_.top_index); + DCHECK_EQ(DecodeSerial(reinterpret_cast<uintptr_t>(iref)), table_[idx].GetSerial()); + DCHECK(!table_[idx].GetReference()->IsNull()); table_[idx].SetReference(obj); } diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h index 86b92cff2d..f877ce8ba7 100644 --- a/runtime/indirect_reference_table.h +++ b/runtime/indirect_reference_table.h @@ -285,6 +285,10 @@ class IndirectReferenceTable { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::alloc_tracker_lock_); + IndirectRefKind GetKind() const { + return kind_; + } + // Return the #of entries in the entire table. This includes holes, and // so may be larger than the actual number of "live" entries. size_t Capacity() const { @@ -331,6 +335,10 @@ class IndirectReferenceTable { return DecodeIndirectRefKind(reinterpret_cast<uintptr_t>(iref)); } + /* Reference validation for CheckJNI. */ + bool IsValidReference(IndirectRef, /*out*/std::string* error_msg) const + REQUIRES_SHARED(Locks::mutator_lock_); + private: static constexpr size_t kSerialBits = MinimumBitsToStore(kIRTPrevCount); static constexpr uint32_t kShiftedSerialMask = (1u << kSerialBits) - 1; @@ -391,7 +399,6 @@ class IndirectReferenceTable { static void AbortIfNoCheckJNI(const std::string& msg); /* extra debugging checks */ - bool GetChecked(IndirectRef) const REQUIRES_SHARED(Locks::mutator_lock_); bool CheckEntry(const char*, IndirectRef, uint32_t) const; /// semi-public - read/write by jni down calls. diff --git a/runtime/indirect_reference_table_test.cc b/runtime/indirect_reference_table_test.cc index c5ae4c63f7..5da7a30ece 100644 --- a/runtime/indirect_reference_table_test.cc +++ b/runtime/indirect_reference_table_test.cc @@ -106,8 +106,9 @@ TEST_F(IndirectReferenceTableTest, BasicTest) { // Table should be empty now. EXPECT_EQ(0U, irt.Capacity()); - // Get invalid entry (off the end of the list). - EXPECT_TRUE(irt.Get(iref0) == nullptr); + // Check that the entry off the end of the list is not valid. + // (CheckJNI shall abort for such entries.) + EXPECT_FALSE(irt.IsValidReference(iref0, &error_msg)); // Add three, remove in the opposite order. iref0 = irt.Add(cookie, obj0.Get(), &error_msg); @@ -145,8 +146,8 @@ TEST_F(IndirectReferenceTableTest, BasicTest) { ASSERT_FALSE(irt.Remove(cookie, iref1)); CheckDump(&irt, 2, 2); - // Get invalid entry (from hole). - EXPECT_TRUE(irt.Get(iref1) == nullptr); + // Check that the reference to the hole is not valid. + EXPECT_FALSE(irt.IsValidReference(iref1, &error_msg)); ASSERT_TRUE(irt.Remove(cookie, iref2)); CheckDump(&irt, 1, 1); @@ -227,15 +228,12 @@ TEST_F(IndirectReferenceTableTest, BasicTest) { ASSERT_EQ(0U, irt.Capacity()) << "temporal del not empty"; CheckDump(&irt, 0, 0); - // null isn't a valid iref. - ASSERT_TRUE(irt.Get(nullptr) == nullptr); - - // Stale lookup. + // Stale reference is not valid. iref0 = irt.Add(cookie, obj0.Get(), &error_msg); EXPECT_TRUE(iref0 != nullptr); CheckDump(&irt, 1, 1); ASSERT_TRUE(irt.Remove(cookie, iref0)); - EXPECT_TRUE(irt.Get(iref0) == nullptr) << "stale lookup succeeded"; + EXPECT_FALSE(irt.IsValidReference(iref0, &error_msg)) << "stale lookup succeeded"; CheckDump(&irt, 0, 0); // Test table resizing. @@ -322,7 +320,7 @@ TEST_F(IndirectReferenceTableTest, Holes) { // Must not have filled the previous hole. EXPECT_EQ(irt.Capacity(), 4u); - EXPECT_TRUE(irt.Get(iref1) == nullptr); + EXPECT_FALSE(irt.IsValidReference(iref1, &error_msg)); CheckDump(&irt, 3, 3); UNUSED(iref0, iref1, iref2, iref3); @@ -357,7 +355,7 @@ TEST_F(IndirectReferenceTableTest, Holes) { IndirectRef iref4 = irt.Add(cookie1, obj4.Get(), &error_msg); EXPECT_EQ(irt.Capacity(), 2u); - EXPECT_TRUE(irt.Get(iref2) == nullptr); + EXPECT_FALSE(irt.IsValidReference(iref2, &error_msg)); CheckDump(&irt, 2, 2); UNUSED(iref0, iref1, iref2, iref3, iref4); @@ -397,7 +395,7 @@ TEST_F(IndirectReferenceTableTest, Holes) { IndirectRef iref4 = irt.Add(cookie1, obj4.Get(), &error_msg); EXPECT_EQ(irt.Capacity(), 3u); - EXPECT_TRUE(irt.Get(iref1) == nullptr); + EXPECT_FALSE(irt.IsValidReference(iref1, &error_msg)); CheckDump(&irt, 3, 3); UNUSED(iref0, iref1, iref2, iref3, iref4); @@ -439,7 +437,7 @@ TEST_F(IndirectReferenceTableTest, Holes) { IndirectRef iref5 = irt.Add(cookie1, obj4.Get(), &error_msg); EXPECT_EQ(irt.Capacity(), 2u); - EXPECT_TRUE(irt.Get(iref3) == nullptr); + EXPECT_FALSE(irt.IsValidReference(iref3, &error_msg)); CheckDump(&irt, 2, 2); UNUSED(iref0, iref1, iref2, iref3, iref4, iref5); @@ -479,7 +477,7 @@ TEST_F(IndirectReferenceTableTest, Holes) { IndirectRef iref4 = irt.Add(cookie1, obj3.Get(), &error_msg); EXPECT_EQ(irt.Capacity(), 2u); - EXPECT_TRUE(irt.Get(iref3) == nullptr); + EXPECT_FALSE(irt.IsValidReference(iref3, &error_msg)); CheckDump(&irt, 2, 2); UNUSED(iref0, iref1, iref2, iref3, iref4); diff --git a/runtime/jni/check_jni.cc b/runtime/jni/check_jni.cc index 1626357709..42e46e9435 100644 --- a/runtime/jni/check_jni.cc +++ b/runtime/jni/check_jni.cc @@ -35,6 +35,7 @@ #include "dex/descriptors_names.h" #include "dex/dex_file-inl.h" #include "gc/space/space.h" +#include "indirect_reference_table-inl.h" #include "java_vm_ext.h" #include "jni_internal.h" #include "mirror/class-inl.h" @@ -50,6 +51,20 @@ #include "well_known_classes.h" namespace art { + +// This helper cannot be in the anonymous namespace because it needs to be +// declared as a friend by JniVmExt and JniEnvExt. +inline IndirectReferenceTable* GetIndirectReferenceTable(ScopedObjectAccess& soa, + IndirectRefKind kind) { + DCHECK_NE(kind, kHandleScopeOrInvalid); + JNIEnvExt* env = soa.Env(); + IndirectReferenceTable* irt = + (kind == kLocal) ? &env->locals_ + : ((kind == kGlobal) ? &env->vm_->globals_ : &env->vm_->weak_globals_); + DCHECK_EQ(irt->GetKind(), kind); + return irt; +} + namespace { using android::base::StringAppendF; @@ -842,26 +857,59 @@ class ScopedCheck { } } - ObjPtr<mirror::Object> obj = soa.Decode<mirror::Object>(java_object); - if (obj == nullptr) { - // Either java_object is invalid or is a cleared weak. - IndirectRef ref = reinterpret_cast<IndirectRef>(java_object); - bool okay; - if (IndirectReferenceTable::GetIndirectRefKind(ref) != kWeakGlobal) { + ObjPtr<mirror::Object> obj = nullptr; + IndirectRef ref = reinterpret_cast<IndirectRef>(java_object); + IndirectRefKind ref_kind = IndirectReferenceTable::GetIndirectRefKind(ref); + bool expect_null = false; + bool okay = true; + std::string error_msg; + if (ref_kind == kHandleScopeOrInvalid) { + if (!soa.Self()->HandleScopeContains(java_object)) { okay = false; + error_msg = "use of invalid jobject"; } else { - obj = soa.Vm()->DecodeWeakGlobal(soa.Self(), ref); - okay = Runtime::Current()->IsClearedJniWeakGlobal(obj); - } - if (!okay) { - AbortF("%s is an invalid %s: %p (%p)", - what, - GetIndirectRefKindString(IndirectReferenceTable::GetIndirectRefKind(java_object)), - java_object, - obj.Ptr()); - return false; + obj = soa.Decode<mirror::Object>(java_object); + } + } else { + IndirectReferenceTable* irt = GetIndirectReferenceTable(soa, ref_kind); + okay = irt->IsValidReference(java_object, &error_msg); + DCHECK_EQ(okay, error_msg.empty()); + if (okay) { + // Note: The `IsValidReference()` checks for null but we do not prevent races, + // so the null check below can still fail. Even if it succeeds, another thread + // could delete the global or weak global before it's used by JNI. + if (ref_kind == kLocal) { + // Local references do not need a read barrier. + obj = irt->Get<kWithoutReadBarrier>(ref); + } else if (ref_kind == kGlobal) { + obj = soa.Env()->GetVm()->DecodeGlobal(ref); + } else { + obj = soa.Env()->GetVm()->DecodeWeakGlobal(soa.Self(), ref); + if (Runtime::Current()->IsClearedJniWeakGlobal(obj)) { + obj = nullptr; + expect_null = true; + } + } } } + if (okay) { + if (!expect_null && obj == nullptr) { + okay = false; + error_msg = "deleted reference"; + } + if (expect_null && !null_ok) { + okay = false; + error_msg = "cleared weak reference"; + } + } + if (!okay) { + AbortF("JNI ERROR (app bug): %s is an invalid %s: %p (%s)", + what, + ToStr<IndirectRefKind>(ref_kind).c_str(), + java_object, + error_msg.c_str()); + return false; + } if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(obj.Ptr())) { Runtime::Current()->GetHeap()->DumpSpaces(LOG_STREAM(ERROR)); @@ -873,7 +921,6 @@ class ScopedCheck { return false; } - bool okay = true; switch (kind) { case kClass: okay = obj->IsClass(); diff --git a/runtime/jni/java_vm_ext.h b/runtime/jni/java_vm_ext.h index 6f7e546de5..015f85cba9 100644 --- a/runtime/jni/java_vm_ext.h +++ b/runtime/jni/java_vm_ext.h @@ -37,6 +37,7 @@ class Libraries; class ParsedOptions; class Runtime; struct RuntimeArgumentMap; +class ScopedObjectAccess; class JavaVMExt; // Hook definition for runtime plugins. @@ -263,6 +264,9 @@ class JavaVMExt : public JavaVM { std::atomic<bool> allocation_tracking_enabled_; std::atomic<bool> old_allocation_tracking_state_; + friend IndirectReferenceTable* GetIndirectReferenceTable(ScopedObjectAccess& soa, + IndirectRefKind kind); + DISALLOW_COPY_AND_ASSIGN(JavaVMExt); }; diff --git a/runtime/jni/jni_env_ext.h b/runtime/jni/jni_env_ext.h index 5d4304d508..2fae8d286d 100644 --- a/runtime/jni/jni_env_ext.h +++ b/runtime/jni/jni_env_ext.h @@ -30,6 +30,7 @@ namespace art { class ArtMethod; class ArtField; class JavaVMExt; +class ScopedObjectAccess; class ScopedObjectAccessAlreadyRunnable; namespace mirror { @@ -212,6 +213,8 @@ class JNIEnvExt : public JNIEnv { template<bool kEnableIndexIds> friend class JNI; friend class ScopedJniEnvLocalRefState; friend class Thread; + friend IndirectReferenceTable* GetIndirectReferenceTable(ScopedObjectAccess& soa, + IndirectRefKind kind); friend void ThreadResetFunctionTable(Thread* thread, void* arg); ART_FRIEND_TEST(JniInternalTest, JNIEnvExtOffsets); }; diff --git a/runtime/jni/jni_internal_test.cc b/runtime/jni/jni_internal_test.cc index a4cfd3a82f..2793857203 100644 --- a/runtime/jni/jni_internal_test.cc +++ b/runtime/jni/jni_internal_test.cc @@ -1996,17 +1996,12 @@ TEST_F(JniInternalTest, DeleteLocalRef) { ASSERT_NE(s, nullptr); env_->DeleteLocalRef(s); - // Currently, deleting an already-deleted reference is just a CheckJNI warning. + // Currently, deleting an already-deleted reference is just a CheckJNI abort. { - bool old_check_jni = vm_->SetCheckJniEnabled(false); - { - CheckJniAbortCatcher check_jni_abort_catcher; - env_->DeleteLocalRef(s); - } + bool old_check_jni = vm_->SetCheckJniEnabled(true); CheckJniAbortCatcher check_jni_abort_catcher; - EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); env_->DeleteLocalRef(s); - std::string expected(StringPrintf("use of deleted local reference %p", s)); + std::string expected = StringPrintf("jobject is an invalid local reference: %p", s); check_jni_abort_catcher.Check(expected.c_str()); EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } @@ -2061,7 +2056,7 @@ TEST_F(JniInternalTest, PushLocalFrame_PopLocalFrame) { { CheckJniAbortCatcher check_jni_abort_catcher; EXPECT_EQ(JNIInvalidRefType, env_->GetObjectRefType(inner1)); - check_jni_abort_catcher.Check("use of deleted local reference"); + check_jni_abort_catcher.Check("jobject is an invalid local reference"); } // Our local reference for the survivor is invalid because the survivor @@ -2069,7 +2064,7 @@ TEST_F(JniInternalTest, PushLocalFrame_PopLocalFrame) { { CheckJniAbortCatcher check_jni_abort_catcher; EXPECT_EQ(JNIInvalidRefType, env_->GetObjectRefType(inner2)); - check_jni_abort_catcher.Check("use of deleted local reference"); + check_jni_abort_catcher.Check("jobject is an invalid local reference"); } EXPECT_EQ(env_->PopLocalFrame(nullptr), nullptr); @@ -2077,11 +2072,11 @@ TEST_F(JniInternalTest, PushLocalFrame_PopLocalFrame) { EXPECT_EQ(JNILocalRefType, env_->GetObjectRefType(original)); CheckJniAbortCatcher check_jni_abort_catcher; EXPECT_EQ(JNIInvalidRefType, env_->GetObjectRefType(outer)); - check_jni_abort_catcher.Check("use of deleted local reference"); + check_jni_abort_catcher.Check("jobject is an invalid local reference"); EXPECT_EQ(JNIInvalidRefType, env_->GetObjectRefType(inner1)); - check_jni_abort_catcher.Check("use of deleted local reference"); + check_jni_abort_catcher.Check("jobject is an invalid local reference"); EXPECT_EQ(JNIInvalidRefType, env_->GetObjectRefType(inner2)); - check_jni_abort_catcher.Check("use of deleted local reference"); + check_jni_abort_catcher.Check("jobject is an invalid local reference"); } TEST_F(JniInternalTest, PushLocalFrame_LimitAndOverflow) { @@ -2135,17 +2130,12 @@ TEST_F(JniInternalTest, DeleteGlobalRef) { ASSERT_NE(o, nullptr); env_->DeleteGlobalRef(o); - // Currently, deleting an already-deleted reference is just a CheckJNI warning. + // Currently, deleting an already-deleted reference is just a CheckJNI abort. { - bool old_check_jni = vm_->SetCheckJniEnabled(false); - { - CheckJniAbortCatcher check_jni_abort_catcher; - env_->DeleteGlobalRef(o); - } + bool old_check_jni = vm_->SetCheckJniEnabled(true); CheckJniAbortCatcher check_jni_abort_catcher; - EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); env_->DeleteGlobalRef(o); - std::string expected(StringPrintf("use of deleted global reference %p", o)); + std::string expected = StringPrintf("jobject is an invalid global reference: %p", o); check_jni_abort_catcher.Check(expected.c_str()); EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } @@ -2188,17 +2178,12 @@ TEST_F(JniInternalTest, DeleteWeakGlobalRef) { ASSERT_NE(o, nullptr); env_->DeleteWeakGlobalRef(o); - // Currently, deleting an already-deleted reference is just a CheckJNI warning. + // Currently, deleting an already-deleted reference is just a CheckJNI abort. { - bool old_check_jni = vm_->SetCheckJniEnabled(false); - { - CheckJniAbortCatcher check_jni_abort_catcher; - env_->DeleteWeakGlobalRef(o); - } + bool old_check_jni = vm_->SetCheckJniEnabled(true); CheckJniAbortCatcher check_jni_abort_catcher; - EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); env_->DeleteWeakGlobalRef(o); - std::string expected(StringPrintf("use of deleted weak global reference %p", o)); + std::string expected(StringPrintf("jobject is an invalid weak global reference: %p", o)); check_jni_abort_catcher.Check(expected.c_str()); EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } diff --git a/runtime/thread.cc b/runtime/thread.cc index 55844109d2..645c51f73b 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -2571,17 +2571,10 @@ ObjPtr<mirror::Object> Thread::DecodeJObject(jobject obj) const { // Local references do not need a read barrier. result = locals.Get<kWithoutReadBarrier>(ref); } else if (kind == kHandleScopeOrInvalid) { - // TODO: make stack indirect reference table lookup more efficient. - // Check if this is a local reference in the handle scope. - if (LIKELY(HandleScopeContains(obj))) { - // Read from handle scope. - result = reinterpret_cast<StackReference<mirror::Object>*>(obj)->AsMirrorPtr(); - VerifyObject(result); - } else { - tlsPtr_.jni_env->vm_->JniAbortF(nullptr, "use of invalid jobject %p", obj); - expect_null = true; - result = nullptr; - } + // Read from handle scope. + DCHECK(HandleScopeContains(obj)); + result = reinterpret_cast<StackReference<mirror::Object>*>(obj)->AsMirrorPtr(); + VerifyObject(result); } else if (kind == kGlobal) { result = tlsPtr_.jni_env->vm_->DecodeGlobal(ref); } else { @@ -2594,10 +2587,9 @@ ObjPtr<mirror::Object> Thread::DecodeJObject(jobject obj) const { } } - if (UNLIKELY(!expect_null && result == nullptr)) { - tlsPtr_.jni_env->vm_->JniAbortF(nullptr, "use of deleted %s %p", - ToStr<IndirectRefKind>(kind).c_str(), obj); - } + DCHECK(expect_null || result != nullptr) + << "use of deleted " << ToStr<IndirectRefKind>(kind).c_str() + << " " << static_cast<const void*>(obj); return result; } |