diff options
25 files changed, 516 insertions, 118 deletions
diff --git a/runtime/jni/jni_env_ext.cc b/runtime/jni/jni_env_ext.cc index 411794c3a9..e2581dd4bb 100644 --- a/runtime/jni/jni_env_ext.cc +++ b/runtime/jni/jni_env_ext.cc @@ -291,11 +291,12 @@ void JNIEnvExt::CheckNoHeldMonitors() { } } -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 @@ const JNINativeInterface* JNIEnvExt::GetFunctionTable(bool check_jni) { 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 924ff514e0..7aae92fa46 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 @@ class JNIEnvExt : public JNIEnv { // 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 @@ class JNIEnvExt : public JNIEnv { 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 @@ class JNIEnvExt : public JNIEnv { 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 @@ class JNIEnvExt : public JNIEnv { 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 c28d813f18..88c6ba12d7 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 @@ template <> ArtMethod* Canonicalize(ArtMethod* t) { // 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 <> size_t JniIdManager::GetLinearSearchStartId<ArtMethod>(ArtMethod* m) } 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 @@ template <typename ArtType> uintptr_t JniIdManager::EncodeGenericId(ArtType* t) 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 @@ jmethodID JniIdManager::EncodeMethodId(ArtMethod* method) { } 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 5e5c581be4..d2948152fb 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 @@ class JniIdManager { 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 3d388815ac..882e10f12d 100644 --- a/runtime/jni/jni_internal.cc +++ b/runtime/jni/jni_internal.cc @@ -206,21 +206,6 @@ static std::string NormalizeJniClassDescriptor(const char* name) { 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 @@ static void ReportInvalidJNINativeMethod(const ScopedObjectAccess& soa, 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 @@ template<bool kEnableIndexIds> 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 @@ static jfieldID FindFieldID(const ScopedObjectAccess& soa, jclass jni_class, con 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 @@ struct JniNativeInterfaceFunctions { 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 b6e106cc88..da1792231b 100644 --- a/runtime/jni/jni_internal.h +++ b/runtime/jni/jni_internal.h @@ -28,6 +28,7 @@ namespace art { class ArtField; class ArtMethod; +class ScopedObjectAccess; const JNINativeInterface* GetJniNativeInterface(); const JNINativeInterface* GetRuntimeShutdownNativeInterface(); @@ -41,6 +42,22 @@ void JniInitializeNativeCallerCheck(); // 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 @@ static inline ArtField* DecodeArtField(jfieldID fid) { 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 @@ template <bool kEnableIndexIds = true> 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 7802ec6c37..3f952b638c 100644 --- a/runtime/jni_id_type.h +++ b/runtime/jni_id_type.h @@ -28,7 +28,10 @@ enum class JniIdType { // 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 846517b524..a299f34ada 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::GetMethodIds() { } } 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::GetStaticFieldIds() { } } 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::GetInstanceFieldIds() { } } 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 fb1e6b7037..bfedfa9c1b 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 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .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 @@ void ParsedOptions::Usage(const char* fmt, ...) { "(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 534d69d785..1b12f6a4b4 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -1306,12 +1306,7 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { 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 @@ void Runtime::SetSignalHookDebuggable(bool value) { 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 a276b8768b..ca017614fa 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -841,10 +841,18 @@ class Runtime { 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 c384eda265..e9d1511ae9 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -77,7 +77,7 @@ RUNTIME_OPTIONS_KEY (bool, EnableHSpaceCompactForOOM, true) 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 279b45b755..002bacb294 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 jclass CacheClass(JNIEnv* env, const char* jni_class_name) { 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 jfieldID CacheField(JNIEnv* env, jclass c, bool is_static, 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 @@ void WellKnownClasses::Init(JNIEnv* env) { 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 @@ void WellKnownClasses::LateInit(JNIEnv* env) { "[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 d7acd5789f..249dfa0724 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -35,11 +35,15 @@ class Class; 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 @@ struct WellKnownClasses { 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 0000000000..a22979ff8d --- /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 0000000000..8b9c215940 --- /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 0000000000..7de7131ca8 --- /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 0000000000..7d26b24902 --- /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 0000000000..f2bb1ab00c --- /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 0000000000..5ea78580ae --- /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 0000000000..42f7ef2a60 --- /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 0000000000..7d26b24902 --- /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 0000000000..755fbd5d23 --- /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 91d93935a3..d1382ab768 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -509,6 +509,7 @@ cc_defaults { "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 bd32647633..85eec1b515 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."] }, |