Add ability to switch to index ids late.
In order to support some debugging features we need to have JNI code
mostly running with jmethodIDs and jfieldIDs as opaque indexes
disconnected from any underlying art data-structure pointers. For
performance though we want to continue to use data-structure pointers
as these IDs when debugging is not needed. To support both
possibilities this adds support for changing from a 'swapablePointer'
to either 'pointer' or 'indicies' regime at runtime.
The SwapablePointer regime still uses pointers as the ids but creates
the data-structures needed to ensure that we can (1) detect that the
methods have pointer-type IDs and (2) we can change some
WellKnownClass internal IDs from one to the other.
Currently one must select this mode explicitly using
'-Xopaque-jni-ids:swapable'. Depending on when the final jni-id-type
is selected a small amount of extra memory is used. Currently manual
testing of turning the default id-type to swapable and changing to
pointer immediately after zygote-fork shows a additional 40-90 kb of
shared zygote-heap memory.
Test: ./test.py --host
Test: m libfieldcounts; \
./tools/jvmti-agents/field-counts/count-fields.py 'Ljava/lang/Class;.extData:Ldalvik/system/ClassExt;' 'Ldalvik/system/ClassExt;.jmethodIDs:Ljava/lang/Object;' 'Ldalvik/system/ClassExt;.staticJfieldIDs:Ljava/lang/Object;' 'Ldalvik/system/ClassExt;.instanceJfieldIDs:Ljava/lang/Object;' -p `adb shell pidof com.android.deskclock`;
Examine output
Bug: 134162467
Change-Id: I1885b10056d5dcc65dad5ae4f858ddc12ba79403
diff --git a/runtime/jni/jni_env_ext.cc b/runtime/jni/jni_env_ext.cc
index 411794c..e2581dd 100644
--- a/runtime/jni/jni_env_ext.cc
+++ b/runtime/jni/jni_env_ext.cc
@@ -291,11 +291,12 @@
}
}
-static void ThreadResetFunctionTable(Thread* thread, void* arg ATTRIBUTE_UNUSED)
+void ThreadResetFunctionTable(Thread* thread, void* arg ATTRIBUTE_UNUSED)
REQUIRES(Locks::jni_function_table_lock_) {
JNIEnvExt* env = thread->GetJniEnv();
bool check_jni = env->IsCheckJniEnabled();
env->functions = JNIEnvExt::GetFunctionTable(check_jni);
+ env->unchecked_functions_ = GetJniNativeInterface();
}
void JNIEnvExt::SetTableOverride(const JNINativeInterface* table_override) {
@@ -323,4 +324,12 @@
return check_jni ? GetCheckJniNativeInterface() : GetJniNativeInterface();
}
+void JNIEnvExt::ResetFunctionTable() {
+ MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+ MutexLock mu2(Thread::Current(), *Locks::jni_function_table_lock_);
+ Runtime* runtime = Runtime::Current();
+ CHECK(runtime != nullptr);
+ runtime->GetThreadList()->ForEach(ThreadResetFunctionTable, nullptr);
+}
+
} // namespace art
diff --git a/runtime/jni/jni_env_ext.h b/runtime/jni/jni_env_ext.h
index 924ff51..7aae92f 100644
--- a/runtime/jni/jni_env_ext.h
+++ b/runtime/jni/jni_env_ext.h
@@ -27,7 +27,10 @@
namespace art {
+class ArtMethod;
+class ArtField;
class JavaVMExt;
+class ScopedObjectAccessAlreadyRunnable;
namespace mirror {
class Object;
@@ -131,6 +134,9 @@
// Set the functions to the runtime shutdown functions.
void SetFunctionsToRuntimeShutdownFunctions();
+ // Set the functions to the new JNI functions based on Runtime::GetJniIdType.
+ void UpdateJniFunctionsPointer();
+
// Set the function table override. This will install the override (or original table, if null)
// to all threads.
// Note: JNI function table overrides are sensitive to the order of operations wrt/ CheckJNI.
@@ -143,6 +149,9 @@
static const JNINativeInterface* GetFunctionTable(bool check_jni)
REQUIRES(Locks::jni_function_table_lock_);
+ static void ResetFunctionTable()
+ REQUIRES(!Locks::thread_list_lock_, !Locks::jni_function_table_lock_);
+
private:
// Checking "locals" requires the mutator lock, but at creation time we're
// really only interested in validity, which isn't changing. To avoid grabbing
@@ -179,7 +188,7 @@
ReferenceTable monitors_;
// Used by -Xcheck:jni.
- const JNINativeInterface* unchecked_functions_;
+ JNINativeInterface const* unchecked_functions_;
// All locked objects, with the (Java caller) stack frame that locked them. Used in CheckJNI
// to ensure that only monitors locked in this native frame are being unlocked, and that at
@@ -202,6 +211,7 @@
template<bool kEnableIndexIds> friend class JNI;
friend class ScopedJniEnvLocalRefState;
friend class Thread;
+ friend void ThreadResetFunctionTable(Thread* thread, void* arg);
ART_FRIEND_TEST(JniInternalTest, JNIEnvExtOffsets);
};
diff --git a/runtime/jni/jni_id_manager.cc b/runtime/jni/jni_id_manager.cc
index c28d813..88c6ba1 100644
--- a/runtime/jni/jni_id_manager.cc
+++ b/runtime/jni/jni_id_manager.cc
@@ -26,6 +26,7 @@
#include "gc/allocation_listener.h"
#include "gc/heap.h"
#include "jni/jni_internal.h"
+#include "jni_id_type.h"
#include "mirror/array-inl.h"
#include "mirror/class-inl.h"
#include "mirror/class.h"
@@ -166,18 +167,28 @@
// We increment the id by 2 each time to allow us to use the LSB as a flag that the ID is an index
// and not a pointer. This gives us 2**31 unique methods that can be addressed on 32-bit art, which
// should be more than enough.
-template <> uintptr_t JniIdManager::GetNextId<ArtField>() {
- uintptr_t res = next_field_id_;
- next_field_id_ += 2;
- CHECK_GT(next_field_id_, res) << "jfieldID Overflow";
- return res;
+template <> uintptr_t JniIdManager::GetNextId<ArtField>(JniIdType type, ArtField* f) {
+ if (LIKELY(type == JniIdType::kIndices)) {
+ uintptr_t res = next_field_id_;
+ next_field_id_ += 2;
+ CHECK_GT(next_field_id_, res) << "jfieldID Overflow";
+ return res;
+ } else {
+ DCHECK_EQ(type, JniIdType::kSwapablePointer);
+ return reinterpret_cast<uintptr_t>(f);
+ }
}
-template <> uintptr_t JniIdManager::GetNextId<ArtMethod>() {
- uintptr_t res = next_method_id_;
- next_method_id_ += 2;
- CHECK_GT(next_method_id_, res) << "jmethodID Overflow";
- return res;
+template <> uintptr_t JniIdManager::GetNextId<ArtMethod>(JniIdType type, ArtMethod* m) {
+ if (LIKELY(type == JniIdType::kIndices)) {
+ uintptr_t res = next_method_id_;
+ next_method_id_ += 2;
+ CHECK_GT(next_method_id_, res) << "jmethodID Overflow";
+ return res;
+ } else {
+ DCHECK_EQ(type, JniIdType::kSwapablePointer);
+ return reinterpret_cast<uintptr_t>(m);
+ }
}
template <> std::vector<ArtField*>& JniIdManager::GetGenericMap<ArtField>() {
return field_id_map_;
@@ -199,7 +210,9 @@
}
template <typename ArtType> uintptr_t JniIdManager::EncodeGenericId(ArtType* t) {
- if (!Runtime::Current()->JniIdsAreIndices() || t == nullptr) {
+ Runtime* runtime = Runtime::Current();
+ JniIdType id_type = runtime->GetJniIdType();
+ if (id_type == JniIdType::kPointer || t == nullptr) {
return reinterpret_cast<uintptr_t>(t);
}
Thread* self = Thread::Current();
@@ -257,12 +270,18 @@
return IndexToId(index);
}
}
- cur_id = GetNextId<ArtType>();
- size_t cur_index = IdToIndex(cur_id);
- std::vector<ArtType*>& vec = GetGenericMap<ArtType>();
- vec.reserve(cur_index + 1);
- vec.resize(std::max(vec.size(), cur_index + 1), nullptr);
- vec[cur_index] = t;
+ cur_id = GetNextId(id_type, t);
+ if (UNLIKELY(id_type == JniIdType::kIndices)) {
+ DCHECK_EQ(cur_id % 2, 1u);
+ size_t cur_index = IdToIndex(cur_id);
+ std::vector<ArtType*>& vec = GetGenericMap<ArtType>();
+ vec.reserve(cur_index + 1);
+ vec.resize(std::max(vec.size(), cur_index + 1), nullptr);
+ vec[cur_index] = t;
+ } else {
+ DCHECK_EQ(cur_id % 2, 0u);
+ DCHECK_EQ(cur_id, reinterpret_cast<uintptr_t>(t));
+ }
if (ids.IsNull()) {
if (kIsDebugBuild && !IsObsolete(t)) {
CHECK_NE(deferred_allocation_refcount_, 0u)
@@ -291,7 +310,7 @@
}
template <typename ArtType> ArtType* JniIdManager::DecodeGenericId(uintptr_t t) {
- if (Runtime::Current()->JniIdsAreIndices() && (t % 2) == 1) {
+ if (Runtime::Current()->GetJniIdType() == JniIdType::kIndices && (t % 2) == 1) {
ReaderMutexLock mu(Thread::Current(), *Locks::jni_id_lock_);
size_t index = IdToIndex(t);
DCHECK_GT(GetGenericMap<ArtType>().size(), index);
diff --git a/runtime/jni/jni_id_manager.h b/runtime/jni/jni_id_manager.h
index 5e5c581..d294815 100644
--- a/runtime/jni/jni_id_manager.h
+++ b/runtime/jni/jni_id_manager.h
@@ -24,6 +24,7 @@
#include "art_field.h"
#include "art_method.h"
#include "base/mutex.h"
+#include "jni_id_type.h"
namespace art {
namespace jni {
@@ -45,7 +46,8 @@
template <typename ArtType>
ArtType* DecodeGenericId(uintptr_t input) REQUIRES(!Locks::jni_id_lock_);
template <typename ArtType> std::vector<ArtType*>& GetGenericMap() REQUIRES(Locks::jni_id_lock_);
- template <typename ArtType> uintptr_t GetNextId() REQUIRES(Locks::jni_id_lock_);
+ template <typename ArtType>
+ uintptr_t GetNextId(JniIdType id, ArtType* t) REQUIRES(Locks::jni_id_lock_);
template <typename ArtType>
size_t GetLinearSearchStartId(ArtType* t) REQUIRES(Locks::jni_id_lock_);
diff --git a/runtime/jni/jni_internal.cc b/runtime/jni/jni_internal.cc
index 3d38881..882e10f 100644
--- a/runtime/jni/jni_internal.cc
+++ b/runtime/jni/jni_internal.cc
@@ -206,21 +206,6 @@
return result;
}
-static void ThrowNoSuchMethodError(ScopedObjectAccess& soa,
- ObjPtr<mirror::Class> c,
- const char* name,
- const char* sig,
- const char* kind)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::string temp;
- soa.Self()->ThrowNewExceptionF("Ljava/lang/NoSuchMethodError;",
- "no %s method \"%s.%s%s\"",
- kind,
- c->GetDescriptor(&temp),
- name,
- sig);
-}
-
static void ReportInvalidJNINativeMethod(const ScopedObjectAccess& soa,
ObjPtr<mirror::Class> c,
const char* kind,
@@ -236,42 +221,11 @@
idx);
}
-static ObjPtr<mirror::Class> EnsureInitialized(Thread* self, ObjPtr<mirror::Class> klass)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (LIKELY(klass->IsInitialized())) {
- return klass;
- }
- StackHandleScope<1> hs(self);
- Handle<mirror::Class> h_klass(hs.NewHandle(klass));
- if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h_klass, true, true)) {
- return nullptr;
- }
- return h_klass.Get();
-}
-
template<bool kEnableIndexIds>
static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class,
const char* name, const char* sig, bool is_static)
REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class));
- if (c == nullptr) {
- return nullptr;
- }
- ArtMethod* method = nullptr;
- auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
- if (c->IsInterface()) {
- method = c->FindInterfaceMethod(name, sig, pointer_size);
- } else {
- method = c->FindClassMethod(name, sig, pointer_size);
- }
- if (method != nullptr && ShouldDenyAccessToMember(method, soa.Self())) {
- method = nullptr;
- }
- if (method == nullptr || method->IsStatic() != is_static) {
- ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");
- return nullptr;
- }
- return jni::EncodeArtMethod<kEnableIndexIds>(method);
+ return jni::EncodeArtMethod<kEnableIndexIds>(FindMethodJNI(soa, jni_class, name, sig, is_static));
}
template<bool kEnableIndexIds>
@@ -310,6 +264,88 @@
static jfieldID FindFieldID(const ScopedObjectAccess& soa, jclass jni_class, const char* name,
const char* sig, bool is_static)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ return jni::EncodeArtField<kEnableIndexIds>(FindFieldJNI(soa, jni_class, name, sig, is_static));
+}
+
+static void ThrowAIOOBE(ScopedObjectAccess& soa,
+ ObjPtr<mirror::Array> array,
+ jsize start,
+ jsize length,
+ const char* identifier)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ std::string type(array->PrettyTypeOf());
+ soa.Self()->ThrowNewExceptionF("Ljava/lang/ArrayIndexOutOfBoundsException;",
+ "%s offset=%d length=%d %s.length=%d",
+ type.c_str(), start, length, identifier, array->GetLength());
+}
+
+static void ThrowSIOOBE(ScopedObjectAccess& soa, jsize start, jsize length,
+ jsize array_length)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ soa.Self()->ThrowNewExceptionF("Ljava/lang/StringIndexOutOfBoundsException;",
+ "offset=%d length=%d string.length()=%d", start, length,
+ array_length);
+}
+
+static void ThrowNoSuchMethodError(const ScopedObjectAccess& soa,
+ ObjPtr<mirror::Class> c,
+ const char* name,
+ const char* sig,
+ const char* kind)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ std::string temp;
+ soa.Self()->ThrowNewExceptionF("Ljava/lang/NoSuchMethodError;",
+ "no %s method \"%s.%s%s\"",
+ kind,
+ c->GetDescriptor(&temp),
+ name,
+ sig);
+}
+
+static ObjPtr<mirror::Class> EnsureInitialized(Thread* self, ObjPtr<mirror::Class> klass)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (LIKELY(klass->IsInitialized())) {
+ return klass;
+ }
+ StackHandleScope<1> hs(self);
+ Handle<mirror::Class> h_klass(hs.NewHandle(klass));
+ if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h_klass, true, true)) {
+ return nullptr;
+ }
+ return h_klass.Get();
+}
+
+ArtMethod* FindMethodJNI(const ScopedObjectAccess& soa,
+ jclass jni_class,
+ const char* name,
+ const char* sig,
+ bool is_static) {
+ ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class));
+ if (c == nullptr) {
+ return nullptr;
+ }
+ ArtMethod* method = nullptr;
+ auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+ if (c->IsInterface()) {
+ method = c->FindInterfaceMethod(name, sig, pointer_size);
+ } else {
+ method = c->FindClassMethod(name, sig, pointer_size);
+ }
+ if (method != nullptr && ShouldDenyAccessToMember(method, soa.Self())) {
+ method = nullptr;
+ }
+ if (method == nullptr || method->IsStatic() != is_static) {
+ ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");
+ return nullptr;
+ }
+ return method;
+}
+
+ArtField* FindFieldJNI(const ScopedObjectAccess& soa,
+ jclass jni_class,
+ const char* name,
+ const char* sig,
+ bool is_static) {
StackHandleScope<2> hs(soa.Self());
Handle<mirror::Class> c(
hs.NewHandle(EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class))));
@@ -359,27 +395,7 @@
sig, name, c->GetDescriptor(&temp));
return nullptr;
}
- return jni::EncodeArtField<kEnableIndexIds>(field);
-}
-
-static void ThrowAIOOBE(ScopedObjectAccess& soa,
- ObjPtr<mirror::Array> array,
- jsize start,
- jsize length,
- const char* identifier)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::string type(array->PrettyTypeOf());
- soa.Self()->ThrowNewExceptionF("Ljava/lang/ArrayIndexOutOfBoundsException;",
- "%s offset=%d length=%d %s.length=%d",
- type.c_str(), start, length, identifier, array->GetLength());
-}
-
-static void ThrowSIOOBE(ScopedObjectAccess& soa, jsize start, jsize length,
- jsize array_length)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- soa.Self()->ThrowNewExceptionF("Ljava/lang/StringIndexOutOfBoundsException;",
- "offset=%d length=%d string.length()=%d", start, length,
- array_length);
+ return field;
}
int ThrowNewException(JNIEnv* env, jclass exception_class, const char* msg, jobject cause)
@@ -2953,11 +2969,11 @@
const JNINativeInterface* GetJniNativeInterface() {
// The template argument is passed down through the Encode/DecodeArtMethod/Field calls so if
- // JniIdsAreIndices is false the calls will be a simple cast with no branches. This ensures that
+ // JniIdType is kPointer the calls will be a simple cast with no branches. This ensures that
// the normal case is still fast.
- return Runtime::Current()->JniIdsAreIndices()
- ? &JniNativeInterfaceFunctions<true>::gJniNativeInterface
- : &JniNativeInterfaceFunctions<false>::gJniNativeInterface;
+ return Runtime::Current()->GetJniIdType() == JniIdType::kPointer
+ ? &JniNativeInterfaceFunctions<false>::gJniNativeInterface
+ : &JniNativeInterfaceFunctions<true>::gJniNativeInterface;
}
void (*gJniSleepForeverStub[])() = {
diff --git a/runtime/jni/jni_internal.h b/runtime/jni/jni_internal.h
index b6e106c..da17922 100644
--- a/runtime/jni/jni_internal.h
+++ b/runtime/jni/jni_internal.h
@@ -28,6 +28,7 @@
class ArtField;
class ArtMethod;
+class ScopedObjectAccess;
const JNINativeInterface* GetJniNativeInterface();
const JNINativeInterface* GetRuntimeShutdownNativeInterface();
@@ -41,6 +42,22 @@
// Removes native stack checking state.
void JniShutdownNativeCallerCheck();
+// Finds the method using JNI semantics and initializes any classes. Does not encode the method in a
+// JNI id
+ArtMethod* FindMethodJNI(const ScopedObjectAccess& soa,
+ jclass java_class,
+ const char* name,
+ const char* sig,
+ bool is_static) REQUIRES_SHARED(Locks::mutator_lock_);
+
+// Finds the field using JNI semantics and initializes any classes. Does not encode the method in a
+// JNI id.
+ArtField* FindFieldJNI(const ScopedObjectAccess& soa,
+ jclass java_class,
+ const char* name,
+ const char* sig,
+ bool is_static) REQUIRES_SHARED(Locks::mutator_lock_);
+
namespace jni {
// We want to maintain a branchless fast-path for performance reasons. The JniIdManager is the
@@ -72,7 +89,7 @@
template <bool kEnableIndexIds = true>
ALWAYS_INLINE
static inline jfieldID EncodeArtField(ArtField* field) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (kEnableIndexIds && Runtime::Current()->JniIdsAreIndices()) {
+ if (kEnableIndexIds && Runtime::Current()->GetJniIdType() != JniIdType::kPointer) {
return Runtime::Current()->GetJniIdManager()->EncodeFieldId(field);
} else {
return reinterpret_cast<jfieldID>(field);
@@ -83,7 +100,7 @@
ALWAYS_INLINE
static inline jmethodID EncodeArtMethod(ArtMethod* art_method)
REQUIRES_SHARED(Locks::mutator_lock_) {
- if (kEnableIndexIds && Runtime::Current()->JniIdsAreIndices()) {
+ if (kEnableIndexIds && Runtime::Current()->GetJniIdType() != JniIdType::kPointer) {
return Runtime::Current()->GetJniIdManager()->EncodeMethodId(art_method);
} else {
return reinterpret_cast<jmethodID>(art_method);
diff --git a/runtime/jni_id_type.h b/runtime/jni_id_type.h
index 7802ec6..3f952b6 100644
--- a/runtime/jni_id_type.h
+++ b/runtime/jni_id_type.h
@@ -28,7 +28,10 @@
// All Jni method/field IDs are indices into a table.
kIndices,
- // The current default provider. Used if you run -XjdwpProvider:default
+ // All Jni method/field IDs are pointers to the corresponding Art{Field,Method} type but we
+ // keep around extra information support changing modes to either kPointer or kIndices later.
+ kSwapablePointer,
+
kDefault = kPointer,
};
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 846517b..a299f34 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -39,6 +39,7 @@
#include "gc/heap-inl.h"
#include "handle_scope-inl.h"
#include "hidden_api.h"
+#include "jni_id_type.h"
#include "subtype_check.h"
#include "method.h"
#include "object-inl.h"
@@ -1595,7 +1596,7 @@
}
}
ObjPtr<PointerArray> Class::GetOrCreateMethodIds(Handle<Class> h_this) {
- DCHECK(Runtime::Current()->JniIdsAreIndices()) << "JNI Ids are pointers!";
+ DCHECK_NE(Runtime::Current()->GetJniIdType(), JniIdType::kPointer) << "JNI Ids are pointers!";
Thread* self = Thread::Current();
ObjPtr<ClassExt> ext(EnsureExtDataPresent(h_this, self));
if (ext.IsNull()) {
@@ -1614,7 +1615,7 @@
}
}
ObjPtr<PointerArray> Class::GetOrCreateStaticFieldIds(Handle<Class> h_this) {
- DCHECK(Runtime::Current()->JniIdsAreIndices()) << "JNI Ids are pointers!";
+ DCHECK_NE(Runtime::Current()->GetJniIdType(), JniIdType::kPointer) << "JNI Ids are pointers!";
Thread* self = Thread::Current();
ObjPtr<ClassExt> ext(EnsureExtDataPresent(h_this, self));
if (ext.IsNull()) {
@@ -1632,7 +1633,7 @@
}
}
ObjPtr<PointerArray> Class::GetOrCreateInstanceFieldIds(Handle<Class> h_this) {
- DCHECK(Runtime::Current()->JniIdsAreIndices()) << "JNI Ids are pointers!";
+ DCHECK_NE(Runtime::Current()->GetJniIdType(), JniIdType::kPointer) << "JNI Ids are pointers!";
Thread* self = Thread::Current();
ObjPtr<ClassExt> ext(EnsureExtDataPresent(h_this, self));
if (ext.IsNull()) {
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index fb1e6b7..bfedfa9 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -27,6 +27,7 @@
#include "base/utils.h"
#include "debugger.h"
#include "gc/heap.h"
+#include "jni_id_type.h"
#include "monitor.h"
#include "runtime.h"
#include "ti/agent.h"
@@ -368,8 +369,13 @@
.WithValueMap({{"false", false}, {"true", true}})
.IntoKey(M::FastClassNotFoundException)
.Define("-Xopaque-jni-ids:_")
- .WithType<bool>()
- .WithValueMap({{"true", true}, {"false", false}})
+ .WithType<JniIdType>()
+ .WithValueMap({{"true", JniIdType::kIndices},
+ {"false", JniIdType::kPointer},
+ {"swapable", JniIdType::kSwapablePointer},
+ {"pointer", JniIdType::kPointer},
+ {"indices", JniIdType::kIndices},
+ {"default", JniIdType::kDefault}})
.IntoKey(M::OpaqueJniIds)
.Define("-XX:VerifierMissingKThrowFatal=_")
.WithType<bool>()
@@ -792,7 +798,8 @@
"(Enable new and experimental agent support)\n");
UsageMessage(stream, " -Xexperimental:agents"
"(Enable new and experimental agent support)\n");
- UsageMessage(stream, " -Xopaque-jni-ids:{true,false} (Use opauque integers for jni ids)\n");
+ UsageMessage(stream, " -Xopaque-jni-ids:{true,false,swapable}");
+ UsageMessage(stream, "(Use opauque integers for jni ids, yes, no or punt for later)\n");
UsageMessage(stream, "\n");
UsageMessage(stream, "The following previously supported Dalvik options are ignored:\n");
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 534d69d..1b12f6a 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1306,12 +1306,7 @@
is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode);
madvise_random_access_ = runtime_options.GetOrDefault(Opt::MadviseRandomAccess);
- if (!runtime_options.Exists(Opt::OpaqueJniIds)) {
- jni_ids_indirection_ = JniIdType::kDefault;
- } else {
- jni_ids_indirection_ = *runtime_options.Get(Opt::OpaqueJniIds) ? JniIdType::kIndices
- : JniIdType::kPointer;
- }
+ jni_ids_indirection_ = runtime_options.GetOrDefault(Opt::OpaqueJniIds);
plugins_ = runtime_options.ReleaseOrDefault(Opt::Plugins);
agent_specs_ = runtime_options.ReleaseOrDefault(Opt::AgentPath);
@@ -2927,4 +2922,14 @@
SkipAddSignalHandler(value);
}
+void Runtime::SetJniIdType(JniIdType t) {
+ CHECK(CanSetJniIdType()) << "Not allowed to change id type!";
+ if (t == GetJniIdType()) {
+ return;
+ }
+ jni_ids_indirection_ = t;
+ JNIEnvExt::ResetFunctionTable();
+ WellKnownClasses::HandleJniIdTypeChange(Thread::Current()->GetJniEnv());
+}
+
} // namespace art
diff --git a/runtime/runtime.h b/runtime/runtime.h
index a276b87..ca01761 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -841,10 +841,18 @@
return jdwp_provider_;
}
- bool JniIdsAreIndices() const {
- return jni_ids_indirection_ != JniIdType::kPointer;
+ JniIdType GetJniIdType() const {
+ return jni_ids_indirection_;
}
+ bool CanSetJniIdType() const {
+ return GetJniIdType() == JniIdType::kSwapablePointer;
+ }
+
+ // Changes the JniIdType to the given type. Only allowed if CanSetJniIdType(). All threads must be
+ // suspended to call this function.
+ void SetJniIdType(JniIdType t);
+
uint32_t GetVerifierLoggingThresholdMs() const {
return verifier_logging_threshold_ms_;
}
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index c384eda..e9d1511 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -77,7 +77,7 @@
RUNTIME_OPTIONS_KEY (bool, UseJitCompilation, true)
RUNTIME_OPTIONS_KEY (bool, DumpNativeStackOnSigQuit, true)
RUNTIME_OPTIONS_KEY (bool, MadviseRandomAccess, false)
-RUNTIME_OPTIONS_KEY (bool, OpaqueJniIds, false) // -Xopaque-jni-ids:{true, false}
+RUNTIME_OPTIONS_KEY (JniIdType, OpaqueJniIds, JniIdType::kDefault) // -Xopaque-jni-ids:{true, false}
RUNTIME_OPTIONS_KEY (unsigned int, JITCompileThreshold)
RUNTIME_OPTIONS_KEY (unsigned int, JITWarmupThreshold)
RUNTIME_OPTIONS_KEY (unsigned int, JITOsrThreshold)
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 279b45b..002bacb 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -28,13 +28,16 @@
#include "entrypoints/quick/quick_entrypoints_enum.h"
#include "entrypoints/runtime_asm_entrypoints.h"
#include "hidden_api.h"
+#include "hidden_api_jni.h"
#include "jni/jni_internal.h"
+#include "jni_id_type.h"
#include "mirror/class.h"
#include "mirror/throwable.h"
#include "nativehelper/scoped_local_ref.h"
#include "obj_ptr-inl.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
+#include "scoped_thread_state_change.h"
#include "thread-current-inl.h"
namespace art {
@@ -172,8 +175,18 @@
static jfieldID CacheField(JNIEnv* env, jclass c, bool is_static,
const char* name, const char* signature) {
- jfieldID fid = is_static ? env->GetStaticFieldID(c, name, signature) :
- env->GetFieldID(c, name, signature);
+ jfieldID fid;
+ {
+ ScopedObjectAccess soa(env);
+ hiddenapi::ScopedCorePlatformApiCheck scpac;
+ if (Runtime::Current()->GetJniIdType() != JniIdType::kSwapablePointer) {
+ fid = jni::EncodeArtField</*kEnableIndexIds*/ true>(
+ FindFieldJNI(soa, c, name, signature, is_static));
+ } else {
+ fid = jni::EncodeArtField</*kEnableIndexIds*/ false>(
+ FindFieldJNI(soa, c, name, signature, is_static));
+ }
+ }
if (fid == nullptr) {
ScopedObjectAccess soa(env);
if (soa.Self()->IsExceptionPending()) {
@@ -189,8 +202,18 @@
static jmethodID CacheMethod(JNIEnv* env, jclass c, bool is_static,
const char* name, const char* signature) {
- jmethodID mid = is_static ? env->GetStaticMethodID(c, name, signature) :
- env->GetMethodID(c, name, signature);
+ jmethodID mid;
+ {
+ ScopedObjectAccess soa(env);
+ hiddenapi::ScopedCorePlatformApiCheck scpac;
+ if (Runtime::Current()->GetJniIdType() != JniIdType::kSwapablePointer) {
+ mid = jni::EncodeArtMethod</*kEnableIndexIds*/ true>(
+ FindMethodJNI(soa, c, name, signature, is_static));
+ } else {
+ mid = jni::EncodeArtMethod</*kEnableIndexIds*/ false>(
+ FindMethodJNI(soa, c, name, signature, is_static));
+ }
+ }
if (mid == nullptr) {
ScopedObjectAccess soa(env);
if (soa.Self()->IsExceptionPending()) {
@@ -348,6 +371,13 @@
org_apache_harmony_dalvik_ddmc_Chunk = CacheClass(env, "org/apache/harmony/dalvik/ddmc/Chunk");
org_apache_harmony_dalvik_ddmc_DdmServer = CacheClass(env, "org/apache/harmony/dalvik/ddmc/DdmServer");
+ InitFieldsAndMethodsOnly(env);
+}
+
+void WellKnownClasses::InitFieldsAndMethodsOnly(JNIEnv* env) {
+ hiddenapi::ScopedHiddenApiEnforcementPolicySetting hiddenapi_exemption(
+ hiddenapi::EnforcementPolicy::kDisabled);
+
dalvik_system_BaseDexClassLoader_getLdLibraryPath = CacheMethod(env, dalvik_system_BaseDexClassLoader, false, "getLdLibraryPath", "()Ljava/lang/String;");
dalvik_system_VMRuntime_runFinalization = CacheMethod(env, dalvik_system_VMRuntime, true, "runFinalization", "(J)V");
dalvik_system_VMRuntime_hiddenApiUsed = CacheMethod(env, dalvik_system_VMRuntime, true, "hiddenApiUsed", "(ILjava/lang/String;Ljava/lang/String;IZ)V");
@@ -452,6 +482,11 @@
"[Ljava/lang/Object;)Ljava/lang/Object;");
}
+void WellKnownClasses::HandleJniIdTypeChange(JNIEnv* env) {
+ WellKnownClasses::InitFieldsAndMethodsOnly(env);
+ WellKnownClasses::LateInit(env);
+}
+
void WellKnownClasses::Clear() {
dalvik_annotation_optimization_CriticalNative = nullptr;
dalvik_annotation_optimization_FastNative = nullptr;
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index d7acd57..249dfa0 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -35,11 +35,15 @@
struct WellKnownClasses {
public:
- static void Init(JNIEnv* env); // Run before native methods are registered.
- static void LateInit(JNIEnv* env); // Run after native methods are registered.
+ // Run before native methods are registered.
+ static void Init(JNIEnv* env);
+ // Run after native methods are registered.
+ static void LateInit(JNIEnv* env);
static void Clear();
+ static void HandleJniIdTypeChange(JNIEnv* env);
+
static void InitStringInit(ObjPtr<mirror::Class> string_class,
ObjPtr<mirror::Class> string_builder_class)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -48,6 +52,10 @@
static ObjPtr<mirror::Class> ToClass(jclass global_jclass) REQUIRES_SHARED(Locks::mutator_lock_);
+ private:
+ static void InitFieldsAndMethodsOnly(JNIEnv* env);
+
+ public:
static jclass dalvik_annotation_optimization_CriticalNative;
static jclass dalvik_annotation_optimization_FastNative;
static jclass dalvik_system_BaseDexClassLoader;
diff --git a/test/1972-jni-id-swap-indices/expected.txt b/test/1972-jni-id-swap-indices/expected.txt
new file mode 100644
index 0000000..a22979f
--- /dev/null
+++ b/test/1972-jni-id-swap-indices/expected.txt
@@ -0,0 +1,7 @@
+JNI_OnLoad called
+JNI Type is: SwapablePointer
+pointer ID looks like a pointer!
+JNI Type is: Indices
+index ID looks like an index!
+pointer ID remains a pointer!
+index WKC ID looks like an index!
diff --git a/test/1972-jni-id-swap-indices/info.txt b/test/1972-jni-id-swap-indices/info.txt
new file mode 100644
index 0000000..8b9c215
--- /dev/null
+++ b/test/1972-jni-id-swap-indices/info.txt
@@ -0,0 +1,3 @@
+Tests changing from SwapablePointer to indices for JniIdType
+
+Also tests that WellKnownClasses jmethodIDs are indices after swap.
\ No newline at end of file
diff --git a/test/1972-jni-id-swap-indices/jni_id.cc b/test/1972-jni-id-swap-indices/jni_id.cc
new file mode 100644
index 0000000..7de7131
--- /dev/null
+++ b/test/1972-jni-id-swap-indices/jni_id.cc
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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 <jni.h>
+#include <sstream>
+#include <stdio.h>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+
+#include "jni/java_vm_ext.h"
+#include "runtime.h"
+
+namespace art {
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_GetMethodId(JNIEnv* env,
+ jclass k ATTRIBUTE_UNUSED,
+ bool is_static,
+ jclass target,
+ jstring name,
+ jstring sig) {
+ auto get_id = is_static ? env->functions->GetStaticMethodID : env->functions->GetMethodID;
+ jboolean cpy;
+ const char* cname = env->GetStringUTFChars(name, &cpy);
+ const char* csig = env->GetStringUTFChars(sig, &cpy);
+ jlong res = static_cast<jlong>(reinterpret_cast<intptr_t>(get_id(env, target, cname, csig)));
+ env->ReleaseStringUTFChars(name, cname);
+ env->ReleaseStringUTFChars(sig, csig);
+ return res;
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_Main_GetJniType(JNIEnv* env, jclass k ATTRIBUTE_UNUSED) {
+ std::ostringstream oss;
+ oss << Runtime::Current()->GetJniIdType();
+ return env->NewStringUTF(oss.str().c_str());
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_SetToPointerIds(JNIEnv* env ATTRIBUTE_UNUSED,
+ jclass k ATTRIBUTE_UNUSED) {
+ Runtime::Current()->SetJniIdType(JniIdType::kPointer);
+}
+extern "C" JNIEXPORT void JNICALL Java_Main_SetToIndexIds(JNIEnv* env ATTRIBUTE_UNUSED,
+ jclass k ATTRIBUTE_UNUSED) {
+ Runtime::Current()->SetJniIdType(JniIdType::kIndices);
+}
+
+} // namespace art
diff --git a/test/1972-jni-id-swap-indices/run b/test/1972-jni-id-swap-indices/run
new file mode 100755
index 0000000..7d26b24
--- /dev/null
+++ b/test/1972-jni-id-swap-indices/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+args=$(echo "$@" | sed 's/--runtime-option -Xopaque-jni-ids\:true//g')
+
+./default-run $args --android-runtime-option -Xopaque-jni-ids:swapable
\ No newline at end of file
diff --git a/test/1972-jni-id-swap-indices/src/Main.java b/test/1972-jni-id-swap-indices/src/Main.java
new file mode 100644
index 0000000..f2bb1ab
--- /dev/null
+++ b/test/1972-jni-id-swap-indices/src/Main.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+import java.util.function.Consumer;
+
+public class Main {
+ public static final boolean PRINT = false;
+
+ public static void doNothingPtr() {}
+
+ public static void doNothingIdx() {}
+
+ public static void DbgPrint(String str) {
+ if (PRINT) {
+ System.out.println(str);
+ }
+ }
+
+ public static long GetId(String name) {
+ return GetMethodId(true, Main.class, name, "()V");
+ }
+
+ public static void main(String[] args) {
+ System.loadLibrary(args[0]);
+ System.out.println("JNI Type is: " + GetJniType());
+ long expect_ptr_id = GetId("doNothingPtr");
+ DbgPrint(String.format("expected_ptr_id is 0x%x", expect_ptr_id));
+ if (expect_ptr_id % 4 != 0) {
+ throw new Error("ID " + expect_ptr_id + " is not aligned!");
+ } else {
+ System.out.println("pointer ID looks like a pointer!");
+ }
+ SetToIndexIds();
+ System.out.println("JNI Type is: " + GetJniType());
+ long expect_idx_id = GetId("doNothingIdx");
+ DbgPrint(String.format("expected_idx_id is 0x%x", expect_idx_id));
+ if (expect_idx_id % 2 != 1) {
+ throw new Error("ID " + expect_ptr_id + " is not odd!");
+ } else {
+ System.out.println("index ID looks like an index!");
+ }
+ long again_ptr_id = GetId("doNothingPtr");
+ if (expect_ptr_id != again_ptr_id) {
+ throw new Error(
+ "Got different id values for same method. " + expect_ptr_id + " vs " + again_ptr_id);
+ } else {
+ System.out.println("pointer ID remains a pointer!");
+ }
+ long well_known_id = GetMethodId(false, Consumer.class, "accept", "(Ljava/lang/Object;)V");
+ DbgPrint(String.format("well_known_id is 0x%x", well_known_id));
+ if (well_known_id % 2 != 1) {
+ throw new Error("WKC ID " + well_known_id + " is not odd!");
+ } else {
+ System.out.println("index WKC ID looks like an index!");
+ }
+ }
+
+ private static native String GetJniType();
+ private static native void SetToIndexIds();
+ private static native long GetMethodId(boolean is_static, Class k, String name, String sig);
+}
diff --git a/test/1973-jni-id-swap-pointer/expected.txt b/test/1973-jni-id-swap-pointer/expected.txt
new file mode 100644
index 0000000..5ea7858
--- /dev/null
+++ b/test/1973-jni-id-swap-pointer/expected.txt
@@ -0,0 +1,6 @@
+JNI_OnLoad called
+JNI Type is: SwapablePointer
+pointer ID looks like a pointer!
+JNI Type is: Pointer
+pointer2 ID looks like a pointer!
+pointer ID remains a pointer!
diff --git a/test/1973-jni-id-swap-pointer/info.txt b/test/1973-jni-id-swap-pointer/info.txt
new file mode 100644
index 0000000..42f7ef2
--- /dev/null
+++ b/test/1973-jni-id-swap-pointer/info.txt
@@ -0,0 +1 @@
+Tests changing from SwapablePointer to indices for JniIdType
\ No newline at end of file
diff --git a/test/1973-jni-id-swap-pointer/run b/test/1973-jni-id-swap-pointer/run
new file mode 100755
index 0000000..7d26b24
--- /dev/null
+++ b/test/1973-jni-id-swap-pointer/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+args=$(echo "$@" | sed 's/--runtime-option -Xopaque-jni-ids\:true//g')
+
+./default-run $args --android-runtime-option -Xopaque-jni-ids:swapable
\ No newline at end of file
diff --git a/test/1973-jni-id-swap-pointer/src/Main.java b/test/1973-jni-id-swap-pointer/src/Main.java
new file mode 100644
index 0000000..755fbd5
--- /dev/null
+++ b/test/1973-jni-id-swap-pointer/src/Main.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+public class Main {
+ public static final boolean PRINT = false;
+
+ public static void doNothingPtr() {}
+
+ public static void doNothingIdx() {}
+
+ public static void DbgPrint(String str) {
+ if (PRINT) {
+ System.out.println(str);
+ }
+ }
+
+ public static long GetId(String name) {
+ return GetMethodId(true, Main.class, name, "()V");
+ }
+
+ public static void main(String[] args) {
+ System.loadLibrary(args[0]);
+ System.out.println("JNI Type is: " + GetJniType());
+ long expect_ptr_id = GetId("doNothingPtr");
+ DbgPrint(String.format("expected_ptr_id is 0x%x", expect_ptr_id));
+ if (expect_ptr_id % 4 != 0) {
+ throw new Error("ID " + expect_ptr_id + " is not aligned!");
+ } else {
+ System.out.println("pointer ID looks like a pointer!");
+ }
+ SetToPointerIds();
+ System.out.println("JNI Type is: " + GetJniType());
+ long expect_ptr_id2 = GetId("doNothingIdx");
+ DbgPrint(String.format("expected_ptr_id2 is 0x%x", expect_ptr_id2));
+ if (expect_ptr_id2 % 4 != 0) {
+ throw new Error("ID " + expect_ptr_id + " is not aligned!");
+ } else {
+ System.out.println("pointer2 ID looks like a pointer!");
+ }
+ long again_ptr_id = GetId("doNothingPtr");
+ if (expect_ptr_id != again_ptr_id) {
+ throw new Error(
+ "Got different id values for same method. " + expect_ptr_id + " vs " + again_ptr_id);
+ } else {
+ System.out.println("pointer ID remains a pointer!");
+ }
+ }
+
+ private static native String GetJniType();
+ private static native void SetToPointerIds();
+ private static native long GetMethodId(boolean is_static, Class k, String name, String sig);
+}
diff --git a/test/Android.bp b/test/Android.bp
index 91d9393..d1382ab 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -509,6 +509,7 @@
"1001-app-image-regions/app_image_regions.cc",
"1002-notify-startup/startup_interface.cc",
"1947-breakpoint-redefine-deopt/check_deopt.cc",
+ "1972-jni-id-swap-indices/jni_id.cc",
"common/runtime_state.cc",
"common/stack_inspect.cc",
],
diff --git a/test/knownfailures.json b/test/knownfailures.json
index bd32647..85eec1b 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1114,7 +1114,10 @@
"1001-app-image-regions",
"1339-dead-reference-safe",
"1951-monitor-enter-no-suspend",
- "1957-error-ext"],
+ "1957-error-ext",
+ "1972-jni-id-swap-indices",
+ "1973-jni-id-swap-pointer"
+ ],
"variant": "jvm",
"description": ["Doesn't run on RI."]
},