diff options
author | Ian Rogers <irogers@google.com> | 2014-07-17 11:09:10 -0700 |
---|---|---|
committer | Ian Rogers <irogers@google.com> | 2014-08-08 08:29:53 -0700 |
commit | 68d8b42ddec39ec0174162d90d4abaa004d1983e (patch) | |
tree | 0bb6ccf3b996bb8363c10d07aa39cde221980602 | |
parent | 9c522c2cbbf50dc687728747b37ad59985750b65 (diff) | |
download | android_art-68d8b42ddec39ec0174162d90d4abaa004d1983e.tar.gz android_art-68d8b42ddec39ec0174162d90d4abaa004d1983e.tar.bz2 android_art-68d8b42ddec39ec0174162d90d4abaa004d1983e.zip |
Wire up check JNI force copy mode.
Increase check JNI checks.
Break apart jni_internal.h in to jni_env_ext.h and java_vm_ext.h.
Fix the abuse of ScopedObjectAccess/annotalysis by ScopedCheck in the case
of VM routines.
Make class loader override and shared library class loader JNI global
references rather than mirror pointers.
Clean-ups to native bridge.
Change-Id: If7c6110b5aade7a402bfb67534af86a7b2cdeb55
51 files changed, 4963 insertions, 2668 deletions
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc index 995ea46993..75d3030baf 100644 --- a/compiler/jni/jni_compiler_test.cc +++ b/compiler/jni/jni_compiler_test.cc @@ -155,13 +155,9 @@ TEST_F(JniCompilerTest, CompileAndRunIntMethodThroughStub) { SetUpForTest(false, "bar", "(I)I", nullptr); // calling through stub will link with &Java_MyClassNatives_bar - ScopedObjectAccess soa(Thread::Current()); std::string reason; - StackHandleScope<1> hs(soa.Self()); - Handle<mirror::ClassLoader> class_loader( - hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader_))); - ASSERT_TRUE( - Runtime::Current()->GetJavaVM()->LoadNativeLibrary("", class_loader, &reason)) << reason; + ASSERT_TRUE(Runtime::Current()->GetJavaVM()->LoadNativeLibrary(env_, "", class_loader_, &reason)) + << reason; jint result = env_->CallNonvirtualIntMethod(jobj_, jklass_, jmethod_, 24); EXPECT_EQ(25, result); @@ -172,13 +168,9 @@ TEST_F(JniCompilerTest, CompileAndRunStaticIntMethodThroughStub) { SetUpForTest(true, "sbar", "(I)I", nullptr); // calling through stub will link with &Java_MyClassNatives_sbar - ScopedObjectAccess soa(Thread::Current()); std::string reason; - StackHandleScope<1> hs(soa.Self()); - Handle<mirror::ClassLoader> class_loader( - hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader_))); - ASSERT_TRUE( - Runtime::Current()->GetJavaVM()->LoadNativeLibrary("", class_loader, &reason)) << reason; + ASSERT_TRUE(Runtime::Current()->GetJavaVM()->LoadNativeLibrary(env_, "", class_loader_, &reason)) + << reason; jint result = env_->CallStaticIntMethod(jklass_, jmethod_, 42); EXPECT_EQ(43, result); @@ -771,10 +763,10 @@ TEST_F(JniCompilerTest, UpcallReturnTypeChecking_Instance) { check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass()"); // Here, we just call the method incorrectly; we should catch that too. - env_->CallVoidMethod(jobj_, jmethod_); + env_->CallObjectMethod(jobj_, jmethod_); check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass()"); - env_->CallStaticVoidMethod(jklass_, jmethod_); - check_jni_abort_catcher.Check("calling non-static method java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass() with CallStaticVoidMethodV"); + env_->CallStaticObjectMethod(jklass_, jmethod_); + check_jni_abort_catcher.Check("calling non-static method java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass() with CallStaticObjectMethodV"); } TEST_F(JniCompilerTest, UpcallReturnTypeChecking_Static) { @@ -789,10 +781,10 @@ TEST_F(JniCompilerTest, UpcallReturnTypeChecking_Static) { check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass()"); // Here, we just call the method incorrectly; we should catch that too. - env_->CallStaticVoidMethod(jklass_, jmethod_); + env_->CallStaticObjectMethod(jklass_, jmethod_); check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass()"); - env_->CallVoidMethod(jobj_, jmethod_); - check_jni_abort_catcher.Check("calling static method java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass() with CallVoidMethodV"); + env_->CallObjectMethod(jobj_, jmethod_); + check_jni_abort_catcher.Check("calling static method java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass() with CallObjectMethodV"); } // This should take jclass, but we're imitating a bug pattern. diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc index 1a35da071c..8e870210b9 100644 --- a/compiler/jni/quick/jni_compiler.cc +++ b/compiler/jni/quick/jni_compiler.cc @@ -27,7 +27,7 @@ #include "dex_file-inl.h" #include "driver/compiler_driver.h" #include "entrypoints/quick/quick_entrypoints.h" -#include "jni_internal.h" +#include "jni_env_ext.h" #include "mirror/art_method.h" #include "utils/assembler.h" #include "utils/managed_register.h" diff --git a/compiler/trampolines/trampoline_compiler.cc b/compiler/trampolines/trampoline_compiler.cc index d5225c1f73..6da375aef0 100644 --- a/compiler/trampolines/trampoline_compiler.cc +++ b/compiler/trampolines/trampoline_compiler.cc @@ -16,7 +16,7 @@ #include "trampoline_compiler.h" -#include "jni_internal.h" +#include "jni_env_ext.h" #include "utils/arm/assembler_arm.h" #include "utils/arm64/assembler_arm64.h" #include "utils/mips/assembler_mips.h" diff --git a/runtime/Android.mk b/runtime/Android.mk index 0b13a3ffec..1e037f5b06 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -81,6 +81,7 @@ LIBART_COMMON_SRC_FILES := \ interpreter/interpreter.cc \ interpreter/interpreter_common.cc \ interpreter/interpreter_switch_impl.cc \ + java_vm_ext.cc \ jdwp/jdwp_event.cc \ jdwp/jdwp_expand_buf.cc \ jdwp/jdwp_handler.cc \ @@ -88,6 +89,7 @@ LIBART_COMMON_SRC_FILES := \ jdwp/jdwp_request.cc \ jdwp/jdwp_socket.cc \ jdwp/object_registry.cc \ + jni_env_ext.cc \ jni_internal.cc \ jobject_comparator.cc \ mem_map.cc \ diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc index c0a865fd0b..abe0aa0249 100644 --- a/runtime/base/mutex.cc +++ b/runtime/base/mutex.cc @@ -35,6 +35,7 @@ Mutex* Locks::allocated_thread_ids_lock_ = nullptr; Mutex* Locks::breakpoint_lock_ = nullptr; ReaderWriterMutex* Locks::classlinker_classes_lock_ = nullptr; ReaderWriterMutex* Locks::heap_bitmap_lock_ = nullptr; +Mutex* Locks::jni_libraries_lock_ = nullptr; Mutex* Locks::logging_lock_ = nullptr; Mutex* Locks::mem_maps_lock_ = nullptr; Mutex* Locks::modify_ldt_lock_ = nullptr; @@ -834,6 +835,7 @@ void Locks::Init() { DCHECK(breakpoint_lock_ != nullptr); DCHECK(classlinker_classes_lock_ != nullptr); DCHECK(heap_bitmap_lock_ != nullptr); + DCHECK(jni_libraries_lock_ != nullptr); DCHECK(logging_lock_ != nullptr); DCHECK(mutator_lock_ != nullptr); DCHECK(thread_list_lock_ != nullptr); @@ -878,6 +880,10 @@ void Locks::Init() { DCHECK(thread_list_lock_ == nullptr); thread_list_lock_ = new Mutex("thread list lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kJniLoadLibraryLock); + DCHECK(jni_libraries_lock_ == nullptr); + jni_libraries_lock_ = new Mutex("JNI shared libraries map lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kBreakpointLock); DCHECK(breakpoint_lock_ == nullptr); breakpoint_lock_ = new Mutex("breakpoint lock", current_lock_level); diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h index d898e49df2..fd766295ac 100644 --- a/runtime/base/mutex.h +++ b/runtime/base/mutex.h @@ -74,7 +74,6 @@ enum LockLevel { kDefaultMutexLevel, kMarkSweepLargeObjectLock, kPinTableLock, - kLoadLibraryLock, kJdwpObjectRegistryLock, kModifyLdtLock, kAllocatedThreadIdsLock, @@ -83,6 +82,7 @@ enum LockLevel { kBreakpointLock, kMonitorLock, kMonitorListLock, + kJniLoadLibraryLock, kThreadListLock, kBreakpointInvokeLock, kDeoptimizationLock, @@ -561,8 +561,11 @@ class Locks { // attaching and detaching. static Mutex* thread_list_lock_ ACQUIRED_AFTER(trace_lock_); + // Guards maintaining loading library data structures. + static Mutex* jni_libraries_lock_ ACQUIRED_AFTER(thread_list_lock_); + // Guards breakpoints. - static Mutex* breakpoint_lock_ ACQUIRED_AFTER(thread_list_lock_); + static Mutex* breakpoint_lock_ ACQUIRED_AFTER(jni_libraries_lock_); // Guards lists of classes within the class linker. static ReaderWriterMutex* classlinker_classes_lock_ ACQUIRED_AFTER(breakpoint_lock_); diff --git a/runtime/check_jni.cc b/runtime/check_jni.cc index a530594d7c..99277a0629 100644 --- a/runtime/check_jni.cc +++ b/runtime/check_jni.cc @@ -25,6 +25,7 @@ #include "dex_file-inl.h" #include "field_helper.h" #include "gc/space/space.h" +#include "java_vm_ext.h" #include "mirror/art_field-inl.h" #include "mirror/art_method-inl.h" #include "mirror/class-inl.h" @@ -35,62 +36,16 @@ #include "runtime.h" #include "scoped_thread_state_change.h" #include "thread.h" +#include "well_known_classes.h" namespace art { -static void JniAbort(const char* jni_function_name, const char* msg) { - Thread* self = Thread::Current(); - ScopedObjectAccess soa(self); - mirror::ArtMethod* current_method = self->GetCurrentMethod(nullptr); - - std::ostringstream os; - os << "JNI DETECTED ERROR IN APPLICATION: " << msg; - - if (jni_function_name != nullptr) { - os << "\n in call to " << jni_function_name; - } - // TODO: is this useful given that we're about to dump the calling thread's stack? - if (current_method != nullptr) { - os << "\n from " << PrettyMethod(current_method); - } - os << "\n"; - self->Dump(os); - - JavaVMExt* vm = Runtime::Current()->GetJavaVM(); - if (vm->check_jni_abort_hook != nullptr) { - vm->check_jni_abort_hook(vm->check_jni_abort_hook_data, os.str()); - } else { - // Ensure that we get a native stack trace for this thread. - self->TransitionFromRunnableToSuspended(kNative); - LOG(FATAL) << os.str(); - self->TransitionFromSuspendedToRunnable(); // Unreachable, keep annotalysis happy. - } -} - -static void JniAbortV(const char* jni_function_name, const char* fmt, va_list ap) { - std::string msg; - StringAppendV(&msg, fmt, ap); - JniAbort(jni_function_name, msg.c_str()); -} - -void JniAbortF(const char* jni_function_name, const char* fmt, ...) { - va_list args; - va_start(args, fmt); - JniAbortV(jni_function_name, fmt, args); - va_end(args); -} - /* * =========================================================================== * JNI function helpers * =========================================================================== */ -static bool IsHandleScopeLocalRef(JNIEnv* env, jobject localRef) { - return GetIndirectRefKind(localRef) == kHandleScopeOrInvalid && - reinterpret_cast<JNIEnvExt*>(env)->self->HandleScopeContains(localRef); -} - // Flags passed into ScopedCheck. #define kFlag_Default 0x0000 @@ -109,134 +64,88 @@ static bool IsHandleScopeLocalRef(JNIEnv* env, jobject localRef) { #define kFlag_Invocation 0x8000 // Part of the invocation interface (JavaVM*). #define kFlag_ForceTrace 0x80000000 // Add this to a JNI function's flags if you want to trace every call. - -static const char* gBuiltInPrefixes[] = { - "Landroid/", - "Lcom/android/", - "Lcom/google/android/", - "Ldalvik/", - "Ljava/", - "Ljavax/", - "Llibcore/", - "Lorg/apache/harmony/", - nullptr +/* + * Java primitive types: + * B - jbyte + * C - jchar + * D - jdouble + * F - jfloat + * I - jint + * J - jlong + * S - jshort + * Z - jboolean (shown as true and false) + * V - void + * + * Java reference types: + * L - jobject + * a - jarray + * c - jclass + * s - jstring + * t - jthrowable + * + * JNI types: + * b - jboolean (shown as JNI_TRUE and JNI_FALSE) + * f - jfieldID + * i - JNI error value (JNI_OK, JNI_ERR, JNI_EDETACHED, JNI_EVERSION) + * m - jmethodID + * p - void* + * r - jint (for release mode arguments) + * u - const char* (Modified UTF-8) + * z - jsize (for lengths; use i if negative values are okay) + * v - JavaVM* + * w - jobjectRefType + * E - JNIEnv* + * . - no argument; just print "..." (used for varargs JNI calls) + * + */ +union JniValueType { + jarray a; + jboolean b; + jclass c; + jfieldID f; + jint i; + jmethodID m; + const void* p; // Pointer. + jint r; // Release mode. + jstring s; + jthrowable t; + const char* u; // Modified UTF-8. + JavaVM* v; + jobjectRefType w; + jsize z; + jbyte B; + jchar C; + jdouble D; + JNIEnv* E; + jfloat F; + jint I; + jlong J; + jobject L; + jshort S; + const void* V; // void + jboolean Z; }; -static bool ShouldTrace(JavaVMExt* vm, mirror::ArtMethod* method) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - // If both "-Xcheck:jni" and "-Xjnitrace:" are enabled, we print trace messages - // when a native method that matches the -Xjnitrace argument calls a JNI function - // such as NewByteArray. - // If -verbose:third-party-jni is on, we want to log any JNI function calls - // made by a third-party native method. - std::string class_name(method->GetDeclaringClassDescriptor()); - if (!vm->trace.empty() && class_name.find(vm->trace) != std::string::npos) { - return true; - } - if (VLOG_IS_ON(third_party_jni)) { - // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look - // like part of Android. - for (size_t i = 0; gBuiltInPrefixes[i] != nullptr; ++i) { - if (StartsWith(class_name, gBuiltInPrefixes[i])) { - return false; - } - } - return true; - } - return false; -} - class ScopedCheck { public: - // For JNIEnv* functions. - explicit ScopedCheck(JNIEnv* env, int flags, const char* functionName) - SHARED_LOCK_FUNCTION(Locks::mutator_lock_) - : soa_(env) { - Init(flags, functionName, true); - CheckThread(flags); + explicit ScopedCheck(int flags, const char* functionName, bool has_method = true) + : function_name_(functionName), flags_(flags), indent_(0), has_method_(has_method) { } - // For JavaVM* functions. - // TODO: it's not correct that this is a lock function, but making it so aids annotalysis. - explicit ScopedCheck(JavaVM* vm, bool has_method, const char* functionName) - SHARED_LOCK_FUNCTION(Locks::mutator_lock_) - : soa_(vm) { - Init(kFlag_Invocation, functionName, has_method); - } - - ~ScopedCheck() UNLOCK_FUNCTION(Locks::mutator_lock_) {} - - const ScopedObjectAccess& soa() { - return soa_; - } - - bool ForceCopy() { - return Runtime::Current()->GetJavaVM()->force_copy; - } + ~ScopedCheck() {} // Checks that 'class_name' is a valid "fully-qualified" JNI class name, like "java/lang/Thread" // or "[Ljava/lang/Object;". A ClassLoader can actually normalize class names a couple of // times, so using "java.lang.Thread" instead of "java/lang/Thread" might work in some // circumstances, but this is incorrect. - void CheckClassName(const char* class_name) { + bool CheckClassName(const char* class_name) { if ((class_name == nullptr) || !IsValidJniClassName(class_name)) { - JniAbortF(function_name_, - "illegal class name '%s'\n" - " (should be of the form 'package/Class', [Lpackage/Class;' or '[[B')", - class_name); - } - } - - /* - * Verify that the field is of the appropriate type. If the field has an - * object type, "java_object" is the object we're trying to assign into it. - * - * Works for both static and instance fields. - */ - void CheckFieldType(jvalue value, jfieldID fid, char prim, bool isStatic) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - StackHandleScope<1> hs(Thread::Current()); - Handle<mirror::ArtField> f(hs.NewHandle(CheckFieldID(fid))); - if (f.Get() == nullptr) { - return; - } - mirror::Class* field_type = FieldHelper(f).GetType(); - if (!field_type->IsPrimitive()) { - jobject java_object = value.l; - if (java_object != nullptr) { - mirror::Object* obj = soa_.Decode<mirror::Object*>(java_object); - // If java_object is a weak global ref whose referent has been cleared, - // obj will be NULL. Otherwise, obj should always be non-NULL - // and valid. - if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(obj)) { - Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR)); - JniAbortF(function_name_, "field operation on invalid %s: %p", - ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object); - return; - } else { - if (!obj->InstanceOf(field_type)) { - JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %s", - PrettyField(f.Get()).c_str(), PrettyTypeOf(obj).c_str()); - return; - } - } - } - } else if (field_type != Runtime::Current()->GetClassLinker()->FindPrimitiveClass(prim)) { - JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %c", - PrettyField(f.Get()).c_str(), prim); - return; - } - - if (isStatic != f.Get()->IsStatic()) { - if (isStatic) { - JniAbortF(function_name_, "accessing non-static field %s as static", - PrettyField(f.Get()).c_str()); - } else { - JniAbortF(function_name_, "accessing static field %s as non-static", - PrettyField(f.Get()).c_str()); - } - return; + AbortF("illegal class name '%s'\n" + " (should be of the form 'package/Class', [Lpackage/Class;' or '[[B')", + class_name); + return false; } + return true; } /* @@ -244,59 +153,87 @@ class ScopedCheck { * * Assumes "jobj" has already been validated. */ - void CheckInstanceFieldID(jobject java_object, jfieldID fid) + bool CheckInstanceFieldID(ScopedObjectAccess& soa, jobject java_object, jfieldID fid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - mirror::Object* o = soa_.Decode<mirror::Object*>(java_object); - if (o == nullptr || !Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) { + mirror::Object* o = soa.Decode<mirror::Object*>(java_object); + if (o == nullptr) { + AbortF("field operation on NULL object: %p", java_object); + return false; + } + if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) { Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR)); - JniAbortF(function_name_, "field operation on invalid %s: %p", - ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object); - return; + AbortF("field operation on invalid %s: %p", + ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), + java_object); + return false; } - mirror::ArtField* f = CheckFieldID(fid); + mirror::ArtField* f = CheckFieldID(soa, fid); if (f == nullptr) { - return; + return false; } mirror::Class* c = o->GetClass(); if (c->FindInstanceField(f->GetName(), f->GetTypeDescriptor()) == nullptr) { - JniAbortF(function_name_, "jfieldID %s not valid for an object of class %s", - PrettyField(f).c_str(), PrettyTypeOf(o).c_str()); + AbortF("jfieldID %s not valid for an object of class %s", + PrettyField(f).c_str(), PrettyTypeOf(o).c_str()); + return false; } + return true; } /* * Verify that the pointer value is non-NULL. */ - void CheckNonNull(const void* ptr) { - if (ptr == nullptr) { - JniAbortF(function_name_, "non-nullable argument was NULL"); + bool CheckNonNull(const void* ptr) { + if (UNLIKELY(ptr == nullptr)) { + AbortF("non-nullable argument was NULL"); + return false; } + return true; } /* * Verify that the method's return type matches the type of call. * 'expectedType' will be "L" for all objects, including arrays. */ - void CheckSig(jmethodID mid, const char* expectedType, bool isStatic) + bool CheckMethodAndSig(ScopedObjectAccess& soa, jobject jobj, jclass jc, + jmethodID mid, Primitive::Type type, InvokeType invoke) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - mirror::ArtMethod* m = CheckMethodID(mid); + mirror::ArtMethod* m = CheckMethodID(soa, mid); if (m == nullptr) { - return; + return false; } - if (*expectedType != m->GetShorty()[0]) { - JniAbortF(function_name_, "the return type of %s does not match %s", - function_name_, PrettyMethod(m).c_str()); + if (type != Primitive::GetType(m->GetShorty()[0])) { + AbortF("the return type of %s does not match %s", function_name_, PrettyMethod(m).c_str()); + return false; } - if (isStatic != m->IsStatic()) { - if (isStatic) { - JniAbortF(function_name_, "calling non-static method %s with %s", - PrettyMethod(m).c_str(), function_name_); + bool is_static = (invoke == kStatic); + if (is_static != m->IsStatic()) { + if (is_static) { + AbortF("calling non-static method %s with %s", + PrettyMethod(m).c_str(), function_name_); } else { - JniAbortF(function_name_, "calling static method %s with %s", - PrettyMethod(m).c_str(), function_name_); + AbortF("calling static method %s with %s", + PrettyMethod(m).c_str(), function_name_); } + return false; } + if (invoke != kVirtual) { + mirror::Class* c = soa.Decode<mirror::Class*>(jc); + if (!m->GetDeclaringClass()->IsAssignableFrom(c)) { + AbortF("can't call %s %s with class %s", invoke == kStatic ? "static" : "nonvirtual", + PrettyMethod(m).c_str(), PrettyClass(c).c_str()); + return false; + } + } + if (invoke != kStatic) { + mirror::Object* o = soa.Decode<mirror::Object*>(jobj); + if (!o->InstanceOf(m->GetDeclaringClass())) { + AbortF("can't call %s on instance of %s", PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str()); + return false; + } + } + return true; } /* @@ -304,17 +241,18 @@ class ScopedCheck { * * Assumes "java_class" has already been validated. */ - void CheckStaticFieldID(jclass java_class, jfieldID fid) + bool CheckStaticFieldID(ScopedObjectAccess& soa, jclass java_class, jfieldID fid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - mirror::Class* c = soa_.Decode<mirror::Class*>(java_class); - mirror::ArtField* f = CheckFieldID(fid); + mirror::Class* c = soa.Decode<mirror::Class*>(java_class); + mirror::ArtField* f = CheckFieldID(soa, fid); if (f == nullptr) { - return; + return false; } if (f->GetDeclaringClass() != c) { - JniAbortF(function_name_, "static jfieldID %p not valid for class %s", - fid, PrettyClass(c).c_str()); + AbortF("static jfieldID %p not valid for class %s", fid, PrettyClass(c).c_str()); + return false; } + return true; } /* @@ -326,17 +264,18 @@ class ScopedCheck { * * Instances of "java_class" must be instances of the method's declaring class. */ - void CheckStaticMethod(jclass java_class, jmethodID mid) + bool CheckStaticMethod(ScopedObjectAccess& soa, jclass java_class, jmethodID mid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - mirror::ArtMethod* m = CheckMethodID(mid); + mirror::ArtMethod* m = CheckMethodID(soa, mid); if (m == nullptr) { - return; + return false; } - mirror::Class* c = soa_.Decode<mirror::Class*>(java_class); + mirror::Class* c = soa.Decode<mirror::Class*>(java_class); if (!m->GetDeclaringClass()->IsAssignableFrom(c)) { - JniAbortF(function_name_, "can't call static %s on class %s", - PrettyMethod(m).c_str(), PrettyClass(c).c_str()); + AbortF("can't call static %s on class %s", PrettyMethod(m).c_str(), PrettyClass(c).c_str()); + return false; } + return true; } /* @@ -346,17 +285,18 @@ class ScopedCheck { * (Note the mid might point to a declaration in an interface; this * will be handled automatically by the instanceof check.) */ - void CheckVirtualMethod(jobject java_object, jmethodID mid) + bool CheckVirtualMethod(ScopedObjectAccess& soa, jobject java_object, jmethodID mid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - mirror::ArtMethod* m = CheckMethodID(mid); + mirror::ArtMethod* m = CheckMethodID(soa, mid); if (m == nullptr) { - return; + return false; } - mirror::Object* o = soa_.Decode<mirror::Object*>(java_object); + mirror::Object* o = soa.Decode<mirror::Object*>(java_object); if (!o->InstanceOf(m->GetDeclaringClass())) { - JniAbortF(function_name_, "can't call %s on instance of %s", - PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str()); + AbortF("can't call %s on instance of %s", PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str()); + return false; } + return true; } /** @@ -395,11 +335,10 @@ class ScopedCheck { * * Use the kFlag_NullableUtf flag where 'u' field(s) are nullable. */ - void Check(bool entry, const char* fmt0, ...) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - va_list ap; - + bool Check(ScopedObjectAccess& soa, bool entry, const char* fmt, JniValueType* args) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { mirror::ArtMethod* traceMethod = nullptr; - if (has_method_ && (!soa_.Vm()->trace.empty() || VLOG_IS_ON(third_party_jni))) { + if (has_method_ && soa.Vm()->IsTracingEnabled()) { // We need to guard some of the invocation interface's calls: a bad caller might // use DetachCurrentThread or GetEnv on a thread that's not yet attached. Thread* self = Thread::Current(); @@ -409,129 +348,70 @@ class ScopedCheck { } if (((flags_ & kFlag_ForceTrace) != 0) || - (traceMethod != nullptr && ShouldTrace(soa_.Vm(), traceMethod))) { - va_start(ap, fmt0); + (traceMethod != nullptr && soa.Vm()->ShouldTrace(traceMethod))) { std::string msg; - for (const char* fmt = fmt0; *fmt;) { - char ch = *fmt++; - if (ch == 'B') { // jbyte - jbyte b = va_arg(ap, int); - if (b >= 0 && b < 10) { - StringAppendF(&msg, "%d", b); - } else { - StringAppendF(&msg, "%#x (%d)", b, b); - } - } else if (ch == 'C') { // jchar - jchar c = va_arg(ap, int); - if (c < 0x7f && c >= ' ') { - StringAppendF(&msg, "U+%x ('%c')", c, c); - } else { - StringAppendF(&msg, "U+%x", c); - } - } else if (ch == 'F' || ch == 'D') { // jfloat, jdouble - StringAppendF(&msg, "%g", va_arg(ap, double)); - } else if (ch == 'I' || ch == 'S') { // jint, jshort - StringAppendF(&msg, "%d", va_arg(ap, int)); - } else if (ch == 'J') { // jlong - StringAppendF(&msg, "%" PRId64, va_arg(ap, jlong)); - } else if (ch == 'Z') { // jboolean - StringAppendF(&msg, "%s", va_arg(ap, int) ? "true" : "false"); - } else if (ch == 'V') { // void - msg += "void"; - } else if (ch == 'v') { // JavaVM* - JavaVM* vm = va_arg(ap, JavaVM*); - StringAppendF(&msg, "(JavaVM*)%p", vm); - } else if (ch == 'E') { // JNIEnv* - JNIEnv* env = va_arg(ap, JNIEnv*); - StringAppendF(&msg, "(JNIEnv*)%p", env); - } else if (ch == 'L' || ch == 'a' || ch == 's') { // jobject, jarray, jstring - // For logging purposes, these are identical. - jobject o = va_arg(ap, jobject); - if (o == nullptr) { - msg += "NULL"; - } else { - StringAppendF(&msg, "%p", o); - } - } else if (ch == 'b') { // jboolean (JNI-style) - jboolean b = va_arg(ap, int); - msg += (b ? "JNI_TRUE" : "JNI_FALSE"); - } else if (ch == 'c') { // jclass - jclass jc = va_arg(ap, jclass); - mirror::Class* c = reinterpret_cast<mirror::Class*>(Thread::Current()->DecodeJObject(jc)); - if (c == nullptr) { - msg += "NULL"; - } else if (c == kInvalidIndirectRefObject || - !Runtime::Current()->GetHeap()->IsValidObjectAddress(c)) { - StringAppendF(&msg, "INVALID POINTER:%p", jc); - } else if (!c->IsClass()) { - msg += "INVALID NON-CLASS OBJECT OF TYPE:" + PrettyTypeOf(c); - } else { - msg += PrettyClass(c); - if (!entry) { - StringAppendF(&msg, " (%p)", jc); - } - } - } else if (ch == 'f') { // jfieldID - jfieldID fid = va_arg(ap, jfieldID); - mirror::ArtField* f = reinterpret_cast<mirror::ArtField*>(fid); - msg += PrettyField(f); - if (!entry) { - StringAppendF(&msg, " (%p)", fid); - } - } else if (ch == 'z') { // non-negative jsize - // You might expect jsize to be size_t, but it's not; it's the same as jint. - // We only treat this specially so we can do the non-negative check. - // TODO: maybe this wasn't worth it? - jint i = va_arg(ap, jint); - StringAppendF(&msg, "%d", i); - } else if (ch == 'm') { // jmethodID - jmethodID mid = va_arg(ap, jmethodID); - mirror::ArtMethod* m = reinterpret_cast<mirror::ArtMethod*>(mid); - msg += PrettyMethod(m); - if (!entry) { - StringAppendF(&msg, " (%p)", mid); - } - } else if (ch == 'p') { // void* ("pointer") - void* p = va_arg(ap, void*); - if (p == nullptr) { - msg += "NULL"; - } else { - StringAppendF(&msg, "(void*) %p", p); - } - } else if (ch == 'r') { // jint (release mode) - jint releaseMode = va_arg(ap, jint); - if (releaseMode == 0) { - msg += "0"; - } else if (releaseMode == JNI_ABORT) { - msg += "JNI_ABORT"; - } else if (releaseMode == JNI_COMMIT) { - msg += "JNI_COMMIT"; - } else { - StringAppendF(&msg, "invalid release mode %d", releaseMode); - } - } else if (ch == 'u') { // const char* (Modified UTF-8) - const char* utf = va_arg(ap, const char*); - if (utf == nullptr) { - msg += "NULL"; - } else { - StringAppendF(&msg, "\"%s\"", utf); - } - } else if (ch == '.') { - msg += "..."; + for (size_t i = 0; fmt[i] != '\0'; ++i) { + TracePossibleHeapValue(soa, entry, fmt[i], args[i], &msg); + if (fmt[i + 1] != '\0') { + StringAppendF(&msg, ", "); + } + } + + if ((flags_ & kFlag_ForceTrace) != 0) { + LOG(INFO) << "JNI: call to " << function_name_ << "(" << msg << ")"; + } else if (entry) { + if (has_method_) { + std::string methodName(PrettyMethod(traceMethod, false)); + LOG(INFO) << "JNI: " << methodName << " -> " << function_name_ << "(" << msg << ")"; + indent_ = methodName.size() + 1; } else { - JniAbortF(function_name_, "unknown trace format specifier: %c", ch); - return; + LOG(INFO) << "JNI: -> " << function_name_ << "(" << msg << ")"; + indent_ = 0; + } + } else { + LOG(INFO) << StringPrintf("JNI: %*s<- %s returned %s", indent_, "", function_name_, msg.c_str()); + } + } + + // We always do the thorough checks on entry, and never on exit... + if (entry) { + for (size_t i = 0; fmt[i] != '\0'; ++i) { + if (!CheckPossibleHeapValue(soa, fmt[i], args[i])) { + return false; } - if (*fmt) { + } + } + return true; + } + + bool CheckNonHeap(JavaVMExt* vm, bool entry, const char* fmt, JniValueType* args) { + bool should_trace = (flags_ & kFlag_ForceTrace) != 0; + if (!should_trace && vm->IsTracingEnabled()) { + // We need to guard some of the invocation interface's calls: a bad caller might + // use DetachCurrentThread or GetEnv on a thread that's not yet attached. + Thread* self = Thread::Current(); + if ((flags_ & kFlag_Invocation) == 0 || self != nullptr) { + ScopedObjectAccess soa(self); + mirror::ArtMethod* traceMethod = self->GetCurrentMethod(nullptr); + should_trace = (traceMethod != nullptr && vm->ShouldTrace(traceMethod)); + } + } + if (should_trace) { + std::string msg; + for (size_t i = 0; fmt[i] != '\0'; ++i) { + TraceNonHeapValue(fmt[i], args[i], &msg); + if (fmt[i + 1] != '\0') { StringAppendF(&msg, ", "); } } - va_end(ap); if ((flags_ & kFlag_ForceTrace) != 0) { LOG(INFO) << "JNI: call to " << function_name_ << "(" << msg << ")"; } else if (entry) { if (has_method_) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + mirror::ArtMethod* traceMethod = self->GetCurrentMethod(nullptr); std::string methodName(PrettyMethod(traceMethod, false)); LOG(INFO) << "JNI: " << methodName << " -> " << function_name_ << "(" << msg << ")"; indent_ = methodName.size() + 1; @@ -546,43 +426,176 @@ class ScopedCheck { // We always do the thorough checks on entry, and never on exit... if (entry) { - va_start(ap, fmt0); - for (const char* fmt = fmt0; *fmt; ++fmt) { - char ch = *fmt; - if (ch == 'a') { - CheckArray(va_arg(ap, jarray)); - } else if (ch == 'c') { - CheckInstance(kClass, va_arg(ap, jclass)); - } else if (ch == 'L') { - CheckObject(va_arg(ap, jobject)); - } else if (ch == 'r') { - CheckReleaseMode(va_arg(ap, jint)); - } else if (ch == 's') { - CheckInstance(kString, va_arg(ap, jstring)); - } else if (ch == 'u') { - if ((flags_ & kFlag_Release) != 0) { - CheckNonNull(va_arg(ap, const char*)); - } else { - bool nullable = ((flags_ & kFlag_NullableUtf) != 0); - CheckUtfString(va_arg(ap, const char*), nullable); - } - } else if (ch == 'z') { - CheckLengthPositive(va_arg(ap, jsize)); - } else if (strchr("BCISZbfmpEv", ch) != nullptr) { - va_arg(ap, uint32_t); // Skip this argument. - } else if (ch == 'D' || ch == 'F') { - va_arg(ap, double); // Skip this argument. - } else if (ch == 'J') { - va_arg(ap, uint64_t); // Skip this argument. - } else if (ch == '.') { - } else { - LOG(FATAL) << "Unknown check format specifier: " << ch; + for (size_t i = 0; fmt[i] != '\0'; ++i) { + if (!CheckNonHeapValue(fmt[i], args[i])) { + return false; } } - va_end(ap); } + return true; + } + + bool CheckReflectedMethod(ScopedObjectAccess& soa, jobject jmethod) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::Object* method = soa.Decode<mirror::Object*>(jmethod); + if (method == nullptr) { + AbortF("expected non-null method"); + return false; + } + mirror::Class* c = method->GetClass(); + if (soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Method) != c && + soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Constructor) != c) { + AbortF("expected java.lang.reflect.Method or " + "java.lang.reflect.Constructor but got object of type %s: %p", + PrettyTypeOf(method).c_str(), jmethod); + return false; + } + return true; + } + + bool CheckConstructor(ScopedObjectAccess& soa, jmethodID mid) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::ArtMethod* method = soa.DecodeMethod(mid); + if (method == nullptr) { + AbortF("expected non-null constructor"); + return false; + } + if (!method->IsConstructor() || method->IsStatic()) { + AbortF("expected a constructor but %s: %p", PrettyTypeOf(method).c_str(), mid); + return false; + } + return true; + } + + bool CheckReflectedField(ScopedObjectAccess& soa, jobject jfield) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::Object* field = soa.Decode<mirror::Object*>(jfield); + if (field == nullptr) { + AbortF("expected non-null java.lang.reflect.Field"); + return false; + } + mirror::Class* c = field->GetClass(); + if (soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Field) != c) { + AbortF("expected java.lang.reflect.Field but got object of type %s: %p", + PrettyTypeOf(field).c_str(), jfield); + return false; + } + return true; + } + + bool CheckThrowable(ScopedObjectAccess& soa, jthrowable jobj) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::Object* obj = soa.Decode<mirror::Object*>(jobj); + if (!obj->GetClass()->IsThrowableClass()) { + AbortF("expected java.lang.Throwable but got object of type " + "%s: %p", PrettyTypeOf(obj).c_str(), obj); + return false; + } + return true; + } + + bool CheckThrowableClass(ScopedObjectAccess& soa, jclass jc) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::Class* c = soa.Decode<mirror::Class*>(jc); + if (!c->IsThrowableClass()) { + AbortF("expected java.lang.Throwable class but got object of " + "type %s: %p", PrettyDescriptor(c).c_str(), c); + return false; + } + return true; + } + + bool CheckReferenceKind(IndirectRefKind expected_kind, JavaVMExt* vm, Thread* self, jobject obj) { + IndirectRefKind found_kind; + if (expected_kind == kLocal) { + found_kind = GetIndirectRefKind(obj); + if (found_kind == kHandleScopeOrInvalid && self->HandleScopeContains(obj)) { + found_kind = kLocal; + } + } else { + found_kind = GetIndirectRefKind(obj); + } + if (obj != nullptr && found_kind != expected_kind) { + AbortF("expected reference of kind %s but found %s: %p", + ToStr<IndirectRefKind>(expected_kind).c_str(), + ToStr<IndirectRefKind>(GetIndirectRefKind(obj)).c_str(), + obj); + return false; + } + return true; + } + + bool CheckInstantiableNonArray(ScopedObjectAccess& soa, jclass jc) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::Class* c = soa.Decode<mirror::Class*>(jc); + if (!c->IsInstantiableNonArray()) { + AbortF("can't make objects of type %s: %p", PrettyDescriptor(c).c_str(), c); + return false; + } + return true; + } + + bool CheckPrimitiveArrayType(ScopedObjectAccess& soa, jarray array, Primitive::Type type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (!CheckArray(soa, array)) { + return false; + } + mirror::Array* a = soa.Decode<mirror::Array*>(array); + if (a->GetClass()->GetComponentType()->GetPrimitiveType() != type) { + AbortF("incompatible array type %s expected %s[]: %p", + PrettyDescriptor(a->GetClass()).c_str(), PrettyDescriptor(type).c_str(), array); + return false; + } + return true; + } + + bool CheckFieldAccess(ScopedObjectAccess& soa, jobject obj, jfieldID fid, bool is_static, + Primitive::Type type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (is_static && !CheckStaticFieldID(soa, down_cast<jclass>(obj), fid)) { + return false; + } + if (!is_static && !CheckInstanceFieldID(soa, obj, fid)) { + return false; + } + mirror::ArtField* field = soa.DecodeField(fid); + DCHECK(field != nullptr); // Already checked by Check. + if (is_static != field->IsStatic()) { + AbortF("attempt to access %s field %s: %p", + field->IsStatic() ? "static" : "non-static", PrettyField(field).c_str(), fid); + return false; + } + if (type != field->GetTypeAsPrimitiveType()) { + AbortF("attempt to access field %s of type %s with the wrong type %s: %p", + PrettyField(field).c_str(), PrettyDescriptor(field->GetTypeDescriptor()).c_str(), + PrettyDescriptor(type).c_str(), fid); + return false; + } + if (is_static) { + mirror::Object* o = soa.Decode<mirror::Object*>(obj); + if (o == nullptr || !o->IsClass()) { + AbortF("attempt to access static field %s with a class argument of type %s: %p", + PrettyField(field).c_str(), PrettyTypeOf(o).c_str(), fid); + return false; + } + mirror::Class* c = o->AsClass(); + if (field->GetDeclaringClass() != c) { + AbortF("attempt to access static field %s with an incompatible class argument of %s: %p", + PrettyField(field).c_str(), PrettyDescriptor(c).c_str(), fid); + return false; + } + } else { + mirror::Object* o = soa.Decode<mirror::Object*>(obj); + if (o == nullptr || !field->GetDeclaringClass()->IsAssignableFrom(o->GetClass())) { + AbortF("attempt to access field %s from an object argument of type %s: %p", + PrettyField(field).c_str(), PrettyTypeOf(o).c_str(), fid); + return false; + } + } + return true; } + private: enum InstanceKind { kClass, kDirectByteBuffer, @@ -598,7 +611,7 @@ class ScopedCheck { * Because we're looking at an object on the GC heap, we have to switch * to "running" mode before doing the checks. */ - bool CheckInstance(InstanceKind kind, jobject java_object) + bool CheckInstance(ScopedObjectAccess& soa, InstanceKind kind, jobject java_object, bool null_ok) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { const char* what = nullptr; switch (kind) { @@ -622,15 +635,20 @@ class ScopedCheck { } if (java_object == nullptr) { - JniAbortF(function_name_, "%s received null %s", function_name_, what); - return false; + if (null_ok) { + return true; + } else { + AbortF("%s received NULL %s", function_name_, what); + return false; + } } - mirror::Object* obj = soa_.Decode<mirror::Object*>(java_object); + mirror::Object* obj = soa.Decode<mirror::Object*>(java_object); if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(obj)) { Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR)); - JniAbortF(function_name_, "%s is an invalid %s: %p (%p)", - what, ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object, obj); + AbortF("%s is an invalid %s: %p (%p)", + what, ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), + java_object, obj); return false; } @@ -652,114 +670,333 @@ class ScopedCheck { break; } if (!okay) { - JniAbortF(function_name_, "%s has wrong type: %s", what, PrettyTypeOf(obj).c_str()); + AbortF("%s has wrong type: %s", what, PrettyTypeOf(obj).c_str()); return false; } return true; } - private: - // Set "has_method" to true if we have a valid thread with a method pointer. - // We won't have one before attaching a thread, after detaching a thread, or - // when shutting down the runtime. - void Init(int flags, const char* functionName, bool has_method) { - flags_ = flags; - function_name_ = functionName; - has_method_ = has_method; + /* + * Verify that the "mode" argument passed to a primitive array Release + * function is one of the valid values. + */ + bool CheckReleaseMode(jint mode) { + if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) { + AbortF("unknown value for release mode: %d", mode); + return false; + } + return true; + } + + bool CheckPossibleHeapValue(ScopedObjectAccess& soa, char fmt, JniValueType arg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + switch (fmt) { + case 'a': // jarray + return CheckArray(soa, arg.a); + case 'c': // jclass + return CheckInstance(soa, kClass, arg.c, false); + case 'f': // jfieldID + return CheckFieldID(soa, arg.f) != nullptr; + case 'm': // jmethodID + return CheckMethodID(soa, arg.m) != nullptr; + case 'r': // release int + return CheckReleaseMode(arg.r); + case 's': // jstring + return CheckInstance(soa, kString, arg.s, false); + case 't': // jthrowable + return CheckInstance(soa, kThrowable, arg.t, false); + case 'E': // JNIEnv* + return CheckThread(arg.E); + case 'L': // jobject + return CheckInstance(soa, kObject, arg.L, true); + default: + return CheckNonHeapValue(fmt, arg); + } + } + + bool CheckNonHeapValue(char fmt, JniValueType arg) { + switch (fmt) { + case '.': // ... + case 'p': // TODO: pointer - null or readable? + case 'v': // JavaVM* + case 'B': // jbyte + case 'C': // jchar + case 'D': // jdouble + case 'F': // jfloat + case 'I': // jint + case 'J': // jlong + case 'S': // jshort + break; // Ignored. + case 'b': // jboolean, why two? Fall-through. + case 'Z': + return CheckBoolean(arg.Z); + case 'u': // utf8 + if ((flags_ & kFlag_Release) != 0) { + return CheckNonNull(arg.u); + } else { + bool nullable = ((flags_ & kFlag_NullableUtf) != 0); + return CheckUtfString(arg.u, nullable); + } + case 'w': // jobjectRefType + switch (arg.w) { + case JNIInvalidRefType: + case JNILocalRefType: + case JNIGlobalRefType: + case JNIWeakGlobalRefType: + break; + default: + AbortF("Unknown reference type"); + return false; + } + break; + case 'z': // jsize + return CheckLengthPositive(arg.z); + default: + AbortF("unknown format specifier: '%c'", fmt); + return false; + } + return true; } + void TracePossibleHeapValue(ScopedObjectAccess& soa, bool entry, char fmt, JniValueType arg, + std::string* msg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + switch (fmt) { + case 'L': // jobject fall-through. + case 'a': // jarray fall-through. + case 's': // jstring fall-through. + case 't': // jthrowable fall-through. + if (arg.L == nullptr) { + *msg += "NULL"; + } else { + StringAppendF(msg, "%p", arg.L); + } + break; + case 'c': { // jclass + jclass jc = arg.c; + mirror::Class* c = soa.Decode<mirror::Class*>(jc); + if (c == nullptr) { + *msg += "NULL"; + } else if (c == kInvalidIndirectRefObject || + !Runtime::Current()->GetHeap()->IsValidObjectAddress(c)) { + StringAppendF(msg, "INVALID POINTER:%p", jc); + } else if (!c->IsClass()) { + *msg += "INVALID NON-CLASS OBJECT OF TYPE:" + PrettyTypeOf(c); + } else { + *msg += PrettyClass(c); + if (!entry) { + StringAppendF(msg, " (%p)", jc); + } + } + break; + } + case 'f': { // jfieldID + jfieldID fid = arg.f; + mirror::ArtField* f = soa.DecodeField(fid); + *msg += PrettyField(f); + if (!entry) { + StringAppendF(msg, " (%p)", fid); + } + break; + } + case 'm': { // jmethodID + jmethodID mid = arg.m; + mirror::ArtMethod* m = soa.DecodeMethod(mid); + *msg += PrettyMethod(m); + if (!entry) { + StringAppendF(msg, " (%p)", mid); + } + break; + } + default: + TraceNonHeapValue(fmt, arg, msg); + break; + } + } + + void TraceNonHeapValue(char fmt, JniValueType arg, std::string* msg) { + switch (fmt) { + case 'B': // jbyte + if (arg.B >= 0 && arg.B < 10) { + StringAppendF(msg, "%d", arg.B); + } else { + StringAppendF(msg, "%#x (%d)", arg.B, arg.B); + } + break; + case 'C': // jchar + if (arg.C < 0x7f && arg.C >= ' ') { + StringAppendF(msg, "U+%x ('%c')", arg.C, arg.C); + } else { + StringAppendF(msg, "U+%x", arg.C); + } + break; + case 'F': // jfloat + StringAppendF(msg, "%g", arg.F); + break; + case 'D': // jdouble + StringAppendF(msg, "%g", arg.D); + break; + case 'S': // jshort + StringAppendF(msg, "%d", arg.S); + break; + case 'i': // jint - fall-through. + case 'I': // jint + StringAppendF(msg, "%d", arg.I); + break; + case 'J': // jlong + StringAppendF(msg, "%" PRId64, arg.J); + break; + case 'Z': // jboolean + case 'b': // jboolean (JNI-style) + *msg += arg.b == JNI_TRUE ? "true" : "false"; + break; + case 'V': // void + DCHECK(arg.V == nullptr); + *msg += "void"; + break; + case 'v': // JavaVM* + StringAppendF(msg, "(JavaVM*)%p", arg.v); + break; + case 'E': + StringAppendF(msg, "(JNIEnv*)%p", arg.E); + break; + case 'z': // non-negative jsize + // You might expect jsize to be size_t, but it's not; it's the same as jint. + // We only treat this specially so we can do the non-negative check. + // TODO: maybe this wasn't worth it? + StringAppendF(msg, "%d", arg.z); + break; + case 'p': // void* ("pointer") + if (arg.p == nullptr) { + *msg += "NULL"; + } else { + StringAppendF(msg, "(void*) %p", arg.p); + } + break; + case 'r': { // jint (release mode) + jint releaseMode = arg.r; + if (releaseMode == 0) { + *msg += "0"; + } else if (releaseMode == JNI_ABORT) { + *msg += "JNI_ABORT"; + } else if (releaseMode == JNI_COMMIT) { + *msg += "JNI_COMMIT"; + } else { + StringAppendF(msg, "invalid release mode %d", releaseMode); + } + break; + } + case 'u': // const char* (Modified UTF-8) + if (arg.u == nullptr) { + *msg += "NULL"; + } else { + StringAppendF(msg, "\"%s\"", arg.u); + } + break; + case 'w': // jobjectRefType + switch (arg.w) { + case JNIInvalidRefType: + *msg += "invalid reference type"; + break; + case JNILocalRefType: + *msg += "local ref type"; + break; + case JNIGlobalRefType: + *msg += "global ref type"; + break; + case JNIWeakGlobalRefType: + *msg += "weak global ref type"; + break; + default: + *msg += "unknown ref type"; + break; + } + break; + case '.': + *msg += "..."; + break; + default: + LOG(FATAL) << function_name_ << ": unknown trace format specifier: '" << fmt << "'"; + } + } /* * Verify that "array" is non-NULL and points to an Array object. * * Since we're dealing with objects, switch to "running" mode. */ - void CheckArray(jarray java_array) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - if (java_array == nullptr) { - JniAbortF(function_name_, "jarray was NULL"); - return; + bool CheckArray(ScopedObjectAccess& soa, jarray java_array) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (UNLIKELY(java_array == nullptr)) { + AbortF("jarray was NULL"); + return false; } - mirror::Array* a = soa_.Decode<mirror::Array*>(java_array); - if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(a)) { + mirror::Array* a = soa.Decode<mirror::Array*>(java_array); + if (UNLIKELY(!Runtime::Current()->GetHeap()->IsValidObjectAddress(a))) { Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR)); - JniAbortF(function_name_, "jarray is an invalid %s: %p (%p)", - ToStr<IndirectRefKind>(GetIndirectRefKind(java_array)).c_str(), java_array, a); + AbortF("jarray is an invalid %s: %p (%p)", + ToStr<IndirectRefKind>(GetIndirectRefKind(java_array)).c_str(), + java_array, a); + return false; } else if (!a->IsArrayInstance()) { - JniAbortF(function_name_, "jarray argument has non-array type: %s", PrettyTypeOf(a).c_str()); + AbortF("jarray argument has non-array type: %s", PrettyTypeOf(a).c_str()); + return false; + } + return true; + } + + bool CheckBoolean(jboolean z) { + if (z != JNI_TRUE && z != JNI_FALSE) { + AbortF("unexpected jboolean value: %d", z); + return false; } + return true; } - void CheckLengthPositive(jsize length) { + bool CheckLengthPositive(jsize length) { if (length < 0) { - JniAbortF(function_name_, "negative jsize: %d", length); + AbortF("negative jsize: %d", length); + return false; } + return true; } - mirror::ArtField* CheckFieldID(jfieldID fid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::ArtField* CheckFieldID(ScopedObjectAccess& soa, jfieldID fid) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { if (fid == nullptr) { - JniAbortF(function_name_, "jfieldID was NULL"); + AbortF("jfieldID was NULL"); return nullptr; } - mirror::ArtField* f = soa_.DecodeField(fid); + mirror::ArtField* f = soa.DecodeField(fid); if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(f) || !f->IsArtField()) { Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR)); - JniAbortF(function_name_, "invalid jfieldID: %p", fid); + AbortF("invalid jfieldID: %p", fid); return nullptr; } return f; } - mirror::ArtMethod* CheckMethodID(jmethodID mid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::ArtMethod* CheckMethodID(ScopedObjectAccess& soa, jmethodID mid) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { if (mid == nullptr) { - JniAbortF(function_name_, "jmethodID was NULL"); + AbortF("jmethodID was NULL"); return nullptr; } - mirror::ArtMethod* m = soa_.DecodeMethod(mid); + mirror::ArtMethod* m = soa.DecodeMethod(mid); if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(m) || !m->IsArtMethod()) { Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR)); - JniAbortF(function_name_, "invalid jmethodID: %p", mid); + AbortF("invalid jmethodID: %p", mid); return nullptr; } return m; } - /* - * Verify that "jobj" is a valid object, and that it's an object that JNI - * is allowed to know about. We allow NULL references. - * - * Switches to "running" mode before performing checks. - */ - void CheckObject(jobject java_object) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - if (java_object == nullptr) { - return; - } - - mirror::Object* o = soa_.Decode<mirror::Object*>(java_object); - if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) { - Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR)); - // TODO: when we remove work_around_app_jni_bugs, this should be impossible. - JniAbortF(function_name_, "native code passing in reference to invalid %s: %p", - ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object); - } - } - - /* - * Verify that the "mode" argument passed to a primitive array Release - * function is one of the valid values. - */ - void CheckReleaseMode(jint mode) { - if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) { - JniAbortF(function_name_, "unknown value for release mode: %d", mode); - } - } - - void CheckThread(int flags) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + bool CheckThread(JNIEnv* env) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { Thread* self = Thread::Current(); if (self == nullptr) { - JniAbortF(function_name_, "a thread (tid %d) is making JNI calls without being attached", GetTid()); - return; + AbortF("a thread (tid %d) is making JNI calls without being attached", GetTid()); + return false; } // Get the *correct* JNIEnv by going through our TLS pointer. @@ -767,21 +1004,22 @@ class ScopedCheck { // Verify that the current thread is (a) attached and (b) associated with // this particular instance of JNIEnv. - if (soa_.Env() != threadEnv) { - JniAbortF(function_name_, "thread %s using JNIEnv* from thread %s", - ToStr<Thread>(*self).c_str(), ToStr<Thread>(*soa_.Self()).c_str()); - return; + if (env != threadEnv) { + AbortF("thread %s using JNIEnv* from thread %s", + ToStr<Thread>(*self).c_str(), ToStr<Thread>(*self).c_str()); + return false; } // Verify that, if this thread previously made a critical "get" call, we // do the corresponding "release" call before we try anything else. - switch (flags & kFlag_CritMask) { + switch (flags_ & kFlag_CritMask) { case kFlag_CritOkay: // okay to call this method break; case kFlag_CritBad: // not okay to call if (threadEnv->critical) { - JniAbortF(function_name_, "thread %s using JNI after critical get", ToStr<Thread>(*self).c_str()); - return; + AbortF("thread %s using JNI after critical get", + ToStr<Thread>(*self).c_str()); + return false; } break; case kFlag_CritGet: // this is a "get" call @@ -791,44 +1029,46 @@ class ScopedCheck { case kFlag_CritRelease: // this is a "release" call threadEnv->critical--; if (threadEnv->critical < 0) { - JniAbortF(function_name_, "thread %s called too many critical releases", ToStr<Thread>(*self).c_str()); - return; + AbortF("thread %s called too many critical releases", + ToStr<Thread>(*self).c_str()); + return false; } break; default: - LOG(FATAL) << "Bad flags (internal error): " << flags; + LOG(FATAL) << "Bad flags (internal error): " << flags_; } // Verify that, if an exception has been raised, the native code doesn't // make any JNI calls other than the Exception* methods. - if ((flags & kFlag_ExcepOkay) == 0 && self->IsExceptionPending()) { + if ((flags_ & kFlag_ExcepOkay) == 0 && self->IsExceptionPending()) { ThrowLocation throw_location; mirror::Throwable* exception = self->GetException(&throw_location); std::string type(PrettyTypeOf(exception)); - JniAbortF(function_name_, "JNI %s called with pending exception '%s' thrown in %s", - function_name_, type.c_str(), throw_location.Dump().c_str()); - return; + AbortF("JNI %s called with pending exception '%s' thrown in %s", + function_name_, type.c_str(), throw_location.Dump().c_str()); + return false; } + return true; } // Verifies that "bytes" points to valid Modified UTF-8 data. - void CheckUtfString(const char* bytes, bool nullable) { + bool CheckUtfString(const char* bytes, bool nullable) { if (bytes == nullptr) { if (!nullable) { - JniAbortF(function_name_, "non-nullable const char* was NULL"); - return; + AbortF("non-nullable const char* was NULL"); + return false; } - return; + return true; } const char* errorKind = nullptr; uint8_t utf8 = CheckUtfBytes(bytes, &errorKind); if (errorKind != nullptr) { - JniAbortF(function_name_, - "input is not valid Modified UTF-8: illegal %s byte %#x\n" - " string: '%s'", errorKind, utf8, bytes); - return; + AbortF("input is not valid Modified UTF-8: illegal %s byte %#x\n" + " string: '%s'", errorKind, utf8, bytes); + return false; } + return true; } static uint8_t CheckUtfBytes(const char* bytes, const char** errorKind) { @@ -880,92 +1120,120 @@ class ScopedCheck { return 0; } - const ScopedObjectAccess soa_; - const char* function_name_; - int flags_; - bool has_method_; + void AbortF(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) { + va_list args; + va_start(args, fmt); + Runtime::Current()->GetJavaVM()->JniAbortV(function_name_, fmt, args); + va_end(args); + } + + // The name of the JNI function being checked. + const char* const function_name_; + + const int flags_; int indent_; + const bool has_method_; + DISALLOW_COPY_AND_ASSIGN(ScopedCheck); }; -#define CHECK_JNI_ENTRY(flags, types, args...) \ - ScopedCheck sc(env, flags, __FUNCTION__); \ - sc.Check(true, types, ##args) - -#define CHECK_JNI_EXIT(type, exp) ({ \ - auto _rc = (exp); \ - sc.Check(false, type, _rc); \ - _rc; }) -#define CHECK_JNI_EXIT_VOID() \ - sc.Check(false, "V") - /* * =========================================================================== * Guarded arrays * =========================================================================== */ -#define kGuardLen 512 /* must be multiple of 2 */ -#define kGuardPattern 0xd5e3 /* uncommon values; d5e3d5e3 invalid addr */ -#define kGuardMagic 0xffd5aa96 - /* this gets tucked in at the start of the buffer; struct size must be even */ -struct GuardedCopy { - uint32_t magic; - uLong adler; - size_t original_length; - const void* original_ptr; - - /* find the GuardedCopy given the pointer into the "live" data */ - static inline const GuardedCopy* FromData(const void* dataBuf) { - return reinterpret_cast<const GuardedCopy*>(ActualBuffer(dataBuf)); - } - +class GuardedCopy { + public: /* * Create an over-sized buffer to hold the contents of "buf". Copy it in, * filling in the area around it with guard data. - * - * We use a 16-bit pattern to make a rogue memset less likely to elude us. */ - static void* Create(const void* buf, size_t len, bool modOkay) { - size_t newLen = ActualLength(len); - uint8_t* newBuf = DebugAlloc(newLen); + static void* Create(const void* original_buf, size_t len, bool mod_okay) { + const size_t new_len = LengthIncludingRedZones(len); + uint8_t* const new_buf = DebugAlloc(new_len); - // Fill it in with a pattern. - uint16_t* pat = reinterpret_cast<uint16_t*>(newBuf); - for (size_t i = 0; i < newLen / 2; i++) { - *pat++ = kGuardPattern; + // If modification is not expected, grab a checksum. + uLong adler = 0; + if (!mod_okay) { + adler = adler32(adler32(0L, Z_NULL, 0), reinterpret_cast<const Bytef*>(original_buf), len); + } + + GuardedCopy* copy = new (new_buf) GuardedCopy(original_buf, len, adler); + + // Fill begin region with canary pattern. + const size_t kStartCanaryLength = (GuardedCopy::kRedZoneSize / 2) - sizeof(GuardedCopy); + for (size_t i = 0, j = 0; i < kStartCanaryLength; ++i) { + const_cast<char*>(copy->StartRedZone())[i] = kCanary[j]; + if (kCanary[j] == '\0') { + j = 0; + } } // Copy the data in; note "len" could be zero. - memcpy(newBuf + kGuardLen / 2, buf, len); + memcpy(const_cast<uint8_t*>(copy->BufferWithinRedZones()), original_buf, len); - // If modification is not expected, grab a checksum. - uLong adler = 0; - if (!modOkay) { - adler = adler32(0L, Z_NULL, 0); - adler = adler32(adler, reinterpret_cast<const Bytef*>(buf), len); - *reinterpret_cast<uLong*>(newBuf) = adler; + // Fill end region with canary pattern. + for (size_t i = 0, j = 0; i < kEndCanaryLength; ++i) { + const_cast<char*>(copy->EndRedZone())[i] = kCanary[j]; + if (kCanary[j] == '\0') { + j = 0; + } } - GuardedCopy* pExtra = reinterpret_cast<GuardedCopy*>(newBuf); - pExtra->magic = kGuardMagic; - pExtra->adler = adler; - pExtra->original_ptr = buf; - pExtra->original_length = len; + return const_cast<uint8_t*>(copy->BufferWithinRedZones()); + } - return newBuf + kGuardLen / 2; + /* + * Create a guarded copy of a primitive array. Modifications to the copied + * data are allowed. Returns a pointer to the copied data. + */ + static void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* is_copy) { + ScopedObjectAccess soa(env); + + mirror::Array* a = soa.Decode<mirror::Array*>(java_array); + size_t component_size = a->GetClass()->GetComponentSize(); + size_t byte_count = a->GetLength() * component_size; + void* result = Create(a->GetRawData(component_size, 0), byte_count, true); + if (is_copy != nullptr) { + *is_copy = JNI_TRUE; + } + return result; } /* + * Perform the array "release" operation, which may or may not copy data + * back into the managed heap, and may or may not release the underlying storage. + */ + static void* ReleaseGuardedPACopy(const char* function_name, JNIEnv* env, jarray java_array, + void* embedded_buf, int mode) { + ScopedObjectAccess soa(env); + mirror::Array* a = soa.Decode<mirror::Array*>(java_array); + + if (!GuardedCopy::Check(function_name, embedded_buf, true)) { + return nullptr; + } + if (mode != JNI_ABORT) { + size_t len = FromEmbedded(embedded_buf)->original_length_; + memcpy(a->GetRawData(a->GetClass()->GetComponentSize(), 0), embedded_buf, len); + } + if (mode != JNI_COMMIT) { + return Destroy(embedded_buf); + } + return embedded_buf; + } + + + /* * Free up the guard buffer, scrub it, and return the original pointer. */ - static void* Destroy(void* dataBuf) { - const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf); - void* original_ptr = const_cast<void*>(pExtra->original_ptr); - size_t len = pExtra->original_length; - DebugFree(dataBuf, len); + static void* Destroy(void* embedded_buf) { + GuardedCopy* copy = FromEmbedded(embedded_buf); + void* original_ptr = const_cast<void*>(copy->original_ptr_); + size_t len = LengthIncludingRedZones(copy->original_length_); + DebugFree(copy, len); return original_ptr; } @@ -975,137 +1243,144 @@ struct GuardedCopy { * * The caller has already checked that "dataBuf" is non-NULL. */ - static void Check(const char* functionName, const void* dataBuf, bool modOkay) { + static bool Check(const char* function_name, const void* embedded_buf, bool mod_okay) { + const GuardedCopy* copy = FromEmbedded(embedded_buf); + return copy->CheckHeader(function_name, mod_okay) && copy->CheckRedZones(function_name); + } + + private: + GuardedCopy(const void* original_buf, size_t len, uLong adler) : + magic_(kGuardMagic), adler_(adler), original_ptr_(original_buf), original_length_(len) { + } + + static uint8_t* DebugAlloc(size_t len) { + void* result = mmap(nullptr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); + if (result == MAP_FAILED) { + PLOG(FATAL) << "GuardedCopy::create mmap(" << len << ") failed"; + } + return reinterpret_cast<uint8_t*>(result); + } + + static void DebugFree(void* buf, size_t len) { + if (munmap(buf, len) != 0) { + PLOG(FATAL) << "munmap(" << buf << ", " << len << ") failed"; + } + } + + static size_t LengthIncludingRedZones(size_t len) { + return len + kRedZoneSize; + } + + // Get the GuardedCopy from the interior pointer. + static GuardedCopy* FromEmbedded(void* embedded_buf) { + return reinterpret_cast<GuardedCopy*>( + reinterpret_cast<uint8_t*>(embedded_buf) - (kRedZoneSize / 2)); + } + + static const GuardedCopy* FromEmbedded(const void* embedded_buf) { + return reinterpret_cast<const GuardedCopy*>( + reinterpret_cast<const uint8_t*>(embedded_buf) - (kRedZoneSize / 2)); + } + + static void AbortF(const char* jni_function_name, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + Runtime::Current()->GetJavaVM()->JniAbortV(jni_function_name, fmt, args); + va_end(args); + } + + bool CheckHeader(const char* function_name, bool mod_okay) const { static const uint32_t kMagicCmp = kGuardMagic; - const uint8_t* fullBuf = ActualBuffer(dataBuf); - const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf); // Before we do anything with "pExtra", check the magic number. We // do the check with memcmp rather than "==" in case the pointer is // unaligned. If it points to completely bogus memory we're going // to crash, but there's no easy way around that. - if (memcmp(&pExtra->magic, &kMagicCmp, 4) != 0) { + if (UNLIKELY(memcmp(&magic_, &kMagicCmp, 4) != 0)) { uint8_t buf[4]; - memcpy(buf, &pExtra->magic, 4); - JniAbortF(functionName, - "guard magic does not match (found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?", - buf[3], buf[2], buf[1], buf[0], dataBuf); // Assumes little-endian. + memcpy(buf, &magic_, 4); + AbortF(function_name, + "guard magic does not match (found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?", + buf[3], buf[2], buf[1], buf[0], this); // Assumes little-endian. + return false; } - size_t len = pExtra->original_length; - - // Check bottom half of guard; skip over optional checksum storage. - const uint16_t* pat = reinterpret_cast<const uint16_t*>(fullBuf); - for (size_t i = sizeof(GuardedCopy) / 2; i < (kGuardLen / 2 - sizeof(GuardedCopy)) / 2; i++) { - if (pat[i] != kGuardPattern) { - JniAbortF(functionName, "guard pattern(1) disturbed at %p +%zd", fullBuf, i*2); + // If modification is not expected, verify checksum. Strictly speaking this is wrong: if we + // told the client that we made a copy, there's no reason they can't alter the buffer. + if (!mod_okay) { + uLong computed_adler = + adler32(adler32(0L, Z_NULL, 0), BufferWithinRedZones(), original_length_); + if (computed_adler != adler_) { + AbortF(function_name, "buffer modified (0x%08lx vs 0x%08lx) at address %p", + computed_adler, adler_, this); + return false; } } + return true; + } - int offset = kGuardLen / 2 + len; - if (offset & 0x01) { - // Odd byte; expected value depends on endian. - const uint16_t patSample = kGuardPattern; - uint8_t expected_byte = reinterpret_cast<const uint8_t*>(&patSample)[1]; - if (fullBuf[offset] != expected_byte) { - JniAbortF(functionName, "guard pattern disturbed in odd byte after %p +%d 0x%02x 0x%02x", - fullBuf, offset, fullBuf[offset], expected_byte); + bool CheckRedZones(const char* function_name) const { + // Check the begin red zone. + const size_t kStartCanaryLength = (GuardedCopy::kRedZoneSize / 2) - sizeof(GuardedCopy); + for (size_t i = 0, j = 0; i < kStartCanaryLength; ++i) { + if (UNLIKELY(StartRedZone()[i] != kCanary[j])) { + AbortF(function_name, "guard pattern before buffer disturbed at %p +%zd", this, i); + return false; } - offset++; - } - - // Check top half of guard. - pat = reinterpret_cast<const uint16_t*>(fullBuf + offset); - for (size_t i = 0; i < kGuardLen / 4; i++) { - if (pat[i] != kGuardPattern) { - JniAbortF(functionName, "guard pattern(2) disturbed at %p +%zd", fullBuf, offset + i*2); + if (kCanary[j] == '\0') { + j = 0; } } - // If modification is not expected, verify checksum. Strictly speaking - // this is wrong: if we told the client that we made a copy, there's no - // reason they can't alter the buffer. - if (!modOkay) { - uLong adler = adler32(0L, Z_NULL, 0); - adler = adler32(adler, (const Bytef*)dataBuf, len); - if (pExtra->adler != adler) { - JniAbortF(functionName, "buffer modified (0x%08lx vs 0x%08lx) at address %p", - pExtra->adler, adler, dataBuf); + // Check end region. + for (size_t i = 0, j = 0; i < kEndCanaryLength; ++i) { + if (UNLIKELY(EndRedZone()[i] != kCanary[j])) { + size_t offset_from_buffer_start = + &(EndRedZone()[i]) - &(StartRedZone()[kStartCanaryLength]); + AbortF(function_name, "guard pattern after buffer disturbed at %p +%zd", this, + offset_from_buffer_start); + return false; + } + if (kCanary[j] == '\0') { + j = 0; } } + return true; } - private: - static uint8_t* DebugAlloc(size_t len) { - void* result = mmap(nullptr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); - if (result == MAP_FAILED) { - PLOG(FATAL) << "GuardedCopy::create mmap(" << len << ") failed"; - } - return reinterpret_cast<uint8_t*>(result); - } - - static void DebugFree(void* dataBuf, size_t len) { - uint8_t* fullBuf = ActualBuffer(dataBuf); - size_t totalByteCount = ActualLength(len); - // TODO: we could mprotect instead, and keep the allocation around for a while. - // This would be even more expensive, but it might catch more errors. - // if (mprotect(fullBuf, totalByteCount, PROT_NONE) != 0) { - // PLOG(WARNING) << "mprotect(PROT_NONE) failed"; - // } - if (munmap(fullBuf, totalByteCount) != 0) { - PLOG(FATAL) << "munmap(" << reinterpret_cast<void*>(fullBuf) << ", " << totalByteCount << ") failed"; - } + // Location that canary value will be written before the guarded region. + const char* StartRedZone() const { + const uint8_t* buf = reinterpret_cast<const uint8_t*>(this); + return reinterpret_cast<const char*>(buf + sizeof(GuardedCopy)); } - static const uint8_t* ActualBuffer(const void* dataBuf) { - return reinterpret_cast<const uint8_t*>(dataBuf) - kGuardLen / 2; + // Return the interior embedded buffer. + const uint8_t* BufferWithinRedZones() const { + const uint8_t* embedded_buf = reinterpret_cast<const uint8_t*>(this) + (kRedZoneSize / 2); + return embedded_buf; } - static uint8_t* ActualBuffer(void* dataBuf) { - return reinterpret_cast<uint8_t*>(dataBuf) - kGuardLen / 2; + // Location that canary value will be written after the guarded region. + const char* EndRedZone() const { + const uint8_t* buf = reinterpret_cast<const uint8_t*>(this); + size_t buf_len = LengthIncludingRedZones(original_length_); + return reinterpret_cast<const char*>(buf + (buf_len - (kRedZoneSize / 2))); } - // Underlying length of a user allocation of 'length' bytes. - static size_t ActualLength(size_t length) { - return (length + kGuardLen + 1) & ~0x01; - } -}; + static constexpr size_t kRedZoneSize = 512; + static constexpr size_t kEndCanaryLength = kRedZoneSize / 2; -/* - * Create a guarded copy of a primitive array. Modifications to the copied - * data are allowed. Returns a pointer to the copied data. - */ -static void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* isCopy) { - ScopedObjectAccess soa(env); - - mirror::Array* a = soa.Decode<mirror::Array*>(java_array); - size_t component_size = a->GetClass()->GetComponentSize(); - size_t byte_count = a->GetLength() * component_size; - void* result = GuardedCopy::Create(a->GetRawData(component_size, 0), byte_count, true); - if (isCopy != nullptr) { - *isCopy = JNI_TRUE; - } - return result; -} + // Value written before and after the guarded array. + static const char* const kCanary; -/* - * Perform the array "release" operation, which may or may not copy data - * back into the managed heap, and may or may not release the underlying storage. - */ -static void ReleaseGuardedPACopy(JNIEnv* env, jarray java_array, void* dataBuf, int mode) { - ScopedObjectAccess soa(env); - mirror::Array* a = soa.Decode<mirror::Array*>(java_array); + static constexpr uint32_t kGuardMagic = 0xffd5aa96; - GuardedCopy::Check(__FUNCTION__, dataBuf, true); - - if (mode != JNI_ABORT) { - size_t len = GuardedCopy::FromData(dataBuf)->original_length; - memcpy(a->GetRawData(a->GetClass()->GetComponentSize(), 0), dataBuf, len); - } - if (mode != JNI_COMMIT) { - GuardedCopy::Destroy(dataBuf); - } -} + const uint32_t magic_; + const uLong adler_; + const void* const original_ptr_; + const size_t original_length_; +}; +const char* const GuardedCopy::kCanary = "JNI BUFFER RED ZONE"; /* * =========================================================================== @@ -1116,667 +1391,1953 @@ static void ReleaseGuardedPACopy(JNIEnv* env, jarray java_array, void* dataBuf, class CheckJNI { public: static jint GetVersion(JNIEnv* env) { - CHECK_JNI_ENTRY(kFlag_Default, "E", env); - return CHECK_JNI_EXIT("I", baseEnv(env)->GetVersion(env)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[1] = {{.E = env }}; + if (sc.Check(soa, true, "E", args)) { + JniValueType result; + result.I = baseEnv(env)->GetVersion(env); + if (sc.Check(soa, false, "I", &result)) { + return result.I; + } + } + return JNI_ERR; + } + + static jint GetJavaVM(JNIEnv *env, JavaVM **vm) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env }, {.p = vm}}; + if (sc.Check(soa, true, "Ep", args)) { + JniValueType result; + result.i = baseEnv(env)->GetJavaVM(env, vm); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } + } + return JNI_ERR; + } + + static jint RegisterNatives(JNIEnv* env, jclass c, const JNINativeMethod* methods, jint nMethods) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[4] = {{.E = env }, {.c = c}, {.p = methods}, {.I = nMethods}}; + if (sc.Check(soa, true, "EcpI", args)) { + JniValueType result; + result.i = baseEnv(env)->RegisterNatives(env, c, methods, nMethods); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } + } + return JNI_ERR; + } + + static jint UnregisterNatives(JNIEnv* env, jclass c) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env }, {.c = c}}; + if (sc.Check(soa, true, "Ec", args)) { + JniValueType result; + result.i = baseEnv(env)->UnregisterNatives(env, c); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } + } + return JNI_ERR; + } + + static jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj) { + // Note: we use "Ep" rather than "EL" because this is the one JNI function that it's okay to + // pass an invalid reference to. + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env }, {.p = obj}}; + if (sc.Check(soa, true, "Ep", args)) { + JniValueType result; + result.w = baseEnv(env)->GetObjectRefType(env, obj); + if (sc.Check(soa, false, "w", &result)) { + return result.w; + } + } + return JNIInvalidRefType; } - static jclass DefineClass(JNIEnv* env, const char* name, jobject loader, const jbyte* buf, jsize bufLen) { - CHECK_JNI_ENTRY(kFlag_Default, "EuLpz", env, name, loader, buf, bufLen); - sc.CheckClassName(name); - return CHECK_JNI_EXIT("c", baseEnv(env)->DefineClass(env, name, loader, buf, bufLen)); + static jclass DefineClass(JNIEnv* env, const char* name, jobject loader, const jbyte* buf, + jsize bufLen) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[5] = {{.E = env}, {.u = name}, {.L = loader}, {.p = buf}, {.z = bufLen}}; + if (sc.Check(soa, true, "EuLpz", args) && sc.CheckClassName(name)) { + JniValueType result; + result.c = baseEnv(env)->DefineClass(env, name, loader, buf, bufLen); + if (sc.Check(soa, false, "c", &result)) { + return result.c; + } + } + return nullptr; } static jclass FindClass(JNIEnv* env, const char* name) { - CHECK_JNI_ENTRY(kFlag_Default, "Eu", env, name); - sc.CheckClassName(name); - return CHECK_JNI_EXIT("c", baseEnv(env)->FindClass(env, name)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.u = name}}; + if (sc.Check(soa, true, "Eu", args) && sc.CheckClassName(name)) { + JniValueType result; + result.c = baseEnv(env)->FindClass(env, name); + if (sc.Check(soa, false, "c", &result)) { + return result.c; + } + } + return nullptr; } static jclass GetSuperclass(JNIEnv* env, jclass c) { - CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c); - return CHECK_JNI_EXIT("c", baseEnv(env)->GetSuperclass(env, c)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.c = c}}; + if (sc.Check(soa, true, "Ec", args)) { + JniValueType result; + result.c = baseEnv(env)->GetSuperclass(env, c); + if (sc.Check(soa, false, "c", &result)) { + return result.c; + } + } + return nullptr; } static jboolean IsAssignableFrom(JNIEnv* env, jclass c1, jclass c2) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecc", env, c1, c2); - return CHECK_JNI_EXIT("b", baseEnv(env)->IsAssignableFrom(env, c1, c2)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.c = c1}, {.c = c2}}; + if (sc.Check(soa, true, "Ecc", args)) { + JniValueType result; + result.b = baseEnv(env)->IsAssignableFrom(env, c1, c2); + if (sc.Check(soa, false, "b", &result)) { + return result.b; + } + } + return JNI_FALSE; } static jmethodID FromReflectedMethod(JNIEnv* env, jobject method) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, method); - // TODO: check that 'field' is a java.lang.reflect.Method. - return CHECK_JNI_EXIT("m", baseEnv(env)->FromReflectedMethod(env, method)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.L = method}}; + if (sc.Check(soa, true, "EL", args) && sc.CheckReflectedMethod(soa, method)) { + JniValueType result; + result.m = baseEnv(env)->FromReflectedMethod(env, method); + if (sc.Check(soa, false, "m", &result)) { + return result.m; + } + } + return nullptr; } static jfieldID FromReflectedField(JNIEnv* env, jobject field) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, field); - // TODO: check that 'field' is a java.lang.reflect.Field. - return CHECK_JNI_EXIT("f", baseEnv(env)->FromReflectedField(env, field)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.L = field}}; + if (sc.Check(soa, true, "EL", args) && sc.CheckReflectedField(soa, field)) { + JniValueType result; + result.f = baseEnv(env)->FromReflectedField(env, field); + if (sc.Check(soa, false, "f", &result)) { + return result.f; + } + } + return nullptr; } static jobject ToReflectedMethod(JNIEnv* env, jclass cls, jmethodID mid, jboolean isStatic) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecmb", env, cls, mid, isStatic); - return CHECK_JNI_EXIT("L", baseEnv(env)->ToReflectedMethod(env, cls, mid, isStatic)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[4] = {{.E = env}, {.c = cls}, {.m = mid}, {.b = isStatic}}; + if (sc.Check(soa, true, "Ecmb", args)) { + JniValueType result; + result.L = baseEnv(env)->ToReflectedMethod(env, cls, mid, isStatic); + if (sc.Check(soa, false, "L", &result) && (result.L != nullptr)) { + DCHECK(sc.CheckReflectedMethod(soa, result.L)); + return result.L; + } + } + return nullptr; } static jobject ToReflectedField(JNIEnv* env, jclass cls, jfieldID fid, jboolean isStatic) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecfb", env, cls, fid, isStatic); - return CHECK_JNI_EXIT("L", baseEnv(env)->ToReflectedField(env, cls, fid, isStatic)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[4] = {{.E = env}, {.c = cls}, {.f = fid}, {.b = isStatic}}; + if (sc.Check(soa, true, "Ecfb", args)) { + JniValueType result; + result.L = baseEnv(env)->ToReflectedField(env, cls, fid, isStatic); + if (sc.Check(soa, false, "L", &result) && (result.L != nullptr)) { + DCHECK(sc.CheckReflectedField(soa, result.L)); + return result.L; + } + } + return nullptr; } static jint Throw(JNIEnv* env, jthrowable obj) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); - // TODO: check that 'obj' is a java.lang.Throwable. - return CHECK_JNI_EXIT("I", baseEnv(env)->Throw(env, obj)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.t = obj}}; + if (sc.Check(soa, true, "Et", args) && sc.CheckThrowable(soa, obj)) { + JniValueType result; + result.i = baseEnv(env)->Throw(env, obj); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } + } + return JNI_ERR; } static jint ThrowNew(JNIEnv* env, jclass c, const char* message) { - CHECK_JNI_ENTRY(kFlag_NullableUtf, "Ecu", env, c, message); - return CHECK_JNI_EXIT("I", baseEnv(env)->ThrowNew(env, c, message)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_NullableUtf, __FUNCTION__); + JniValueType args[5] = {{.E = env}, {.c = c}, {.u = message}}; + if (sc.Check(soa, true, "Ecu", args) && sc.CheckThrowableClass(soa, c)) { + JniValueType result; + result.i = baseEnv(env)->ThrowNew(env, c, message); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } + } + return JNI_ERR; } static jthrowable ExceptionOccurred(JNIEnv* env) { - CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env); - return CHECK_JNI_EXIT("L", baseEnv(env)->ExceptionOccurred(env)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__); + JniValueType args[1] = {{.E = env}}; + if (sc.Check(soa, true, "E", args)) { + JniValueType result; + result.t = baseEnv(env)->ExceptionOccurred(env); + if (sc.Check(soa, false, "t", &result)) { + return result.t; + } + } + return nullptr; } static void ExceptionDescribe(JNIEnv* env) { - CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env); - baseEnv(env)->ExceptionDescribe(env); - CHECK_JNI_EXIT_VOID(); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__); + JniValueType args[1] = {{.E = env}}; + if (sc.Check(soa, true, "E", args)) { + JniValueType result; + baseEnv(env)->ExceptionDescribe(env); + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } } static void ExceptionClear(JNIEnv* env) { - CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env); - baseEnv(env)->ExceptionClear(env); - CHECK_JNI_EXIT_VOID(); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__); + JniValueType args[1] = {{.E = env}}; + if (sc.Check(soa, true, "E", args)) { + JniValueType result; + baseEnv(env)->ExceptionClear(env); + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } + } + + static jboolean ExceptionCheck(JNIEnv* env) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_CritOkay | kFlag_ExcepOkay, __FUNCTION__); + JniValueType args[1] = {{.E = env}}; + if (sc.Check(soa, true, "E", args)) { + JniValueType result; + result.b = baseEnv(env)->ExceptionCheck(env); + if (sc.Check(soa, false, "b", &result)) { + return result.b; + } + } + return JNI_FALSE; } static void FatalError(JNIEnv* env, const char* msg) { // The JNI specification doesn't say it's okay to call FatalError with a pending exception, // but you're about to abort anyway, and it's quite likely that you have a pending exception, // and it's not unimaginable that you don't know that you do. So we allow it. - CHECK_JNI_ENTRY(kFlag_ExcepOkay | kFlag_NullableUtf, "Eu", env, msg); - baseEnv(env)->FatalError(env, msg); - CHECK_JNI_EXIT_VOID(); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay | kFlag_NullableUtf, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.u = msg}}; + if (sc.Check(soa, true, "Eu", args)) { + JniValueType result; + baseEnv(env)->FatalError(env, msg); + // Unreachable. + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } } static jint PushLocalFrame(JNIEnv* env, jint capacity) { - CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EI", env, capacity); - return CHECK_JNI_EXIT("I", baseEnv(env)->PushLocalFrame(env, capacity)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.I = capacity}}; + if (sc.Check(soa, true, "EI", args)) { + JniValueType result; + result.i = baseEnv(env)->PushLocalFrame(env, capacity); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } + } + return JNI_ERR; } static jobject PopLocalFrame(JNIEnv* env, jobject res) { - CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, res); - return CHECK_JNI_EXIT("L", baseEnv(env)->PopLocalFrame(env, res)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.L = res}}; + if (sc.Check(soa, true, "EL", args)) { + JniValueType result; + result.L = baseEnv(env)->PopLocalFrame(env, res); + sc.Check(soa, false, "L", &result); + return result.L; + } + return nullptr; } static jobject NewGlobalRef(JNIEnv* env, jobject obj) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); - return CHECK_JNI_EXIT("L", baseEnv(env)->NewGlobalRef(env, obj)); + return NewRef(__FUNCTION__, env, obj, kGlobal); } - static jobject NewLocalRef(JNIEnv* env, jobject ref) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, ref); - return CHECK_JNI_EXIT("L", baseEnv(env)->NewLocalRef(env, ref)); + static jobject NewLocalRef(JNIEnv* env, jobject obj) { + return NewRef(__FUNCTION__, env, obj, kLocal); } - static void DeleteGlobalRef(JNIEnv* env, jobject globalRef) { - CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, globalRef); - if (globalRef != nullptr && GetIndirectRefKind(globalRef) != kGlobal) { - JniAbortF(__FUNCTION__, "DeleteGlobalRef on %s: %p", - ToStr<IndirectRefKind>(GetIndirectRefKind(globalRef)).c_str(), globalRef); - } else { - baseEnv(env)->DeleteGlobalRef(env, globalRef); - CHECK_JNI_EXIT_VOID(); - } + static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) { + return NewRef(__FUNCTION__, env, obj, kWeakGlobal); } - static void DeleteWeakGlobalRef(JNIEnv* env, jweak weakGlobalRef) { - CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, weakGlobalRef); - if (weakGlobalRef != nullptr && GetIndirectRefKind(weakGlobalRef) != kWeakGlobal) { - JniAbortF(__FUNCTION__, "DeleteWeakGlobalRef on %s: %p", - ToStr<IndirectRefKind>(GetIndirectRefKind(weakGlobalRef)).c_str(), weakGlobalRef); - } else { - baseEnv(env)->DeleteWeakGlobalRef(env, weakGlobalRef); - CHECK_JNI_EXIT_VOID(); - } + static void DeleteGlobalRef(JNIEnv* env, jobject obj) { + DeleteRef(__FUNCTION__, env, obj, kGlobal); } - static void DeleteLocalRef(JNIEnv* env, jobject localRef) { - CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, localRef); - if (localRef != nullptr && GetIndirectRefKind(localRef) != kLocal && !IsHandleScopeLocalRef(env, localRef)) { - JniAbortF(__FUNCTION__, "DeleteLocalRef on %s: %p", - ToStr<IndirectRefKind>(GetIndirectRefKind(localRef)).c_str(), localRef); - } else { - baseEnv(env)->DeleteLocalRef(env, localRef); - CHECK_JNI_EXIT_VOID(); - } + static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) { + DeleteRef(__FUNCTION__, env, obj, kWeakGlobal); + } + + static void DeleteLocalRef(JNIEnv* env, jobject obj) { + DeleteRef(__FUNCTION__, env, obj, kLocal); } static jint EnsureLocalCapacity(JNIEnv *env, jint capacity) { - CHECK_JNI_ENTRY(kFlag_Default, "EI", env, capacity); - return CHECK_JNI_EXIT("I", baseEnv(env)->EnsureLocalCapacity(env, capacity)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.I = capacity}}; + if (sc.Check(soa, true, "EI", args)) { + JniValueType result; + result.i = baseEnv(env)->EnsureLocalCapacity(env, capacity); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } + } + return JNI_ERR; } static jboolean IsSameObject(JNIEnv* env, jobject ref1, jobject ref2) { - CHECK_JNI_ENTRY(kFlag_Default, "ELL", env, ref1, ref2); - return CHECK_JNI_EXIT("b", baseEnv(env)->IsSameObject(env, ref1, ref2)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.L = ref1}, {.L = ref2}}; + if (sc.Check(soa, true, "ELL", args)) { + JniValueType result; + result.b = baseEnv(env)->IsSameObject(env, ref1, ref2); + if (sc.Check(soa, false, "b", &result)) { + return result.b; + } + } + return JNI_FALSE; } static jobject AllocObject(JNIEnv* env, jclass c) { - CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c); - return CHECK_JNI_EXIT("L", baseEnv(env)->AllocObject(env, c)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.c = c}}; + if (sc.Check(soa, true, "Ec", args) && sc.CheckInstantiableNonArray(soa, c)) { + JniValueType result; + result.L = baseEnv(env)->AllocObject(env, c); + if (sc.Check(soa, false, "L", &result)) { + return result.L; + } + } + return nullptr; + } + + static jobject NewObjectV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}}; + if (sc.Check(soa, true, "Ecm.", args) && sc.CheckInstantiableNonArray(soa, c) && + sc.CheckConstructor(soa, mid)) { + JniValueType result; + result.L = baseEnv(env)->NewObjectV(env, c, mid, vargs); + if (sc.Check(soa, false, "L", &result)) { + return result.L; + } + } + return nullptr; } static jobject NewObject(JNIEnv* env, jclass c, jmethodID mid, ...) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); va_list args; va_start(args, mid); - jobject result = baseEnv(env)->NewObjectV(env, c, mid, args); + jobject result = NewObjectV(env, c, mid, args); va_end(args); - return CHECK_JNI_EXIT("L", result); - } - - static jobject NewObjectV(JNIEnv* env, jclass c, jmethodID mid, va_list args) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); - return CHECK_JNI_EXIT("L", baseEnv(env)->NewObjectV(env, c, mid, args)); + return result; } - static jobject NewObjectA(JNIEnv* env, jclass c, jmethodID mid, jvalue* args) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); - return CHECK_JNI_EXIT("L", baseEnv(env)->NewObjectA(env, c, mid, args)); + static jobject NewObjectA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}}; + if (sc.Check(soa, true, "Ecm.", args) && sc.CheckInstantiableNonArray(soa, c) && + sc.CheckConstructor(soa, mid)) { + JniValueType result; + result.L = baseEnv(env)->NewObjectA(env, c, mid, vargs); + if (sc.Check(soa, false, "L", &result)) { + return result.L; + } + } + return nullptr; } static jclass GetObjectClass(JNIEnv* env, jobject obj) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); - return CHECK_JNI_EXIT("c", baseEnv(env)->GetObjectClass(env, obj)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.L = obj}}; + if (sc.Check(soa, true, "EL", args)) { + JniValueType result; + result.c = baseEnv(env)->GetObjectClass(env, obj); + if (sc.Check(soa, false, "c", &result)) { + return result.c; + } + } + return nullptr; } static jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass c) { - CHECK_JNI_ENTRY(kFlag_Default, "ELc", env, obj, c); - return CHECK_JNI_EXIT("b", baseEnv(env)->IsInstanceOf(env, obj, c)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.L = obj}, {.c = c}}; + if (sc.Check(soa, true, "ELc", args)) { + JniValueType result; + result.b = baseEnv(env)->IsInstanceOf(env, obj, c); + if (sc.Check(soa, false, "b", &result)) { + return result.b; + } + } + return JNI_FALSE; } static jmethodID GetMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig); - return CHECK_JNI_EXIT("m", baseEnv(env)->GetMethodID(env, c, name, sig)); + return GetMethodIDInternal(__FUNCTION__, env, c, name, sig, false); } - static jfieldID GetFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig); - return CHECK_JNI_EXIT("f", baseEnv(env)->GetFieldID(env, c, name, sig)); + static jmethodID GetStaticMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) { + return GetMethodIDInternal(__FUNCTION__, env, c, name, sig, true); } - static jmethodID GetStaticMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig); - return CHECK_JNI_EXIT("m", baseEnv(env)->GetStaticMethodID(env, c, name, sig)); + static jfieldID GetFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) { + return GetFieldIDInternal(__FUNCTION__, env, c, name, sig, false); } static jfieldID GetStaticFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) { - CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig); - return CHECK_JNI_EXIT("f", baseEnv(env)->GetStaticFieldID(env, c, name, sig)); - } - -#define FIELD_ACCESSORS(_ctype, _jname, _jvalue_type, _type) \ - static _ctype GetStatic##_jname##Field(JNIEnv* env, jclass c, jfieldID fid) { \ - CHECK_JNI_ENTRY(kFlag_Default, "Ecf", env, c, fid); \ - sc.CheckStaticFieldID(c, fid); \ - return CHECK_JNI_EXIT(_type, baseEnv(env)->GetStatic##_jname##Field(env, c, fid)); \ - } \ - static _ctype Get##_jname##Field(JNIEnv* env, jobject obj, jfieldID fid) { \ - CHECK_JNI_ENTRY(kFlag_Default, "ELf", env, obj, fid); \ - sc.CheckInstanceFieldID(obj, fid); \ - return CHECK_JNI_EXIT(_type, baseEnv(env)->Get##_jname##Field(env, obj, fid)); \ - } \ - static void SetStatic##_jname##Field(JNIEnv* env, jclass c, jfieldID fid, _ctype value) { \ - CHECK_JNI_ENTRY(kFlag_Default, "Ecf" _type, env, c, fid, value); \ - sc.CheckStaticFieldID(c, fid); \ - /* "value" arg only used when type == ref */ \ - jvalue java_type_value; \ - java_type_value._jvalue_type = value; \ - sc.CheckFieldType(java_type_value, fid, _type[0], true); \ - baseEnv(env)->SetStatic##_jname##Field(env, c, fid, value); \ - CHECK_JNI_EXIT_VOID(); \ - } \ - static void Set##_jname##Field(JNIEnv* env, jobject obj, jfieldID fid, _ctype value) { \ - CHECK_JNI_ENTRY(kFlag_Default, "ELf" _type, env, obj, fid, value); \ - sc.CheckInstanceFieldID(obj, fid); \ - /* "value" arg only used when type == ref */ \ - jvalue java_type_value; \ - java_type_value._jvalue_type = value; \ - sc.CheckFieldType(java_type_value, fid, _type[0], false); \ - baseEnv(env)->Set##_jname##Field(env, obj, fid, value); \ - CHECK_JNI_EXIT_VOID(); \ - } - -FIELD_ACCESSORS(jobject, Object, l, "L"); -FIELD_ACCESSORS(jboolean, Boolean, z, "Z"); -FIELD_ACCESSORS(jbyte, Byte, b, "B"); -FIELD_ACCESSORS(jchar, Char, c, "C"); -FIELD_ACCESSORS(jshort, Short, s, "S"); -FIELD_ACCESSORS(jint, Int, i, "I"); -FIELD_ACCESSORS(jlong, Long, j, "J"); -FIELD_ACCESSORS(jfloat, Float, f, "F"); -FIELD_ACCESSORS(jdouble, Double, d, "D"); - -#define CALL(_ctype, _jname, _retdecl, _retasgn, _retok, _retsig) \ - /* Virtual... */ \ - static _ctype Call##_jname##Method(JNIEnv* env, jobject obj, \ - jmethodID mid, ...) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, false); \ - sc.CheckVirtualMethod(obj, mid); \ - _retdecl; \ - va_list args; \ - va_start(args, mid); \ - _retasgn(baseEnv(env)->Call##_jname##MethodV(env, obj, mid, args)); \ - va_end(args); \ - _retok; \ - } \ - static _ctype Call##_jname##MethodV(JNIEnv* env, jobject obj, \ - jmethodID mid, va_list args) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, false); \ - sc.CheckVirtualMethod(obj, mid); \ - _retdecl; \ - _retasgn(baseEnv(env)->Call##_jname##MethodV(env, obj, mid, args)); \ - _retok; \ - } \ - static _ctype Call##_jname##MethodA(JNIEnv* env, jobject obj, \ - jmethodID mid, jvalue* args) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, false); \ - sc.CheckVirtualMethod(obj, mid); \ - _retdecl; \ - _retasgn(baseEnv(env)->Call##_jname##MethodA(env, obj, mid, args)); \ - _retok; \ - } \ - /* Non-virtual... */ \ - static _ctype CallNonvirtual##_jname##Method(JNIEnv* env, \ - jobject obj, jclass c, jmethodID mid, ...) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, false); \ - sc.CheckVirtualMethod(obj, mid); \ - _retdecl; \ - va_list args; \ - va_start(args, mid); \ - _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodV(env, obj, c, mid, args)); \ - va_end(args); \ - _retok; \ - } \ - static _ctype CallNonvirtual##_jname##MethodV(JNIEnv* env, \ - jobject obj, jclass c, jmethodID mid, va_list args) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, false); \ - sc.CheckVirtualMethod(obj, mid); \ - _retdecl; \ - _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodV(env, obj, c, mid, args)); \ - _retok; \ - } \ - static _ctype CallNonvirtual##_jname##MethodA(JNIEnv* env, \ - jobject obj, jclass c, jmethodID mid, jvalue* args) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, false); \ - sc.CheckVirtualMethod(obj, mid); \ - _retdecl; \ - _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodA(env, obj, c, mid, args)); \ - _retok; \ - } \ - /* Static... */ \ - static _ctype CallStatic##_jname##Method(JNIEnv* env, jclass c, jmethodID mid, ...) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, true); \ - sc.CheckStaticMethod(c, mid); \ - _retdecl; \ - va_list args; \ - va_start(args, mid); \ - _retasgn(baseEnv(env)->CallStatic##_jname##MethodV(env, c, mid, args)); \ - va_end(args); \ - _retok; \ - } \ - static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass c, jmethodID mid, va_list args) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, true); \ - sc.CheckStaticMethod(c, mid); \ - _retdecl; \ - _retasgn(baseEnv(env)->CallStatic##_jname##MethodV(env, c, mid, args)); \ - _retok; \ - } \ - static _ctype CallStatic##_jname##MethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* args) \ - { \ - CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \ - sc.CheckSig(mid, _retsig, true); \ - sc.CheckStaticMethod(c, mid); \ - _retdecl; \ - _retasgn(baseEnv(env)->CallStatic##_jname##MethodA(env, c, mid, args)); \ - _retok; \ - } - -#define NON_VOID_RETURN(_retsig, _ctype) return CHECK_JNI_EXIT(_retsig, (_ctype) result) -#define VOID_RETURN CHECK_JNI_EXIT_VOID() - -CALL(jobject, Object, mirror::Object* result, result = reinterpret_cast<mirror::Object*>, NON_VOID_RETURN("L", jobject), "L"); -CALL(jboolean, Boolean, jboolean result, result =, NON_VOID_RETURN("Z", jboolean), "Z"); -CALL(jbyte, Byte, jbyte result, result =, NON_VOID_RETURN("B", jbyte), "B"); -CALL(jchar, Char, jchar result, result =, NON_VOID_RETURN("C", jchar), "C"); -CALL(jshort, Short, jshort result, result =, NON_VOID_RETURN("S", jshort), "S"); -CALL(jint, Int, jint result, result =, NON_VOID_RETURN("I", jint), "I"); -CALL(jlong, Long, jlong result, result =, NON_VOID_RETURN("J", jlong), "J"); -CALL(jfloat, Float, jfloat result, result =, NON_VOID_RETURN("F", jfloat), "F"); -CALL(jdouble, Double, jdouble result, result =, NON_VOID_RETURN("D", jdouble), "D"); -CALL(void, Void, , , VOID_RETURN, "V"); - - static jstring NewString(JNIEnv* env, const jchar* unicodeChars, jsize len) { - CHECK_JNI_ENTRY(kFlag_Default, "Epz", env, unicodeChars, len); - return CHECK_JNI_EXIT("s", baseEnv(env)->NewString(env, unicodeChars, len)); + return GetFieldIDInternal(__FUNCTION__, env, c, name, sig, true); + } + +#define FIELD_ACCESSORS(jtype, name, ptype, shorty) \ + static jtype GetStatic##name##Field(JNIEnv* env, jclass c, jfieldID fid) { \ + return GetField(__FUNCTION__, env, c, fid, true, ptype).shorty; \ + } \ + \ + static jtype Get##name##Field(JNIEnv* env, jobject obj, jfieldID fid) { \ + return GetField(__FUNCTION__, env, obj, fid, false, ptype).shorty; \ + } \ + \ + static void SetStatic##name##Field(JNIEnv* env, jclass c, jfieldID fid, jtype v) { \ + JniValueType value; \ + value.shorty = v; \ + SetField(__FUNCTION__, env, c, fid, true, ptype, value); \ + } \ + \ + static void Set##name##Field(JNIEnv* env, jobject obj, jfieldID fid, jtype v) { \ + JniValueType value; \ + value.shorty = v; \ + SetField(__FUNCTION__, env, obj, fid, false, ptype, value); \ + } + + FIELD_ACCESSORS(jobject, Object, Primitive::kPrimNot, L) + FIELD_ACCESSORS(jboolean, Boolean, Primitive::kPrimBoolean, Z) + FIELD_ACCESSORS(jbyte, Byte, Primitive::kPrimByte, B) + FIELD_ACCESSORS(jchar, Char, Primitive::kPrimChar, C) + FIELD_ACCESSORS(jshort, Short, Primitive::kPrimShort, S) + FIELD_ACCESSORS(jint, Int, Primitive::kPrimInt, I) + FIELD_ACCESSORS(jlong, Long, Primitive::kPrimLong, J) + FIELD_ACCESSORS(jfloat, Float, Primitive::kPrimFloat, F) + FIELD_ACCESSORS(jdouble, Double, Primitive::kPrimDouble, D) +#undef FIELD_ACCESSORS + + static void CallVoidMethodA(JNIEnv* env, jobject obj, jmethodID mid, jvalue* vargs) { + CallMethodA(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual); + } + + static void CallNonvirtualVoidMethodA(JNIEnv* env, jobject obj, jclass c, jmethodID mid, + jvalue* vargs) { + CallMethodA(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect); + } + + static void CallStaticVoidMethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) { + CallMethodA(__FUNCTION__, env, c, nullptr, mid, vargs, Primitive::kPrimVoid, kStatic); + } + + static void CallVoidMethodV(JNIEnv* env, jobject obj, jmethodID mid, va_list vargs) { + CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual); + } + + static void CallNonvirtualVoidMethodV(JNIEnv* env, jobject obj, jclass c, jmethodID mid, + va_list vargs) { + CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect); + } + + static void CallStaticVoidMethodV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) { + CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, Primitive::kPrimVoid, kStatic); + } + + static void CallVoidMethod(JNIEnv* env, jobject obj, jmethodID mid, ...) { + va_list vargs; + va_start(vargs, mid); + CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual); + va_end(vargs); + } + + static void CallNonvirtualVoidMethod(JNIEnv* env, jobject obj, jclass c, jmethodID mid, ...) { + va_list vargs; + va_start(vargs, mid); + CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect); + va_end(vargs); + } + + static void CallStaticVoidMethod(JNIEnv* env, jclass c, jmethodID mid, ...) { + va_list vargs; + va_start(vargs, mid); + CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, Primitive::kPrimVoid, kStatic); + va_end(vargs); + } + +#define CALL(rtype, name, ptype, shorty) \ + static rtype Call##name##MethodA(JNIEnv* env, jobject obj, jmethodID mid, jvalue* vargs) { \ + return CallMethodA(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \ + } \ + \ + static rtype CallNonvirtual##name##MethodA(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \ + jvalue* vargs) { \ + return CallMethodA(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \ + } \ + \ + static rtype CallStatic##name##MethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) { \ + return CallMethodA(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \ + } \ + \ + static rtype Call##name##MethodV(JNIEnv* env, jobject obj, jmethodID mid, va_list vargs) { \ + return CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \ + } \ + \ + static rtype CallNonvirtual##name##MethodV(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \ + va_list vargs) { \ + return CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \ + } \ + \ + static rtype CallStatic##name##MethodV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) { \ + return CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \ + } \ + \ + static rtype Call##name##Method(JNIEnv* env, jobject obj, jmethodID mid, ...) { \ + va_list vargs; \ + va_start(vargs, mid); \ + rtype result = \ + CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \ + va_end(vargs); \ + return result; \ + } \ + \ + static rtype CallNonvirtual##name##Method(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \ + ...) { \ + va_list vargs; \ + va_start(vargs, mid); \ + rtype result = \ + CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \ + va_end(vargs); \ + return result; \ + } \ + \ + static rtype CallStatic##name##Method(JNIEnv* env, jclass c, jmethodID mid, ...) { \ + va_list vargs; \ + va_start(vargs, mid); \ + rtype result = \ + CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \ + va_end(vargs); \ + return result; \ + } + + CALL(jobject, Object, Primitive::kPrimNot, L) + CALL(jboolean, Boolean, Primitive::kPrimBoolean, Z) + CALL(jbyte, Byte, Primitive::kPrimByte, B) + CALL(jchar, Char, Primitive::kPrimChar, C) + CALL(jshort, Short, Primitive::kPrimShort, S) + CALL(jint, Int, Primitive::kPrimInt, I) + CALL(jlong, Long, Primitive::kPrimLong, J) + CALL(jfloat, Float, Primitive::kPrimFloat, F) + CALL(jdouble, Double, Primitive::kPrimDouble, D) +#undef CALL + + static jstring NewString(JNIEnv* env, const jchar* unicode_chars, jsize len) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.p = unicode_chars}, {.z = len}}; + if (sc.Check(soa, true, "Epz", args)) { + JniValueType result; + result.s = baseEnv(env)->NewString(env, unicode_chars, len); + if (sc.Check(soa, false, "s", &result)) { + return result.s; + } + } + return nullptr; } - static jsize GetStringLength(JNIEnv* env, jstring string) { - CHECK_JNI_ENTRY(kFlag_CritOkay, "Es", env, string); - return CHECK_JNI_EXIT("I", baseEnv(env)->GetStringLength(env, string)); + static jstring NewStringUTF(JNIEnv* env, const char* chars) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_NullableUtf, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.u = chars}}; + if (sc.Check(soa, true, "Eu", args)) { + JniValueType result; + // TODO: stale? show pointer and truncate string. + result.s = baseEnv(env)->NewStringUTF(env, chars); + if (sc.Check(soa, false, "s", &result)) { + return result.s; + } + } + return nullptr; } - static const jchar* GetStringChars(JNIEnv* env, jstring java_string, jboolean* isCopy) { - CHECK_JNI_ENTRY(kFlag_CritOkay, "Esp", env, java_string, isCopy); - const jchar* result = baseEnv(env)->GetStringChars(env, java_string, isCopy); - if (sc.ForceCopy() && result != nullptr) { - mirror::String* s = sc.soa().Decode<mirror::String*>(java_string); - int byteCount = s->GetLength() * 2; - result = (const jchar*) GuardedCopy::Create(result, byteCount, false); - if (isCopy != nullptr) { - *isCopy = JNI_TRUE; + static jsize GetStringLength(JNIEnv* env, jstring string) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_CritOkay, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.s = string}}; + if (sc.Check(soa, true, "Es", args)) { + JniValueType result; + result.z = baseEnv(env)->GetStringLength(env, string); + if (sc.Check(soa, false, "z", &result)) { + return result.z; } } - return CHECK_JNI_EXIT("p", result); + return JNI_ERR; } - static void ReleaseStringChars(JNIEnv* env, jstring string, const jchar* chars) { - CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "Esp", env, string, chars); - sc.CheckNonNull(chars); - if (sc.ForceCopy()) { - GuardedCopy::Check(__FUNCTION__, chars, false); - chars = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<jchar*>(chars))); + static jsize GetStringUTFLength(JNIEnv* env, jstring string) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_CritOkay, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.s = string}}; + if (sc.Check(soa, true, "Es", args)) { + JniValueType result; + result.z = baseEnv(env)->GetStringUTFLength(env, string); + if (sc.Check(soa, false, "z", &result)) { + return result.z; + } } - baseEnv(env)->ReleaseStringChars(env, string, chars); - CHECK_JNI_EXIT_VOID(); + return JNI_ERR; } - static jstring NewStringUTF(JNIEnv* env, const char* bytes) { - CHECK_JNI_ENTRY(kFlag_NullableUtf, "Eu", env, bytes); // TODO: show pointer and truncate string. - return CHECK_JNI_EXIT("s", baseEnv(env)->NewStringUTF(env, bytes)); + static const jchar* GetStringChars(JNIEnv* env, jstring string, jboolean* is_copy) { + return reinterpret_cast<const jchar*>(GetStringCharsInternal(__FUNCTION__, env, string, + is_copy, false, false)); } - static jsize GetStringUTFLength(JNIEnv* env, jstring string) { - CHECK_JNI_ENTRY(kFlag_CritOkay, "Es", env, string); - return CHECK_JNI_EXIT("I", baseEnv(env)->GetStringUTFLength(env, string)); + static const char* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* is_copy) { + return reinterpret_cast<const char*>(GetStringCharsInternal(__FUNCTION__, env, string, + is_copy, true, false)); } - static const char* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* isCopy) { - CHECK_JNI_ENTRY(kFlag_CritOkay, "Esp", env, string, isCopy); - const char* result = baseEnv(env)->GetStringUTFChars(env, string, isCopy); - if (sc.ForceCopy() && result != nullptr) { - result = (const char*) GuardedCopy::Create(result, strlen(result) + 1, false); - if (isCopy != nullptr) { - *isCopy = JNI_TRUE; - } - } - return CHECK_JNI_EXIT("u", result); // TODO: show pointer and truncate string. + static const jchar* GetStringCritical(JNIEnv* env, jstring string, jboolean* is_copy) { + return reinterpret_cast<const jchar*>(GetStringCharsInternal(__FUNCTION__, env, string, + is_copy, false, true)); + } + + static void ReleaseStringChars(JNIEnv* env, jstring string, const jchar* chars) { + ReleaseStringCharsInternal(__FUNCTION__, env, string, chars, false, false); } static void ReleaseStringUTFChars(JNIEnv* env, jstring string, const char* utf) { - CHECK_JNI_ENTRY(kFlag_ExcepOkay | kFlag_Release, "Esu", env, string, utf); // TODO: show pointer and truncate string. - if (sc.ForceCopy()) { - GuardedCopy::Check(__FUNCTION__, utf, false); - utf = reinterpret_cast<const char*>(GuardedCopy::Destroy(const_cast<char*>(utf))); + ReleaseStringCharsInternal(__FUNCTION__, env, string, utf, true, false); + } + + static void ReleaseStringCritical(JNIEnv* env, jstring string, const jchar* chars) { + ReleaseStringCharsInternal(__FUNCTION__, env, string, chars, false, true); + } + + static void GetStringRegion(JNIEnv* env, jstring string, jsize start, jsize len, jchar* buf) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_CritOkay, __FUNCTION__); + JniValueType args[5] = {{.E = env}, {.s = string}, {.z = start}, {.z = len}, {.p = buf}}; + // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices + // result in ArrayIndexOutOfBoundsExceptions in the base implementation. + if (sc.Check(soa, true, "EsIIp", args)) { + baseEnv(env)->GetStringRegion(env, string, start, len, buf); + JniValueType result; + result.V = nullptr; + sc.Check(soa, false, "V", &result); } - baseEnv(env)->ReleaseStringUTFChars(env, string, utf); - CHECK_JNI_EXIT_VOID(); } - static jsize GetArrayLength(JNIEnv* env, jarray array) { - CHECK_JNI_ENTRY(kFlag_CritOkay, "Ea", env, array); - return CHECK_JNI_EXIT("I", baseEnv(env)->GetArrayLength(env, array)); + static void GetStringUTFRegion(JNIEnv* env, jstring string, jsize start, jsize len, char* buf) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_CritOkay, __FUNCTION__); + JniValueType args[5] = {{.E = env}, {.s = string}, {.z = start}, {.z = len}, {.p = buf}}; + // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices + // result in ArrayIndexOutOfBoundsExceptions in the base implementation. + if (sc.Check(soa, true, "EsIIp", args)) { + baseEnv(env)->GetStringUTFRegion(env, string, start, len, buf); + JniValueType result; + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } } - static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass elementClass, jobject initialElement) { - CHECK_JNI_ENTRY(kFlag_Default, "EzcL", env, length, elementClass, initialElement); - return CHECK_JNI_EXIT("a", baseEnv(env)->NewObjectArray(env, length, elementClass, initialElement)); + static jsize GetArrayLength(JNIEnv* env, jarray array) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_CritOkay, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.a = array}}; + if (sc.Check(soa, true, "Ea", args)) { + JniValueType result; + result.z = baseEnv(env)->GetArrayLength(env, array); + if (sc.Check(soa, false, "z", &result)) { + return result.z; + } + } + return JNI_ERR; + } + + static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass element_class, + jobject initial_element) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[4] = + {{.E = env}, {.z = length}, {.c = element_class}, {.L = initial_element}}; + if (sc.Check(soa, true, "EzcL", args)) { + JniValueType result; + // Note: assignability tests of initial_element are done in the base implementation. + result.a = baseEnv(env)->NewObjectArray(env, length, element_class, initial_element); + if (sc.Check(soa, false, "a", &result)) { + return down_cast<jobjectArray>(result.a); + } + } + return nullptr; } static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) { - CHECK_JNI_ENTRY(kFlag_Default, "EaI", env, array, index); - return CHECK_JNI_EXIT("L", baseEnv(env)->GetObjectArrayElement(env, array, index)); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.a = array}, {.z = index}}; + if (sc.Check(soa, true, "Eaz", args)) { + JniValueType result; + result.L = baseEnv(env)->GetObjectArrayElement(env, array, index); + if (sc.Check(soa, false, "L", &result)) { + return result.L; + } + } + return nullptr; } static void SetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index, jobject value) { - CHECK_JNI_ENTRY(kFlag_Default, "EaIL", env, array, index, value); - baseEnv(env)->SetObjectArrayElement(env, array, index, value); - CHECK_JNI_EXIT_VOID(); - } - -#define NEW_PRIMITIVE_ARRAY(_artype, _jname) \ - static _artype New##_jname##Array(JNIEnv* env, jsize length) { \ - CHECK_JNI_ENTRY(kFlag_Default, "Ez", env, length); \ - return CHECK_JNI_EXIT("a", baseEnv(env)->New##_jname##Array(env, length)); \ - } -NEW_PRIMITIVE_ARRAY(jbooleanArray, Boolean); -NEW_PRIMITIVE_ARRAY(jbyteArray, Byte); -NEW_PRIMITIVE_ARRAY(jcharArray, Char); -NEW_PRIMITIVE_ARRAY(jshortArray, Short); -NEW_PRIMITIVE_ARRAY(jintArray, Int); -NEW_PRIMITIVE_ARRAY(jlongArray, Long); -NEW_PRIMITIVE_ARRAY(jfloatArray, Float); -NEW_PRIMITIVE_ARRAY(jdoubleArray, Double); - -struct ForceCopyGetChecker { - public: - ForceCopyGetChecker(ScopedCheck& sc, jboolean* isCopy) { - force_copy = sc.ForceCopy(); - no_copy = 0; - if (force_copy && isCopy != nullptr) { - // Capture this before the base call tramples on it. - no_copy = *reinterpret_cast<uint32_t*>(isCopy); + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[4] = {{.E = env}, {.a = array}, {.z = index}, {.L = value}}; + // Note: the index arguments is checked as 'I' rather than 'z' as invalid indices result in + // ArrayIndexOutOfBoundsExceptions in the base implementation. Similarly invalid stores result + // in ArrayStoreExceptions. + if (sc.Check(soa, true, "EaIL", args)) { + baseEnv(env)->SetObjectArrayElement(env, array, index, value); + JniValueType result; + result.V = nullptr; + sc.Check(soa, false, "V", &result); } } - template<typename ResultT> - ResultT Check(JNIEnv* env, jarray array, jboolean* isCopy, ResultT result) { - if (force_copy && result != nullptr) { - result = reinterpret_cast<ResultT>(CreateGuardedPACopy(env, array, isCopy)); - } - return result; + static jbooleanArray NewBooleanArray(JNIEnv* env, jsize length) { + return down_cast<jbooleanArray>(NewPrimitiveArray(__FUNCTION__, env, length, + Primitive::kPrimBoolean)); } - uint32_t no_copy; - bool force_copy; -}; + static jbyteArray NewByteArray(JNIEnv* env, jsize length) { + return down_cast<jbyteArray>(NewPrimitiveArray(__FUNCTION__, env, length, + Primitive::kPrimByte)); + } -#define GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \ - static _ctype* Get##_jname##ArrayElements(JNIEnv* env, _ctype##Array array, jboolean* isCopy) { \ - CHECK_JNI_ENTRY(kFlag_Default, "Eap", env, array, isCopy); \ - _ctype* result = ForceCopyGetChecker(sc, isCopy).Check(env, array, isCopy, baseEnv(env)->Get##_jname##ArrayElements(env, array, isCopy)); \ - return CHECK_JNI_EXIT("p", result); \ - } - -#define RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \ - static void Release##_jname##ArrayElements(JNIEnv* env, _ctype##Array array, _ctype* elems, jint mode) { \ - CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "Eapr", env, array, elems, mode); \ - sc.CheckNonNull(elems); \ - if (sc.ForceCopy()) { \ - ReleaseGuardedPACopy(env, array, elems, mode); \ - } \ - baseEnv(env)->Release##_jname##ArrayElements(env, array, elems, mode); \ - CHECK_JNI_EXIT_VOID(); \ - } - -#define GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \ - static void Get##_jname##ArrayRegion(JNIEnv* env, _ctype##Array array, jsize start, jsize len, _ctype* buf) { \ - CHECK_JNI_ENTRY(kFlag_Default, "EaIIp", env, array, start, len, buf); \ - baseEnv(env)->Get##_jname##ArrayRegion(env, array, start, len, buf); \ - CHECK_JNI_EXIT_VOID(); \ - } - -#define SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \ - static void Set##_jname##ArrayRegion(JNIEnv* env, _ctype##Array array, jsize start, jsize len, const _ctype* buf) { \ - CHECK_JNI_ENTRY(kFlag_Default, "EaIIp", env, array, start, len, buf); \ - baseEnv(env)->Set##_jname##ArrayRegion(env, array, start, len, buf); \ - CHECK_JNI_EXIT_VOID(); \ - } - -#define PRIMITIVE_ARRAY_FUNCTIONS(_ctype, _jname, _typechar) \ - GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \ - RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \ - GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname); \ - SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname); - -// TODO: verify primitive array type matches call type. -PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, 'Z'); -PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, 'B'); -PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, 'C'); -PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short, 'S'); -PRIMITIVE_ARRAY_FUNCTIONS(jint, Int, 'I'); -PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long, 'J'); -PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float, 'F'); -PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double, 'D'); + static jcharArray NewCharArray(JNIEnv* env, jsize length) { + return down_cast<jcharArray>(NewPrimitiveArray(__FUNCTION__, env, length, + Primitive::kPrimChar)); + } - static jint RegisterNatives(JNIEnv* env, jclass c, const JNINativeMethod* methods, jint nMethods) { - CHECK_JNI_ENTRY(kFlag_Default, "EcpI", env, c, methods, nMethods); - return CHECK_JNI_EXIT("I", baseEnv(env)->RegisterNatives(env, c, methods, nMethods)); + static jshortArray NewShortArray(JNIEnv* env, jsize length) { + return down_cast<jshortArray>(NewPrimitiveArray(__FUNCTION__, env, length, + Primitive::kPrimShort)); } - static jint UnregisterNatives(JNIEnv* env, jclass c) { - CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c); - return CHECK_JNI_EXIT("I", baseEnv(env)->UnregisterNatives(env, c)); + static jintArray NewIntArray(JNIEnv* env, jsize length) { + return down_cast<jintArray>(NewPrimitiveArray(__FUNCTION__, env, length, Primitive::kPrimInt)); } + static jlongArray NewLongArray(JNIEnv* env, jsize length) { + return down_cast<jlongArray>(NewPrimitiveArray(__FUNCTION__, env, length, + Primitive::kPrimLong)); + } + + static jfloatArray NewFloatArray(JNIEnv* env, jsize length) { + return down_cast<jfloatArray>(NewPrimitiveArray(__FUNCTION__, env, length, + Primitive::kPrimFloat)); + } + + static jdoubleArray NewDoubleArray(JNIEnv* env, jsize length) { + return down_cast<jdoubleArray>(NewPrimitiveArray(__FUNCTION__, env, length, + Primitive::kPrimDouble)); + } + +#define PRIMITIVE_ARRAY_FUNCTIONS(ctype, name, ptype) \ + static ctype* Get##name##ArrayElements(JNIEnv* env, ctype##Array array, jboolean* is_copy) { \ + return reinterpret_cast<ctype*>( \ + GetPrimitiveArrayElements(__FUNCTION__, ptype, env, array, is_copy)); \ + } \ + \ + static void Release##name##ArrayElements(JNIEnv* env, ctype##Array array, ctype* elems, \ + jint mode) { \ + ReleasePrimitiveArrayElements(__FUNCTION__, ptype, env, array, elems, mode); \ + } \ + \ + static void Get##name##ArrayRegion(JNIEnv* env, ctype##Array array, jsize start, jsize len, \ + ctype* buf) { \ + GetPrimitiveArrayRegion(__FUNCTION__, ptype, env, array, start, len, buf); \ + } \ + \ + static void Set##name##ArrayRegion(JNIEnv* env, ctype##Array array, jsize start, jsize len, \ + const ctype* buf) { \ + SetPrimitiveArrayRegion(__FUNCTION__, ptype, env, array, start, len, buf); \ + } + + PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, Primitive::kPrimBoolean) + PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, Primitive::kPrimByte) + PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, Primitive::kPrimChar) + PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short, Primitive::kPrimShort) + PRIMITIVE_ARRAY_FUNCTIONS(jint, Int, Primitive::kPrimInt) + PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long, Primitive::kPrimLong) + PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float, Primitive::kPrimFloat) + PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double, Primitive::kPrimDouble) +#undef PRIMITIVE_ARRAY_FUNCTIONS + static jint MonitorEnter(JNIEnv* env, jobject obj) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); - if (!sc.CheckInstance(ScopedCheck::kObject, obj)) { - return JNI_ERR; // Only for jni_internal_test. Real code will have aborted already. + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.L = obj}}; + if (sc.Check(soa, true, "EL", args)) { + JniValueType result; + result.i = baseEnv(env)->MonitorEnter(env, obj); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } } - return CHECK_JNI_EXIT("I", baseEnv(env)->MonitorEnter(env, obj)); + return JNI_ERR; } static jint MonitorExit(JNIEnv* env, jobject obj) { - CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, obj); - if (!sc.CheckInstance(ScopedCheck::kObject, obj)) { - return JNI_ERR; // Only for jni_internal_test. Real code will have aborted already. + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.L = obj}}; + if (sc.Check(soa, true, "EL", args)) { + JniValueType result; + result.i = baseEnv(env)->MonitorExit(env, obj); + if (sc.Check(soa, false, "i", &result)) { + return result.i; + } } - return CHECK_JNI_EXIT("I", baseEnv(env)->MonitorExit(env, obj)); + return JNI_ERR; } - static jint GetJavaVM(JNIEnv *env, JavaVM **vm) { - CHECK_JNI_ENTRY(kFlag_Default, "Ep", env, vm); - return CHECK_JNI_EXIT("I", baseEnv(env)->GetJavaVM(env, vm)); + static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* is_copy) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_CritGet, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.a = array}, {.p = is_copy}}; + if (sc.Check(soa, true, "Eap", args)) { + JniValueType result; + result.p = baseEnv(env)->GetPrimitiveArrayCritical(env, array, is_copy); + if (result.p != nullptr && soa.ForceCopy()) { + result.p = GuardedCopy::CreateGuardedPACopy(env, array, is_copy); + } + if (sc.Check(soa, false, "p", &result)) { + return const_cast<void*>(result.p); + } + } + return nullptr; } - static void GetStringRegion(JNIEnv* env, jstring str, jsize start, jsize len, jchar* buf) { - CHECK_JNI_ENTRY(kFlag_CritOkay, "EsIIp", env, str, start, len, buf); - baseEnv(env)->GetStringRegion(env, str, start, len, buf); - CHECK_JNI_EXIT_VOID(); + static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_CritRelease | kFlag_ExcepOkay, __FUNCTION__); + sc.CheckNonNull(carray); + JniValueType args[4] = {{.E = env}, {.a = array}, {.p = carray}, {.r = mode}}; + if (sc.Check(soa, true, "Eapr", args)) { + if (soa.ForceCopy()) { + GuardedCopy::ReleaseGuardedPACopy(__FUNCTION__, env, array, carray, mode); + } + baseEnv(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode); + JniValueType result; + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } } - static void GetStringUTFRegion(JNIEnv* env, jstring str, jsize start, jsize len, char* buf) { - CHECK_JNI_ENTRY(kFlag_CritOkay, "EsIIp", env, str, start, len, buf); - baseEnv(env)->GetStringUTFRegion(env, str, start, len, buf); - CHECK_JNI_EXIT_VOID(); + static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[3] = {{.E = env}, {.p = address}, {.J = capacity}}; + if (sc.Check(soa, true, "EpJ", args)) { + JniValueType result; + // Note: the validity of address and capacity are checked in the base implementation. + result.L = baseEnv(env)->NewDirectByteBuffer(env, address, capacity); + if (sc.Check(soa, false, "L", &result)) { + return result.L; + } + } + return nullptr; } - static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* isCopy) { - CHECK_JNI_ENTRY(kFlag_CritGet, "Eap", env, array, isCopy); - void* result = baseEnv(env)->GetPrimitiveArrayCritical(env, array, isCopy); - if (sc.ForceCopy() && result != nullptr) { - result = CreateGuardedPACopy(env, array, isCopy); + static void* GetDirectBufferAddress(JNIEnv* env, jobject buf) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.L = buf}}; + if (sc.Check(soa, true, "EL", args)) { + JniValueType result; + // Note: this is implemented in the base environment by a GetLongField which will sanity + // check the type of buf in GetLongField above. + result.p = baseEnv(env)->GetDirectBufferAddress(env, buf); + if (sc.Check(soa, false, "p", &result)) { + return const_cast<void*>(result.p); + } } - return CHECK_JNI_EXIT("p", result); + return nullptr; } - static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) { - CHECK_JNI_ENTRY(kFlag_CritRelease | kFlag_ExcepOkay, "Eapr", env, array, carray, mode); - sc.CheckNonNull(carray); - if (sc.ForceCopy()) { - ReleaseGuardedPACopy(env, array, carray, mode); + static jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.L = buf}}; + if (sc.Check(soa, true, "EL", args)) { + JniValueType result; + // Note: this is implemented in the base environment by a GetIntField which will sanity + // check the type of buf in GetIntField above. + result.J = baseEnv(env)->GetDirectBufferCapacity(env, buf); + if (sc.Check(soa, false, "J", &result)) { + return result.J; + } } - baseEnv(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode); - CHECK_JNI_EXIT_VOID(); + return JNI_ERR; } - static const jchar* GetStringCritical(JNIEnv* env, jstring java_string, jboolean* isCopy) { - CHECK_JNI_ENTRY(kFlag_CritGet, "Esp", env, java_string, isCopy); - const jchar* result = baseEnv(env)->GetStringCritical(env, java_string, isCopy); - if (sc.ForceCopy() && result != nullptr) { - mirror::String* s = sc.soa().Decode<mirror::String*>(java_string); - int byteCount = s->GetLength() * 2; - result = (const jchar*) GuardedCopy::Create(result, byteCount, false); - if (isCopy != nullptr) { - *isCopy = JNI_TRUE; + private: + static JavaVMExt* GetJavaVMExt(JNIEnv* env) { + return reinterpret_cast<JNIEnvExt*>(env)->vm; + } + + static const JNINativeInterface* baseEnv(JNIEnv* env) { + return reinterpret_cast<JNIEnvExt*>(env)->unchecked_functions; + } + + static jobject NewRef(const char* function_name, JNIEnv* env, jobject obj, IndirectRefKind kind) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType args[2] = {{.E = env}, {.L = obj}}; + if (sc.Check(soa, true, "EL", args)) { + JniValueType result; + switch (kind) { + case kGlobal: + result.L = baseEnv(env)->NewGlobalRef(env, obj); + break; + case kLocal: + result.L = baseEnv(env)->NewLocalRef(env, obj); + break; + case kWeakGlobal: + result.L = baseEnv(env)->NewWeakGlobalRef(env, obj); + break; + default: + LOG(FATAL) << "Unexpected reference kind: " << kind; + } + if (sc.Check(soa, false, "L", &result)) { + DCHECK_EQ(IsSameObject(env, obj, result.L), JNI_TRUE); + DCHECK(sc.CheckReferenceKind(kind, soa.Vm(), soa.Self(), result.L)); + return result.L; } } - return CHECK_JNI_EXIT("p", result); + return nullptr; + } + + static void DeleteRef(const char* function_name, JNIEnv* env, jobject obj, IndirectRefKind kind) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay, function_name); + JniValueType args[2] = {{.E = env}, {.L = obj}}; + sc.Check(soa, true, "EL", args); + if (sc.CheckReferenceKind(kind, soa.Vm(), soa.Self(), obj)) { + JniValueType result; + switch (kind) { + case kGlobal: + baseEnv(env)->DeleteGlobalRef(env, obj); + break; + case kLocal: + baseEnv(env)->DeleteLocalRef(env, obj); + break; + case kWeakGlobal: + baseEnv(env)->DeleteWeakGlobalRef(env, obj); + break; + default: + LOG(FATAL) << "Unexpected reference kind: " << kind; + } + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } } - static void ReleaseStringCritical(JNIEnv* env, jstring string, const jchar* carray) { - CHECK_JNI_ENTRY(kFlag_CritRelease | kFlag_ExcepOkay, "Esp", env, string, carray); - sc.CheckNonNull(carray); - if (sc.ForceCopy()) { - GuardedCopy::Check(__FUNCTION__, carray, false); - carray = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<jchar*>(carray))); + static jmethodID GetMethodIDInternal(const char* function_name, JNIEnv* env, jclass c, + const char* name, const char* sig, bool is_static) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType args[4] = {{.E = env}, {.c = c}, {.u = name}, {.u = sig}}; + if (sc.Check(soa, true, "Ecuu", args)) { + JniValueType result; + if (is_static) { + result.m = baseEnv(env)->GetStaticMethodID(env, c, name, sig); + } else { + result.m = baseEnv(env)->GetMethodID(env, c, name, sig); + } + if (sc.Check(soa, false, "m", &result)) { + return result.m; + } } - baseEnv(env)->ReleaseStringCritical(env, string, carray); - CHECK_JNI_EXIT_VOID(); + return nullptr; } - static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj); - return CHECK_JNI_EXIT("L", baseEnv(env)->NewWeakGlobalRef(env, obj)); + static jfieldID GetFieldIDInternal(const char* function_name, JNIEnv* env, jclass c, + const char* name, const char* sig, bool is_static) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType args[4] = {{.E = env}, {.c = c}, {.u = name}, {.u = sig}}; + if (sc.Check(soa, true, "Ecuu", args)) { + JniValueType result; + if (is_static) { + result.f = baseEnv(env)->GetStaticFieldID(env, c, name, sig); + } else { + result.f = baseEnv(env)->GetFieldID(env, c, name, sig); + } + if (sc.Check(soa, false, "f", &result)) { + return result.f; + } + } + return nullptr; + } + + static JniValueType GetField(const char* function_name, JNIEnv* env, jobject obj, jfieldID fid, + bool is_static, Primitive::Type type) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType args[3] = {{.E = env}, {.L = obj}, {.f = fid}}; + JniValueType result; + if (sc.Check(soa, true, is_static ? "Ecf" : "ELf", args) && + sc.CheckFieldAccess(soa, obj, fid, is_static, type)) { + const char* result_check = nullptr; + switch (type) { + case Primitive::kPrimNot: + if (is_static) { + result.L = baseEnv(env)->GetStaticObjectField(env, down_cast<jclass>(obj), fid); + } else { + result.L = baseEnv(env)->GetObjectField(env, obj, fid); + } + result_check = "L"; + break; + case Primitive::kPrimBoolean: + if (is_static) { + result.Z = baseEnv(env)->GetStaticBooleanField(env, down_cast<jclass>(obj), fid); + } else { + result.Z = baseEnv(env)->GetBooleanField(env, obj, fid); + } + result_check = "Z"; + break; + case Primitive::kPrimByte: + if (is_static) { + result.B = baseEnv(env)->GetStaticByteField(env, down_cast<jclass>(obj), fid); + } else { + result.B = baseEnv(env)->GetByteField(env, obj, fid); + } + result_check = "B"; + break; + case Primitive::kPrimChar: + if (is_static) { + result.C = baseEnv(env)->GetStaticCharField(env, down_cast<jclass>(obj), fid); + } else { + result.C = baseEnv(env)->GetCharField(env, obj, fid); + } + result_check = "C"; + break; + case Primitive::kPrimShort: + if (is_static) { + result.S = baseEnv(env)->GetStaticShortField(env, down_cast<jclass>(obj), fid); + } else { + result.S = baseEnv(env)->GetShortField(env, obj, fid); + } + result_check = "S"; + break; + case Primitive::kPrimInt: + if (is_static) { + result.I = baseEnv(env)->GetStaticIntField(env, down_cast<jclass>(obj), fid); + } else { + result.I = baseEnv(env)->GetIntField(env, obj, fid); + } + result_check = "I"; + break; + case Primitive::kPrimLong: + if (is_static) { + result.J = baseEnv(env)->GetStaticLongField(env, down_cast<jclass>(obj), fid); + } else { + result.J = baseEnv(env)->GetLongField(env, obj, fid); + } + result_check = "J"; + break; + case Primitive::kPrimFloat: + if (is_static) { + result.F = baseEnv(env)->GetStaticFloatField(env, down_cast<jclass>(obj), fid); + } else { + result.F = baseEnv(env)->GetFloatField(env, obj, fid); + } + result_check = "F"; + break; + case Primitive::kPrimDouble: + if (is_static) { + result.D = baseEnv(env)->GetStaticDoubleField(env, down_cast<jclass>(obj), fid); + } else { + result.D = baseEnv(env)->GetDoubleField(env, obj, fid); + } + result_check = "D"; + break; + case Primitive::kPrimVoid: + LOG(FATAL) << "Unexpected type: " << type; + break; + } + if (sc.Check(soa, false, result_check, &result)) { + return result; + } + } + result.J = 0; + return result; } - static jboolean ExceptionCheck(JNIEnv* env) { - CHECK_JNI_ENTRY(kFlag_CritOkay | kFlag_ExcepOkay, "E", env); - return CHECK_JNI_EXIT("b", baseEnv(env)->ExceptionCheck(env)); + static void SetField(const char* function_name, JNIEnv* env, jobject obj, jfieldID fid, + bool is_static, Primitive::Type type, JniValueType value) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType args[4] = {{.E = env}, {.L = obj}, {.f = fid}, value}; + char sig[5] = { 'E', is_static ? 'c' : 'L', 'f', + type == Primitive::kPrimNot ? 'L' : Primitive::Descriptor(type)[0], '\0'}; + if (sc.Check(soa, true, sig, args) && + sc.CheckFieldAccess(soa, obj, fid, is_static, type)) { + switch (type) { + case Primitive::kPrimNot: + if (is_static) { + baseEnv(env)->SetStaticObjectField(env, down_cast<jclass>(obj), fid, value.L); + } else { + baseEnv(env)->SetObjectField(env, obj, fid, value.L); + } + break; + case Primitive::kPrimBoolean: + if (is_static) { + baseEnv(env)->SetStaticBooleanField(env, down_cast<jclass>(obj), fid, value.Z); + } else { + baseEnv(env)->SetBooleanField(env, obj, fid, value.Z); + } + break; + case Primitive::kPrimByte: + if (is_static) { + baseEnv(env)->SetStaticByteField(env, down_cast<jclass>(obj), fid, value.B); + } else { + baseEnv(env)->SetByteField(env, obj, fid, value.B); + } + break; + case Primitive::kPrimChar: + if (is_static) { + baseEnv(env)->SetStaticCharField(env, down_cast<jclass>(obj), fid, value.C); + } else { + baseEnv(env)->SetCharField(env, obj, fid, value.C); + } + break; + case Primitive::kPrimShort: + if (is_static) { + baseEnv(env)->SetStaticShortField(env, down_cast<jclass>(obj), fid, value.S); + } else { + baseEnv(env)->SetShortField(env, obj, fid, value.S); + } + break; + case Primitive::kPrimInt: + if (is_static) { + baseEnv(env)->SetStaticIntField(env, down_cast<jclass>(obj), fid, value.I); + } else { + baseEnv(env)->SetIntField(env, obj, fid, value.I); + } + break; + case Primitive::kPrimLong: + if (is_static) { + baseEnv(env)->SetStaticLongField(env, down_cast<jclass>(obj), fid, value.J); + } else { + baseEnv(env)->SetLongField(env, obj, fid, value.J); + } + break; + case Primitive::kPrimFloat: + if (is_static) { + baseEnv(env)->SetStaticFloatField(env, down_cast<jclass>(obj), fid, value.F); + } else { + baseEnv(env)->SetFloatField(env, obj, fid, value.F); + } + break; + case Primitive::kPrimDouble: + if (is_static) { + baseEnv(env)->SetStaticDoubleField(env, down_cast<jclass>(obj), fid, value.D); + } else { + baseEnv(env)->SetDoubleField(env, obj, fid, value.D); + } + break; + case Primitive::kPrimVoid: + LOG(FATAL) << "Unexpected type: " << type; + break; + } + JniValueType result; + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } } - static jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj) { - // Note: we use "Ep" rather than "EL" because this is the one JNI function - // that it's okay to pass an invalid reference to. - CHECK_JNI_ENTRY(kFlag_Default, "Ep", env, obj); - // TODO: proper decoding of jobjectRefType! - return CHECK_JNI_EXIT("I", baseEnv(env)->GetObjectRefType(env, obj)); + static bool CheckCallArgs(ScopedObjectAccess& soa, ScopedCheck& sc, JNIEnv* env, jobject obj, + jclass c, jmethodID mid, InvokeType invoke) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + bool checked; + switch (invoke) { + case kVirtual: { + DCHECK(c == nullptr); + JniValueType args[3] = {{.E = env}, {.L = obj}, {.m = mid}}; + checked = sc.Check(soa, true, "ELm.", args); + break; + } + case kDirect: { + JniValueType args[4] = {{.E = env}, {.L = obj}, {.c = c}, {.m = mid}}; + checked = sc.Check(soa, true, "ELcm.", args); + break; + } + case kStatic: { + DCHECK(obj == nullptr); + JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}}; + checked = sc.Check(soa, true, "Ecm.", args); + break; + } + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + checked = false; + break; + } + return checked; + } + + static JniValueType CallMethodA(const char* function_name, JNIEnv* env, jobject obj, jclass c, + jmethodID mid, jvalue* vargs, Primitive::Type type, + InvokeType invoke) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType result; + if (CheckCallArgs(soa, sc, env, obj, c, mid, invoke) && + sc.CheckMethodAndSig(soa, obj, c, mid, type, invoke)) { + const char* result_check; + switch (type) { + case Primitive::kPrimNot: + result_check = "L"; + switch (invoke) { + case kVirtual: + result.L = baseEnv(env)->CallObjectMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.L = baseEnv(env)->CallNonvirtualObjectMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.L = baseEnv(env)->CallStaticObjectMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimBoolean: + result_check = "Z"; + switch (invoke) { + case kVirtual: + result.Z = baseEnv(env)->CallBooleanMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.Z = baseEnv(env)->CallNonvirtualBooleanMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.Z = baseEnv(env)->CallStaticBooleanMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimByte: + result_check = "B"; + switch (invoke) { + case kVirtual: + result.B = baseEnv(env)->CallByteMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.B = baseEnv(env)->CallNonvirtualByteMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.B = baseEnv(env)->CallStaticByteMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimChar: + result_check = "C"; + switch (invoke) { + case kVirtual: + result.C = baseEnv(env)->CallCharMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.C = baseEnv(env)->CallNonvirtualCharMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.C = baseEnv(env)->CallStaticCharMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimShort: + result_check = "S"; + switch (invoke) { + case kVirtual: + result.S = baseEnv(env)->CallShortMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.S = baseEnv(env)->CallNonvirtualShortMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.S = baseEnv(env)->CallStaticShortMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimInt: + result_check = "I"; + switch (invoke) { + case kVirtual: + result.I = baseEnv(env)->CallIntMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.I = baseEnv(env)->CallNonvirtualIntMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.I = baseEnv(env)->CallStaticIntMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimLong: + result_check = "J"; + switch (invoke) { + case kVirtual: + result.J = baseEnv(env)->CallLongMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.J = baseEnv(env)->CallNonvirtualLongMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.J = baseEnv(env)->CallStaticLongMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimFloat: + result_check = "F"; + switch (invoke) { + case kVirtual: + result.F = baseEnv(env)->CallFloatMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.F = baseEnv(env)->CallNonvirtualFloatMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.F = baseEnv(env)->CallStaticFloatMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimDouble: + result_check = "D"; + switch (invoke) { + case kVirtual: + result.D = baseEnv(env)->CallDoubleMethodA(env, obj, mid, vargs); + break; + case kDirect: + result.D = baseEnv(env)->CallNonvirtualDoubleMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + result.D = baseEnv(env)->CallStaticDoubleMethodA(env, c, mid, vargs); + break; + default: + break; + } + break; + case Primitive::kPrimVoid: + result_check = "V"; + result.V = nullptr; + switch (invoke) { + case kVirtual: + baseEnv(env)->CallVoidMethodA(env, obj, mid, vargs); + break; + case kDirect: + baseEnv(env)->CallNonvirtualVoidMethodA(env, obj, c, mid, vargs); + break; + case kStatic: + baseEnv(env)->CallStaticVoidMethodA(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + default: + LOG(FATAL) << "Unexpected return type: " << type; + result_check = nullptr; + } + if (sc.Check(soa, false, result_check, &result)) { + return result; + } + } + result.J = 0; + return result; } - static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) { - CHECK_JNI_ENTRY(kFlag_Default, "EpJ", env, address, capacity); - if (address == nullptr) { - JniAbortF(__FUNCTION__, "non-nullable address is NULL"); - return nullptr; + static JniValueType CallMethodV(const char* function_name, JNIEnv* env, jobject obj, jclass c, + jmethodID mid, va_list vargs, Primitive::Type type, + InvokeType invoke) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType result; + if (CheckCallArgs(soa, sc, env, obj, c, mid, invoke) && + sc.CheckMethodAndSig(soa, obj, c, mid, type, invoke)) { + const char* result_check; + switch (type) { + case Primitive::kPrimNot: + result_check = "L"; + switch (invoke) { + case kVirtual: + result.L = baseEnv(env)->CallObjectMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.L = baseEnv(env)->CallNonvirtualObjectMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.L = baseEnv(env)->CallStaticObjectMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimBoolean: + result_check = "Z"; + switch (invoke) { + case kVirtual: + result.Z = baseEnv(env)->CallBooleanMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.Z = baseEnv(env)->CallNonvirtualBooleanMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.Z = baseEnv(env)->CallStaticBooleanMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimByte: + result_check = "B"; + switch (invoke) { + case kVirtual: + result.B = baseEnv(env)->CallByteMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.B = baseEnv(env)->CallNonvirtualByteMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.B = baseEnv(env)->CallStaticByteMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimChar: + result_check = "C"; + switch (invoke) { + case kVirtual: + result.C = baseEnv(env)->CallCharMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.C = baseEnv(env)->CallNonvirtualCharMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.C = baseEnv(env)->CallStaticCharMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimShort: + result_check = "S"; + switch (invoke) { + case kVirtual: + result.S = baseEnv(env)->CallShortMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.S = baseEnv(env)->CallNonvirtualShortMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.S = baseEnv(env)->CallStaticShortMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimInt: + result_check = "I"; + switch (invoke) { + case kVirtual: + result.I = baseEnv(env)->CallIntMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.I = baseEnv(env)->CallNonvirtualIntMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.I = baseEnv(env)->CallStaticIntMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimLong: + result_check = "J"; + switch (invoke) { + case kVirtual: + result.J = baseEnv(env)->CallLongMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.J = baseEnv(env)->CallNonvirtualLongMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.J = baseEnv(env)->CallStaticLongMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimFloat: + result_check = "F"; + switch (invoke) { + case kVirtual: + result.F = baseEnv(env)->CallFloatMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.F = baseEnv(env)->CallNonvirtualFloatMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.F = baseEnv(env)->CallStaticFloatMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimDouble: + result_check = "D"; + switch (invoke) { + case kVirtual: + result.D = baseEnv(env)->CallDoubleMethodV(env, obj, mid, vargs); + break; + case kDirect: + result.D = baseEnv(env)->CallNonvirtualDoubleMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + result.D = baseEnv(env)->CallStaticDoubleMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + case Primitive::kPrimVoid: + result_check = "V"; + result.V = nullptr; + switch (invoke) { + case kVirtual: + baseEnv(env)->CallVoidMethodV(env, obj, mid, vargs); + break; + case kDirect: + baseEnv(env)->CallNonvirtualVoidMethodV(env, obj, c, mid, vargs); + break; + case kStatic: + baseEnv(env)->CallStaticVoidMethodV(env, c, mid, vargs); + break; + default: + LOG(FATAL) << "Unexpected invoke: " << invoke; + } + break; + default: + LOG(FATAL) << "Unexpected return type: " << type; + result_check = nullptr; + } + if (sc.Check(soa, false, result_check, &result)) { + return result; + } } - return CHECK_JNI_EXIT("L", baseEnv(env)->NewDirectByteBuffer(env, address, capacity)); + result.J = 0; + return result; } - static void* GetDirectBufferAddress(JNIEnv* env, jobject buf) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, buf); - // TODO: check that 'buf' is a java.nio.Buffer. - return CHECK_JNI_EXIT("p", baseEnv(env)->GetDirectBufferAddress(env, buf)); + static const void* GetStringCharsInternal(const char* function_name, JNIEnv* env, jstring string, + jboolean* is_copy, bool utf, bool critical) { + ScopedObjectAccess soa(env); + int flags = critical ? kFlag_CritGet : kFlag_CritOkay; + ScopedCheck sc(flags, function_name); + JniValueType args[3] = {{.E = env}, {.s = string}, {.p = is_copy}}; + if (sc.Check(soa, true, "Esp", args)) { + JniValueType result; + if (utf) { + CHECK(!critical); + result.u = baseEnv(env)->GetStringUTFChars(env, string, is_copy); + } else { + if (critical) { + result.p = baseEnv(env)->GetStringCritical(env, string, is_copy); + } else { + result.p = baseEnv(env)->GetStringChars(env, string, is_copy); + } + } + // TODO: could we be smarter about not copying when local_is_copy? + if (result.p != nullptr && soa.ForceCopy()) { + if (utf) { + size_t length_in_bytes = strlen(result.u) + 1; + result.u = + reinterpret_cast<const char*>(GuardedCopy::Create(result.u, length_in_bytes, false)); + } else { + size_t length_in_bytes = baseEnv(env)->GetStringLength(env, string) * 2; + result.p = + reinterpret_cast<const jchar*>(GuardedCopy::Create(result.p, length_in_bytes, false)); + } + if (is_copy != nullptr) { + *is_copy = JNI_TRUE; + } + } + if (sc.Check(soa, false, utf ? "u" : "p", &result)) { + return utf ? result.u : result.p; + } + } + return nullptr; } - static jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) { - CHECK_JNI_ENTRY(kFlag_Default, "EL", env, buf); - // TODO: check that 'buf' is a java.nio.Buffer. - return CHECK_JNI_EXIT("J", baseEnv(env)->GetDirectBufferCapacity(env, buf)); + static void ReleaseStringCharsInternal(const char* function_name, JNIEnv* env, jstring string, + const void* chars, bool utf, bool critical) { + ScopedObjectAccess soa(env); + int flags = kFlag_ExcepOkay | kFlag_Release; + if (critical) { + flags |= kFlag_CritRelease; + } + ScopedCheck sc(flags, function_name); + sc.CheckNonNull(chars); + bool force_copy_ok = !soa.ForceCopy() || GuardedCopy::Check(function_name, chars, false); + if (force_copy_ok && soa.ForceCopy()) { + chars = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<void*>(chars))); + } + if (force_copy_ok) { + JniValueType args[3] = {{.E = env}, {.s = string}, {.p = chars}}; + if (sc.Check(soa, true, utf ? "Esu" : "Esp", args)) { + if (utf) { + CHECK(!critical); + baseEnv(env)->ReleaseStringUTFChars(env, string, reinterpret_cast<const char*>(chars)); + } else { + if (critical) { + baseEnv(env)->ReleaseStringCritical(env, string, reinterpret_cast<const jchar*>(chars)); + } else { + baseEnv(env)->ReleaseStringChars(env, string, reinterpret_cast<const jchar*>(chars)); + } + } + JniValueType result; + sc.Check(soa, false, "V", &result); + } + } } - private: - static inline const JNINativeInterface* baseEnv(JNIEnv* env) { - return reinterpret_cast<JNIEnvExt*>(env)->unchecked_functions; + static jarray NewPrimitiveArray(const char* function_name, JNIEnv* env, jsize length, + Primitive::Type type) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.z = length}}; + if (sc.Check(soa, true, "Ez", args)) { + JniValueType result; + switch (type) { + case Primitive::kPrimBoolean: + result.a = baseEnv(env)->NewBooleanArray(env, length); + break; + case Primitive::kPrimByte: + result.a = baseEnv(env)->NewByteArray(env, length); + break; + case Primitive::kPrimChar: + result.a = baseEnv(env)->NewCharArray(env, length); + break; + case Primitive::kPrimShort: + result.a = baseEnv(env)->NewShortArray(env, length); + break; + case Primitive::kPrimInt: + result.a = baseEnv(env)->NewIntArray(env, length); + break; + case Primitive::kPrimLong: + result.a = baseEnv(env)->NewLongArray(env, length); + break; + case Primitive::kPrimFloat: + result.a = baseEnv(env)->NewFloatArray(env, length); + break; + case Primitive::kPrimDouble: + result.a = baseEnv(env)->NewDoubleArray(env, length); + break; + default: + LOG(FATAL) << "Unexpected primitive type: " << type; + } + if (sc.Check(soa, false, "a", &result)) { + return result.a; + } + } + return nullptr; + } + + static void* GetPrimitiveArrayElements(const char* function_name, Primitive::Type type, + JNIEnv* env, jarray array, jboolean* is_copy) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType args[3] = {{.E = env}, {.a = array}, {.p = is_copy}}; + if (sc.Check(soa, true, "Eap", args) && sc.CheckPrimitiveArrayType(soa, array, type)) { + JniValueType result; + switch (type) { + case Primitive::kPrimBoolean: + result.p = baseEnv(env)->GetBooleanArrayElements(env, down_cast<jbooleanArray>(array), + is_copy); + break; + case Primitive::kPrimByte: + result.p = baseEnv(env)->GetByteArrayElements(env, down_cast<jbyteArray>(array), + is_copy); + break; + case Primitive::kPrimChar: + result.p = baseEnv(env)->GetCharArrayElements(env, down_cast<jcharArray>(array), + is_copy); + break; + case Primitive::kPrimShort: + result.p = baseEnv(env)->GetShortArrayElements(env, down_cast<jshortArray>(array), + is_copy); + break; + case Primitive::kPrimInt: + result.p = baseEnv(env)->GetIntArrayElements(env, down_cast<jintArray>(array), is_copy); + break; + case Primitive::kPrimLong: + result.p = baseEnv(env)->GetLongArrayElements(env, down_cast<jlongArray>(array), + is_copy); + break; + case Primitive::kPrimFloat: + result.p = baseEnv(env)->GetFloatArrayElements(env, down_cast<jfloatArray>(array), + is_copy); + break; + case Primitive::kPrimDouble: + result.p = baseEnv(env)->GetDoubleArrayElements(env, down_cast<jdoubleArray>(array), + is_copy); + break; + default: + LOG(FATAL) << "Unexpected primitive type: " << type; + } + if (result.p != nullptr && soa.ForceCopy()) { + result.p = GuardedCopy::CreateGuardedPACopy(env, array, is_copy); + if (is_copy != nullptr) { + *is_copy = JNI_TRUE; + } + } + if (sc.Check(soa, false, "p", &result)) { + return const_cast<void*>(result.p); + } + } + return nullptr; + } + + static void ReleasePrimitiveArrayElements(const char* function_name, Primitive::Type type, + JNIEnv* env, jarray array, void* elems, jint mode) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_ExcepOkay, function_name); + if (sc.CheckNonNull(elems) && sc.CheckPrimitiveArrayType(soa, array, type)) { + if (soa.ForceCopy()) { + elems = GuardedCopy::ReleaseGuardedPACopy(function_name, env, array, elems, mode); + } + if (!soa.ForceCopy() || elems != nullptr) { + JniValueType args[4] = {{.E = env}, {.a = array}, {.p = elems}, {.r = mode}}; + if (sc.Check(soa, true, "Eapr", args)) { + switch (type) { + case Primitive::kPrimBoolean: + baseEnv(env)->ReleaseBooleanArrayElements(env, down_cast<jbooleanArray>(array), + reinterpret_cast<jboolean*>(elems), mode); + break; + case Primitive::kPrimByte: + baseEnv(env)->ReleaseByteArrayElements(env, down_cast<jbyteArray>(array), + reinterpret_cast<jbyte*>(elems), mode); + break; + case Primitive::kPrimChar: + baseEnv(env)->ReleaseCharArrayElements(env, down_cast<jcharArray>(array), + reinterpret_cast<jchar*>(elems), mode); + break; + case Primitive::kPrimShort: + baseEnv(env)->ReleaseShortArrayElements(env, down_cast<jshortArray>(array), + reinterpret_cast<jshort*>(elems), mode); + break; + case Primitive::kPrimInt: + baseEnv(env)->ReleaseIntArrayElements(env, down_cast<jintArray>(array), + reinterpret_cast<jint*>(elems), mode); + break; + case Primitive::kPrimLong: + baseEnv(env)->ReleaseLongArrayElements(env, down_cast<jlongArray>(array), + reinterpret_cast<jlong*>(elems), mode); + break; + case Primitive::kPrimFloat: + baseEnv(env)->ReleaseFloatArrayElements(env, down_cast<jfloatArray>(array), + reinterpret_cast<jfloat*>(elems), mode); + break; + case Primitive::kPrimDouble: + baseEnv(env)->ReleaseDoubleArrayElements(env, down_cast<jdoubleArray>(array), + reinterpret_cast<jdouble*>(elems), mode); + break; + default: + LOG(FATAL) << "Unexpected primitive type: " << type; + } + JniValueType result; + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } + } + } + } + + static void GetPrimitiveArrayRegion(const char* function_name, Primitive::Type type, JNIEnv* env, + jarray array, jsize start, jsize len, void* buf) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType args[5] = {{.E = env}, {.a = array}, {.z = start}, {.z = len}, {.p = buf}}; + // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices + // result in ArrayIndexOutOfBoundsExceptions in the base implementation. + if (sc.Check(soa, true, "EaIIp", args) && sc.CheckPrimitiveArrayType(soa, array, type)) { + switch (type) { + case Primitive::kPrimBoolean: + baseEnv(env)->GetBooleanArrayRegion(env, down_cast<jbooleanArray>(array), start, len, + reinterpret_cast<jboolean*>(buf)); + break; + case Primitive::kPrimByte: + baseEnv(env)->GetByteArrayRegion(env, down_cast<jbyteArray>(array), start, len, + reinterpret_cast<jbyte*>(buf)); + break; + case Primitive::kPrimChar: + baseEnv(env)->GetCharArrayRegion(env, down_cast<jcharArray>(array), start, len, + reinterpret_cast<jchar*>(buf)); + break; + case Primitive::kPrimShort: + baseEnv(env)->GetShortArrayRegion(env, down_cast<jshortArray>(array), start, len, + reinterpret_cast<jshort*>(buf)); + break; + case Primitive::kPrimInt: + baseEnv(env)->GetIntArrayRegion(env, down_cast<jintArray>(array), start, len, + reinterpret_cast<jint*>(buf)); + break; + case Primitive::kPrimLong: + baseEnv(env)->GetLongArrayRegion(env, down_cast<jlongArray>(array), start, len, + reinterpret_cast<jlong*>(buf)); + break; + case Primitive::kPrimFloat: + baseEnv(env)->GetFloatArrayRegion(env, down_cast<jfloatArray>(array), start, len, + reinterpret_cast<jfloat*>(buf)); + break; + case Primitive::kPrimDouble: + baseEnv(env)->GetDoubleArrayRegion(env, down_cast<jdoubleArray>(array), start, len, + reinterpret_cast<jdouble*>(buf)); + break; + default: + LOG(FATAL) << "Unexpected primitive type: " << type; + } + JniValueType result; + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } + } + + static void SetPrimitiveArrayRegion(const char* function_name, Primitive::Type type, JNIEnv* env, + jarray array, jsize start, jsize len, const void* buf) { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, function_name); + JniValueType args[5] = {{.E = env}, {.a = array}, {.z = start}, {.z = len}, {.p = buf}}; + // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices + // result in ArrayIndexOutOfBoundsExceptions in the base implementation. + if (sc.Check(soa, true, "EaIIp", args) && sc.CheckPrimitiveArrayType(soa, array, type)) { + switch (type) { + case Primitive::kPrimBoolean: + baseEnv(env)->SetBooleanArrayRegion(env, down_cast<jbooleanArray>(array), start, len, + reinterpret_cast<const jboolean*>(buf)); + break; + case Primitive::kPrimByte: + baseEnv(env)->SetByteArrayRegion(env, down_cast<jbyteArray>(array), start, len, + reinterpret_cast<const jbyte*>(buf)); + break; + case Primitive::kPrimChar: + baseEnv(env)->SetCharArrayRegion(env, down_cast<jcharArray>(array), start, len, + reinterpret_cast<const jchar*>(buf)); + break; + case Primitive::kPrimShort: + baseEnv(env)->SetShortArrayRegion(env, down_cast<jshortArray>(array), start, len, + reinterpret_cast<const jshort*>(buf)); + break; + case Primitive::kPrimInt: + baseEnv(env)->SetIntArrayRegion(env, down_cast<jintArray>(array), start, len, + reinterpret_cast<const jint*>(buf)); + break; + case Primitive::kPrimLong: + baseEnv(env)->SetLongArrayRegion(env, down_cast<jlongArray>(array), start, len, + reinterpret_cast<const jlong*>(buf)); + break; + case Primitive::kPrimFloat: + baseEnv(env)->SetFloatArrayRegion(env, down_cast<jfloatArray>(array), start, len, + reinterpret_cast<const jfloat*>(buf)); + break; + case Primitive::kPrimDouble: + baseEnv(env)->SetDoubleArrayRegion(env, down_cast<jdoubleArray>(array), start, len, + reinterpret_cast<const jdouble*>(buf)); + break; + default: + LOG(FATAL) << "Unexpected primitive type: " << type; + } + JniValueType result; + result.V = nullptr; + sc.Check(soa, false, "V", &result); + } } }; @@ -2023,38 +3584,58 @@ const JNINativeInterface* GetCheckJniNativeInterface() { class CheckJII { public: static jint DestroyJavaVM(JavaVM* vm) { - ScopedCheck sc(vm, false, __FUNCTION__); - sc.Check(true, "v", vm); - return CHECK_JNI_EXIT("I", BaseVm(vm)->DestroyJavaVM(vm)); + ScopedCheck sc(kFlag_Invocation, __FUNCTION__, false); + JniValueType args[1] = {{.v = vm}}; + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "v", args); + JniValueType result; + result.i = BaseVm(vm)->DestroyJavaVM(vm); + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result); + return result.i; } static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) { - ScopedCheck sc(vm, false, __FUNCTION__); - sc.Check(true, "vpp", vm, p_env, thr_args); - return CHECK_JNI_EXIT("I", BaseVm(vm)->AttachCurrentThread(vm, p_env, thr_args)); + ScopedCheck sc(kFlag_Invocation, __FUNCTION__); + JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.p = thr_args}}; + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpp", args); + JniValueType result; + result.i = BaseVm(vm)->AttachCurrentThread(vm, p_env, thr_args); + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result); + return result.i; } static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) { - ScopedCheck sc(vm, false, __FUNCTION__); - sc.Check(true, "vpp", vm, p_env, thr_args); - return CHECK_JNI_EXIT("I", BaseVm(vm)->AttachCurrentThreadAsDaemon(vm, p_env, thr_args)); + ScopedCheck sc(kFlag_Invocation, __FUNCTION__); + JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.p = thr_args}}; + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpp", args); + JniValueType result; + result.i = BaseVm(vm)->AttachCurrentThreadAsDaemon(vm, p_env, thr_args); + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result); + return result.i; } static jint DetachCurrentThread(JavaVM* vm) { - ScopedCheck sc(vm, true, __FUNCTION__); - sc.Check(true, "v", vm); - return CHECK_JNI_EXIT("I", BaseVm(vm)->DetachCurrentThread(vm)); + ScopedCheck sc(kFlag_Invocation, __FUNCTION__); + JniValueType args[1] = {{.v = vm}}; + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "v", args); + JniValueType result; + result.i = BaseVm(vm)->DetachCurrentThread(vm); + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result); + return result.i; } - static jint GetEnv(JavaVM* vm, void** env, jint version) { - ScopedCheck sc(vm, true, __FUNCTION__); - sc.Check(true, "vpI", vm); - return CHECK_JNI_EXIT("I", BaseVm(vm)->GetEnv(vm, env, version)); + static jint GetEnv(JavaVM* vm, void** p_env, jint version) { + ScopedCheck sc(kFlag_Invocation, __FUNCTION__); + JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.I = version}}; + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpI", args); + JniValueType result; + result.i = BaseVm(vm)->GetEnv(vm, p_env, version); + sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result); + return result.i; } private: - static inline const JNIInvokeInterface* BaseVm(JavaVM* vm) { - return reinterpret_cast<JavaVMExt*>(vm)->unchecked_functions; + static const JNIInvokeInterface* BaseVm(JavaVM* vm) { + return reinterpret_cast<JavaVMExt*>(vm)->GetUncheckedFunctions(); } }; diff --git a/runtime/check_jni.h b/runtime/check_jni.h new file mode 100644 index 0000000000..f41abf81ce --- /dev/null +++ b/runtime/check_jni.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef ART_RUNTIME_CHECK_JNI_H_ +#define ART_RUNTIME_CHECK_JNI_H_ + +#include <jni.h> + +namespace art { + +const JNINativeInterface* GetCheckJniNativeInterface(); +const JNIInvokeInterface* GetCheckJniInvokeInterface(); + +} // namespace art + +#endif // ART_RUNTIME_CHECK_JNI_H_ diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 72572a05b7..3b4976f2a0 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -16,9 +16,6 @@ #include "class_linker.h" -#include <fcntl.h> -#include <sys/file.h> -#include <sys/stat.h> #include <deque> #include <memory> #include <string> diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index 6f3b3a33d5..ab4a2bbdf7 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -354,23 +354,22 @@ jobject CommonRuntimeTest::LoadDex(const char* dex_name) { for (const DexFile* dex_file : dex_files) { class_linker_->RegisterDexFile(*dex_file); } - ScopedObjectAccessUnchecked soa(Thread::Current()); - ScopedLocalRef<jobject> class_loader_local(soa.Env(), - soa.Env()->AllocObject(WellKnownClasses::dalvik_system_PathClassLoader)); - jobject class_loader = soa.Env()->NewGlobalRef(class_loader_local.get()); - soa.Self()->SetClassLoaderOverride(soa.Decode<mirror::ClassLoader*>(class_loader_local.get())); + Thread* self = Thread::Current(); + JNIEnvExt* env = self->GetJniEnv(); + ScopedLocalRef<jobject> class_loader_local(env, + env->AllocObject(WellKnownClasses::dalvik_system_PathClassLoader)); + jobject class_loader = env->NewGlobalRef(class_loader_local.get()); + self->SetClassLoaderOverride(class_loader_local.get()); Runtime::Current()->SetCompileTimeClassPath(class_loader, dex_files); return class_loader; } CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) { - vm_->check_jni_abort_hook = Hook; - vm_->check_jni_abort_hook_data = &actual_; + vm_->SetCheckJniAbortHook(Hook, &actual_); } CheckJniAbortCatcher::~CheckJniAbortCatcher() { - vm_->check_jni_abort_hook = nullptr; - vm_->check_jni_abort_hook_data = nullptr; + vm_->SetCheckJniAbortHook(nullptr, nullptr); EXPECT_TRUE(actual_.empty()) << actual_; } diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 0fefc210bd..12c1241270 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -138,7 +138,7 @@ class CheckJniAbortCatcher { private: static void Hook(void* data, const std::string& reason); - JavaVMExt* vm_; + JavaVMExt* const vm_; std::string actual_; DISALLOW_COPY_AND_ASSIGN(CheckJniAbortCatcher); diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc index be3895a726..4755b9e9db 100644 --- a/runtime/entrypoints/entrypoint_utils.cc +++ b/runtime/entrypoints/entrypoint_utils.cc @@ -220,7 +220,8 @@ void CheckReferenceResult(mirror::Object* o, Thread* self) { } mirror::ArtMethod* m = self->GetCurrentMethod(NULL); if (o == kInvalidIndirectRefObject) { - JniAbortF(NULL, "invalid reference returned from %s", PrettyMethod(m).c_str()); + Runtime::Current()->GetJavaVM()->JniAbortF(NULL, "invalid reference returned from %s", + PrettyMethod(m).c_str()); } // Make sure that the result is an instance of the type this method was expected to return. StackHandleScope<1> hs(self); @@ -228,8 +229,9 @@ void CheckReferenceResult(mirror::Object* o, Thread* self) { mirror::Class* return_type = MethodHelper(h_m).GetReturnType(); if (!o->InstanceOf(return_type)) { - JniAbortF(NULL, "attempt to return an instance of %s from %s", PrettyTypeOf(o).c_str(), - PrettyMethod(h_m.Get()).c_str()); + Runtime::Current()->GetJavaVM()->JniAbortF(NULL, "attempt to return an instance of %s from %s", + PrettyTypeOf(o).c_str(), + PrettyMethod(h_m.Get()).c_str()); } } diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index b61105f6f3..6d8190ed02 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -2514,6 +2514,17 @@ void Heap::RevokeAllThreadLocalAllocationStacks(Thread* self) { } } +void Heap::AssertThreadLocalBuffersAreRevoked(Thread* thread) { + if (kIsDebugBuild) { + if (rosalloc_space_ != nullptr) { + rosalloc_space_->AssertThreadLocalBuffersAreRevoked(thread); + } + if (bump_pointer_space_ != nullptr) { + bump_pointer_space_->AssertThreadLocalBuffersAreRevoked(thread); + } + } +} + void Heap::AssertAllBumpPointerSpaceThreadLocalBuffersAreRevoked() { if (kIsDebugBuild) { if (bump_pointer_space_ != nullptr) { diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index a82392a8be..1851662669 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -455,6 +455,7 @@ class Heap { void RevokeThreadLocalBuffers(Thread* thread); void RevokeRosAllocThreadLocalBuffers(Thread* thread); void RevokeAllThreadLocalBuffers(); + void AssertThreadLocalBuffersAreRevoked(Thread* thread); void AssertAllBumpPointerSpaceThreadLocalBuffersAreRevoked(); void RosAllocVerification(TimingLogger* timings, const char* name) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_); diff --git a/runtime/gc/space/rosalloc_space.cc b/runtime/gc/space/rosalloc_space.cc index 92c6f534cb..3f39c7707b 100644 --- a/runtime/gc/space/rosalloc_space.cc +++ b/runtime/gc/space/rosalloc_space.cc @@ -338,6 +338,12 @@ void RosAllocSpace::RevokeAllThreadLocalBuffers() { rosalloc_->RevokeAllThreadLocalRuns(); } +void RosAllocSpace::AssertThreadLocalBuffersAreRevoked(Thread* thread) { + if (kIsDebugBuild) { + rosalloc_->AssertThreadLocalRunsAreRevoked(thread); + } +} + void RosAllocSpace::AssertAllThreadLocalBuffersAreRevoked() { if (kIsDebugBuild) { rosalloc_->AssertAllThreadLocalRunsAreRevoked(); diff --git a/runtime/gc/space/rosalloc_space.h b/runtime/gc/space/rosalloc_space.h index f50530576b..f1ce115bde 100644 --- a/runtime/gc/space/rosalloc_space.h +++ b/runtime/gc/space/rosalloc_space.h @@ -101,6 +101,7 @@ class RosAllocSpace : public MallocSpace { void RevokeThreadLocalBuffers(Thread* thread); void RevokeAllThreadLocalBuffers(); + void AssertThreadLocalBuffersAreRevoked(Thread* thread); void AssertAllThreadLocalBuffersAreRevoked(); // Returns the class of a recently freed object. diff --git a/runtime/indirect_reference_table.cc b/runtime/indirect_reference_table.cc index 9b2b82e477..1ba2291adf 100644 --- a/runtime/indirect_reference_table.cc +++ b/runtime/indirect_reference_table.cc @@ -56,7 +56,8 @@ std::ostream& operator<<(std::ostream& os, const MutatorLockedDumpable<T>& rhs) void IndirectReferenceTable::AbortIfNoCheckJNI() { // If -Xcheck:jni is on, it'll give a more detailed error before aborting. - if (!Runtime::Current()->GetJavaVM()->check_jni) { + JavaVMExt* vm = Runtime::Current()->GetJavaVM(); + if (!vm->IsCheckJniEnabled()) { // Otherwise, we want to abort rather than hand back a bad reference. LOG(FATAL) << "JNI ERROR (app bug): see above."; } diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h index fb910e2943..d25bc42dfc 100644 --- a/runtime/indirect_reference_table.h +++ b/runtime/indirect_reference_table.h @@ -25,16 +25,18 @@ #include "base/logging.h" #include "base/mutex.h" #include "gc_root.h" -#include "mem_map.h" #include "object_callbacks.h" #include "offsets.h" #include "read_barrier_option.h" namespace art { + namespace mirror { class Object; } // namespace mirror +class MemMap; + /* * Maintain a table of indirect references. Used for local/global JNI * references. diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc new file mode 100644 index 0000000000..9eab3fde13 --- /dev/null +++ b/runtime/java_vm_ext.cc @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2011 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_internal.h" + +#include <dlfcn.h> + +#include "base/mutex.h" +#include "base/stl_util.h" +#include "check_jni.h" +#include "indirect_reference_table-inl.h" +#include "mirror/art_method.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "native_bridge.h" +#include "java_vm_ext.h" +#include "parsed_options.h" +#include "ScopedLocalRef.h" +#include "scoped_thread_state_change.h" +#include "thread-inl.h" +#include "thread_list.h" + +namespace art { + +static const size_t kPinTableInitial = 16; // Arbitrary. +static const size_t kPinTableMax = 1024; // Arbitrary sanity check. + +static size_t gGlobalsInitial = 512; // Arbitrary. +static size_t gGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.) + +static const size_t kWeakGlobalsInitial = 16; // Arbitrary. +static const size_t kWeakGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.) + +static bool IsBadJniVersion(int version) { + // We don't support JNI_VERSION_1_1. These are the only other valid versions. + return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6; +} + +class SharedLibrary { + public: + SharedLibrary(JNIEnv* env, Thread* self, const std::string& path, void* handle, + jobject class_loader) + : path_(path), + handle_(handle), + needs_native_bridge_(false), + class_loader_(env->NewGlobalRef(class_loader)), + jni_on_load_lock_("JNI_OnLoad lock"), + jni_on_load_cond_("JNI_OnLoad condition variable", jni_on_load_lock_), + jni_on_load_thread_id_(self->GetThreadId()), + jni_on_load_result_(kPending) { + } + + ~SharedLibrary() { + Thread* self = Thread::Current(); + if (self != nullptr) { + self->GetJniEnv()->DeleteGlobalRef(class_loader_); + } + } + + jobject GetClassLoader() const { + return class_loader_; + } + + const std::string& GetPath() const { + return path_; + } + + /* + * Check the result of an earlier call to JNI_OnLoad on this library. + * If the call has not yet finished in another thread, wait for it. + */ + bool CheckOnLoadResult() + LOCKS_EXCLUDED(jni_on_load_lock_) { + Thread* self = Thread::Current(); + bool okay; + { + MutexLock mu(self, jni_on_load_lock_); + + if (jni_on_load_thread_id_ == self->GetThreadId()) { + // Check this so we don't end up waiting for ourselves. We need to return "true" so the + // caller can continue. + LOG(INFO) << *self << " recursive attempt to load library " << "\"" << path_ << "\""; + okay = true; + } else { + while (jni_on_load_result_ == kPending) { + VLOG(jni) << "[" << *self << " waiting for \"" << path_ << "\" " << "JNI_OnLoad...]"; + jni_on_load_cond_.Wait(self); + } + + okay = (jni_on_load_result_ == kOkay); + VLOG(jni) << "[Earlier JNI_OnLoad for \"" << path_ << "\" " + << (okay ? "succeeded" : "failed") << "]"; + } + } + return okay; + } + + void SetResult(bool result) LOCKS_EXCLUDED(jni_on_load_lock_) { + Thread* self = Thread::Current(); + MutexLock mu(self, jni_on_load_lock_); + + jni_on_load_result_ = result ? kOkay : kFailed; + jni_on_load_thread_id_ = 0; + + // Broadcast a wakeup to anybody sleeping on the condition variable. + jni_on_load_cond_.Broadcast(self); + } + + void SetNeedsNativeBridge() { + needs_native_bridge_ = true; + } + + bool NeedsNativeBridge() const { + return needs_native_bridge_; + } + + void* FindSymbol(const std::string& symbol_name) { + return dlsym(handle_, symbol_name.c_str()); + } + + void* FindSymbolWithNativeBridge(const std::string& symbol_name, const char* shorty) { + CHECK(NeedsNativeBridge()); + + uint32_t len = 0; + return NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len); + } + + private: + enum JNI_OnLoadState { + kPending, + kFailed, + kOkay, + }; + + // Path to library "/system/lib/libjni.so". + const std::string path_; + + // The void* returned by dlopen(3). + void* const handle_; + + // True if a native bridge is required. + bool needs_native_bridge_; + + // The ClassLoader this library is associated with, a global JNI reference that is + // created/deleted with the scope of the library. + const jobject class_loader_; + + // Guards remaining items. + Mutex jni_on_load_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + // Wait for JNI_OnLoad in other thread. + ConditionVariable jni_on_load_cond_ GUARDED_BY(jni_on_load_lock_); + // Recursive invocation guard. + uint32_t jni_on_load_thread_id_ GUARDED_BY(jni_on_load_lock_); + // Result of earlier JNI_OnLoad call. + JNI_OnLoadState jni_on_load_result_ GUARDED_BY(jni_on_load_lock_); +}; + +// This exists mainly to keep implementation details out of the header file. +class Libraries { + public: + Libraries() { + } + + ~Libraries() { + STLDeleteValues(&libraries_); + } + + void Dump(std::ostream& os) const { + bool first = true; + for (const auto& library : libraries_) { + if (!first) { + os << ' '; + } + first = false; + os << library.first; + } + } + + size_t size() const { + return libraries_.size(); + } + + SharedLibrary* Get(const std::string& path) { + auto it = libraries_.find(path); + return (it == libraries_.end()) ? nullptr : it->second; + } + + void Put(const std::string& path, SharedLibrary* library) { + libraries_.Put(path, library); + } + + // See section 11.3 "Linking Native Methods" of the JNI spec. + void* FindNativeMethod(mirror::ArtMethod* m, std::string& detail) + EXCLUSIVE_LOCKS_REQUIRED(Locks::jni_libraries_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + std::string jni_short_name(JniShortName(m)); + std::string jni_long_name(JniLongName(m)); + const mirror::ClassLoader* declaring_class_loader = m->GetDeclaringClass()->GetClassLoader(); + ScopedObjectAccessUnchecked soa(Thread::Current()); + for (const auto& lib : libraries_) { + SharedLibrary* library = lib.second; + if (soa.Decode<mirror::ClassLoader*>(library->GetClassLoader()) != declaring_class_loader) { + // We only search libraries loaded by the appropriate ClassLoader. + continue; + } + // Try the short name then the long name... + void* fn; + if (library->NeedsNativeBridge()) { + const char* shorty = m->GetShorty(); + fn = library->FindSymbolWithNativeBridge(jni_short_name, shorty); + if (fn == nullptr) { + fn = library->FindSymbolWithNativeBridge(jni_long_name, shorty); + } + } else { + fn = library->FindSymbol(jni_short_name); + if (fn == nullptr) { + fn = library->FindSymbol(jni_long_name); + } + } + if (fn == nullptr) { + fn = library->FindSymbol(jni_long_name); + } + if (fn != nullptr) { + VLOG(jni) << "[Found native code for " << PrettyMethod(m) + << " in \"" << library->GetPath() << "\"]"; + return fn; + } + } + detail += "No implementation found for "; + detail += PrettyMethod(m); + detail += " (tried " + jni_short_name + " and " + jni_long_name + ")"; + LOG(ERROR) << detail; + return nullptr; + } + + private: + SafeMap<std::string, SharedLibrary*> libraries_; +}; + + +class JII { + public: + static jint DestroyJavaVM(JavaVM* vm) { + if (vm == nullptr) { + return JNI_ERR; + } + JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); + delete raw_vm->GetRuntime(); + return JNI_OK; + } + + static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) { + return AttachCurrentThreadInternal(vm, p_env, thr_args, false); + } + + static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) { + return AttachCurrentThreadInternal(vm, p_env, thr_args, true); + } + + static jint DetachCurrentThread(JavaVM* vm) { + if (vm == nullptr || Thread::Current() == nullptr) { + return JNI_ERR; + } + JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); + Runtime* runtime = raw_vm->GetRuntime(); + runtime->DetachCurrentThread(); + return JNI_OK; + } + + static jint GetEnv(JavaVM* vm, void** env, jint version) { + // GetEnv always returns a JNIEnv* for the most current supported JNI version, + // and unlike other calls that take a JNI version doesn't care if you supply + // JNI_VERSION_1_1, which we don't otherwise support. + if (IsBadJniVersion(version) && version != JNI_VERSION_1_1) { + LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version; + return JNI_EVERSION; + } + if (vm == nullptr || env == nullptr) { + return JNI_ERR; + } + Thread* thread = Thread::Current(); + if (thread == nullptr) { + *env = nullptr; + return JNI_EDETACHED; + } + *env = thread->GetJniEnv(); + return JNI_OK; + } + + private: + static jint AttachCurrentThreadInternal(JavaVM* vm, JNIEnv** p_env, void* raw_args, bool as_daemon) { + if (vm == nullptr || p_env == nullptr) { + return JNI_ERR; + } + + // Return immediately if we're already attached. + Thread* self = Thread::Current(); + if (self != nullptr) { + *p_env = self->GetJniEnv(); + return JNI_OK; + } + + Runtime* runtime = reinterpret_cast<JavaVMExt*>(vm)->GetRuntime(); + + // No threads allowed in zygote mode. + if (runtime->IsZygote()) { + LOG(ERROR) << "Attempt to attach a thread in the zygote"; + return JNI_ERR; + } + + JavaVMAttachArgs* args = static_cast<JavaVMAttachArgs*>(raw_args); + const char* thread_name = nullptr; + jobject thread_group = nullptr; + if (args != nullptr) { + if (IsBadJniVersion(args->version)) { + LOG(ERROR) << "Bad JNI version passed to " + << (as_daemon ? "AttachCurrentThreadAsDaemon" : "AttachCurrentThread") << ": " + << args->version; + return JNI_EVERSION; + } + thread_name = args->name; + thread_group = args->group; + } + + if (!runtime->AttachCurrentThread(thread_name, as_daemon, thread_group, !runtime->IsCompiler())) { + *p_env = nullptr; + return JNI_ERR; + } else { + *p_env = Thread::Current()->GetJniEnv(); + return JNI_OK; + } + } +}; + +const JNIInvokeInterface gJniInvokeInterface = { + nullptr, // reserved0 + nullptr, // reserved1 + nullptr, // reserved2 + JII::DestroyJavaVM, + JII::AttachCurrentThread, + JII::DetachCurrentThread, + JII::GetEnv, + JII::AttachCurrentThreadAsDaemon +}; + +JavaVMExt::JavaVMExt(Runtime* runtime, ParsedOptions* options) + : runtime_(runtime), + check_jni_abort_hook_(nullptr), + check_jni_abort_hook_data_(nullptr), + check_jni_(false), // Initialized properly in the constructor body below. + force_copy_(options->force_copy_), + tracing_enabled_(!options->jni_trace_.empty() || VLOG_IS_ON(third_party_jni)), + trace_(options->jni_trace_), + pins_lock_("JNI pin table lock", kPinTableLock), + pin_table_("pin table", kPinTableInitial, kPinTableMax), + globals_lock_("JNI global reference table lock"), + globals_(gGlobalsInitial, gGlobalsMax, kGlobal), + libraries_(new Libraries), + unchecked_functions_(&gJniInvokeInterface), + weak_globals_lock_("JNI weak global reference table lock"), + weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal), + allow_new_weak_globals_(true), + weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) { + functions = unchecked_functions_; + if (options->check_jni_) { + SetCheckJniEnabled(true); + } +} + +JavaVMExt::~JavaVMExt() { +} + +void JavaVMExt::JniAbort(const char* jni_function_name, const char* msg) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + mirror::ArtMethod* current_method = self->GetCurrentMethod(nullptr); + + std::ostringstream os; + os << "JNI DETECTED ERROR IN APPLICATION: " << msg; + + if (jni_function_name != nullptr) { + os << "\n in call to " << jni_function_name; + } + // TODO: is this useful given that we're about to dump the calling thread's stack? + if (current_method != nullptr) { + os << "\n from " << PrettyMethod(current_method); + } + os << "\n"; + self->Dump(os); + + if (check_jni_abort_hook_ != nullptr) { + check_jni_abort_hook_(check_jni_abort_hook_data_, os.str()); + } else { + // Ensure that we get a native stack trace for this thread. + self->TransitionFromRunnableToSuspended(kNative); + LOG(FATAL) << os.str(); + self->TransitionFromSuspendedToRunnable(); // Unreachable, keep annotalysis happy. + } +} + +void JavaVMExt::JniAbortV(const char* jni_function_name, const char* fmt, va_list ap) { + std::string msg; + StringAppendV(&msg, fmt, ap); + JniAbort(jni_function_name, msg.c_str()); +} + +void JavaVMExt::JniAbortF(const char* jni_function_name, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + JniAbortV(jni_function_name, fmt, args); + va_end(args); +} + +bool JavaVMExt::ShouldTrace(mirror::ArtMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + // Fast where no tracing is enabled. + if (trace_.empty() && !VLOG_IS_ON(third_party_jni)) { + return false; + } + // Perform checks based on class name. + StringPiece class_name(method->GetDeclaringClassDescriptor()); + if (!trace_.empty() && class_name.find(trace_) != std::string::npos) { + return true; + } + if (!VLOG_IS_ON(third_party_jni)) { + return false; + } + // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look + // like part of Android. + static const char* gBuiltInPrefixes[] = { + "Landroid/", + "Lcom/android/", + "Lcom/google/android/", + "Ldalvik/", + "Ljava/", + "Ljavax/", + "Llibcore/", + "Lorg/apache/harmony/", + }; + for (size_t i = 0; i < arraysize(gBuiltInPrefixes); ++i) { + if (class_name.starts_with(gBuiltInPrefixes[i])) { + return false; + } + } + return true; +} + +jobject JavaVMExt::AddGlobalRef(Thread* self, mirror::Object* obj) { + // Check for null after decoding the object to handle cleared weak globals. + if (obj == nullptr) { + return nullptr; + } + WriterMutexLock mu(self, globals_lock_); + IndirectRef ref = globals_.Add(IRT_FIRST_SEGMENT, obj); + return reinterpret_cast<jobject>(ref); +} + +jweak JavaVMExt::AddWeakGlobalRef(Thread* self, mirror::Object* obj) { + if (obj == nullptr) { + return nullptr; + } + MutexLock mu(self, weak_globals_lock_); + while (UNLIKELY(!allow_new_weak_globals_)) { + weak_globals_add_condition_.WaitHoldingLocks(self); + } + IndirectRef ref = weak_globals_.Add(IRT_FIRST_SEGMENT, obj); + return reinterpret_cast<jweak>(ref); +} + +void JavaVMExt::DeleteGlobalRef(Thread* self, jobject obj) { + if (obj == nullptr) { + return; + } + WriterMutexLock mu(self, globals_lock_); + if (!globals_.Remove(IRT_FIRST_SEGMENT, obj)) { + LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") " + << "failed to find entry"; + } +} + +void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) { + if (obj == nullptr) { + return; + } + MutexLock mu(self, weak_globals_lock_); + if (!weak_globals_.Remove(IRT_FIRST_SEGMENT, obj)) { + LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") " + << "failed to find entry"; + } +} + +static void ThreadEnableCheckJni(Thread* thread, void* arg) { + bool* check_jni = reinterpret_cast<bool*>(arg); + thread->GetJniEnv()->SetCheckJniEnabled(*check_jni); +} + +bool JavaVMExt::SetCheckJniEnabled(bool enabled) { + bool old_check_jni = check_jni_; + check_jni_ = enabled; + functions = enabled ? GetCheckJniInvokeInterface() : unchecked_functions_; + MutexLock mu(Thread::Current(), *Locks::thread_list_lock_); + runtime_->GetThreadList()->ForEach(ThreadEnableCheckJni, &check_jni_); + return old_check_jni; +} + +void JavaVMExt::DumpForSigQuit(std::ostream& os) { + os << "JNI: CheckJNI is " << (check_jni_ ? "on" : "off"); + if (force_copy_) { + os << " (with forcecopy)"; + } + Thread* self = Thread::Current(); + { + MutexLock mu(self, pins_lock_); + os << "; pins=" << pin_table_.Size(); + } + { + ReaderMutexLock mu(self, globals_lock_); + os << "; globals=" << globals_.Capacity(); + } + { + MutexLock mu(self, weak_globals_lock_); + if (weak_globals_.Capacity() > 0) { + os << " (plus " << weak_globals_.Capacity() << " weak)"; + } + } + os << '\n'; + + { + MutexLock mu(self, *Locks::jni_libraries_lock_); + os << "Libraries: " << Dumpable<Libraries>(*libraries_) << " (" << libraries_->size() << ")\n"; + } +} + +void JavaVMExt::DisallowNewWeakGlobals() { + MutexLock mu(Thread::Current(), weak_globals_lock_); + allow_new_weak_globals_ = false; +} + +void JavaVMExt::AllowNewWeakGlobals() { + Thread* self = Thread::Current(); + MutexLock mu(self, weak_globals_lock_); + allow_new_weak_globals_ = true; + weak_globals_add_condition_.Broadcast(self); +} + +mirror::Object* JavaVMExt::DecodeGlobal(Thread* self, IndirectRef ref) { + return globals_.SynchronizedGet(self, &globals_lock_, ref); +} + +mirror::Object* JavaVMExt::DecodeWeakGlobal(Thread* self, IndirectRef ref) { + MutexLock mu(self, weak_globals_lock_); + while (UNLIKELY(!allow_new_weak_globals_)) { + weak_globals_add_condition_.WaitHoldingLocks(self); + } + return weak_globals_.Get(ref); +} + +void JavaVMExt::PinPrimitiveArray(Thread* self, mirror::Array* array) { + MutexLock mu(self, pins_lock_); + pin_table_.Add(array); +} + +void JavaVMExt::UnpinPrimitiveArray(Thread* self, mirror::Array* array) { + MutexLock mu(self, pins_lock_); + pin_table_.Remove(array); +} + +void JavaVMExt::DumpReferenceTables(std::ostream& os) { + Thread* self = Thread::Current(); + { + ReaderMutexLock mu(self, globals_lock_); + globals_.Dump(os); + } + { + MutexLock mu(self, weak_globals_lock_); + weak_globals_.Dump(os); + } + { + MutexLock mu(self, pins_lock_); + pin_table_.Dump(os); + } +} + +bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, + std::string* error_msg) { + error_msg->clear(); + + // See if we've already loaded this library. If we have, and the class loader + // matches, return successfully without doing anything. + // TODO: for better results we should canonicalize the pathname (or even compare + // inodes). This implementation is fine if everybody is using System.loadLibrary. + SharedLibrary* library; + Thread* self = Thread::Current(); + { + // TODO: move the locking (and more of this logic) into Libraries. + MutexLock mu(self, *Locks::jni_libraries_lock_); + library = libraries_->Get(path); + } + if (library != nullptr) { + if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) { + // The library will be associated with class_loader. The JNI + // spec says we can't load the same library into more than one + // class loader. + StringAppendF(error_msg, "Shared library \"%s\" already opened by " + "ClassLoader %p; can't open in ClassLoader %p", + path.c_str(), library->GetClassLoader(), class_loader); + LOG(WARNING) << error_msg; + return false; + } + VLOG(jni) << "[Shared library \"" << path << "\" already loaded in " + << " ClassLoader " << class_loader << "]"; + if (!library->CheckOnLoadResult()) { + StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt " + "to load \"%s\"", path.c_str()); + return false; + } + return true; + } + + // Open the shared library. Because we're using a full path, the system + // doesn't have to search through LD_LIBRARY_PATH. (It may do so to + // resolve this library's dependencies though.) + + // Failures here are expected when java.library.path has several entries + // and we have to hunt for the lib. + + // Below we dlopen but there is no paired dlclose, this would be necessary if we supported + // class unloading. Libraries will only be unloaded when the reference count (incremented by + // dlopen) becomes zero from dlclose. + + Locks::mutator_lock_->AssertNotHeld(self); + const char* path_str = path.empty() ? nullptr : path.c_str(); + void* handle = dlopen(path_str, RTLD_LAZY); + bool needs_native_bridge = false; + if (handle == nullptr) { + if (NativeBridgeIsSupported(path_str)) { + handle = NativeBridgeLoadLibrary(path_str, RTLD_LAZY); + needs_native_bridge = true; + } + } + + VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]"; + + if (handle == nullptr) { + *error_msg = dlerror(); + LOG(ERROR) << "dlopen(\"" << path << "\", RTLD_LAZY) failed: " << *error_msg; + return false; + } + + if (env->ExceptionCheck() == JNI_TRUE) { + LOG(ERROR) << "Unexpected exception:"; + env->ExceptionDescribe(); + env->ExceptionClear(); + } + // Create a new entry. + // TODO: move the locking (and more of this logic) into Libraries. + bool created_library = false; + { + // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering. + std::unique_ptr<SharedLibrary> new_library( + new SharedLibrary(env, self, path, handle, class_loader)); + MutexLock mu(self, *Locks::jni_libraries_lock_); + library = libraries_->Get(path); + if (library == nullptr) { // We won race to get libraries_lock. + library = new_library.release(); + libraries_->Put(path, library); + created_library = true; + } + } + if (!created_library) { + LOG(INFO) << "WOW: we lost a race to add shared library: " + << "\"" << path << "\" ClassLoader=" << class_loader; + return library->CheckOnLoadResult(); + } + VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]"; + + bool was_successful = false; + void* sym; + if (needs_native_bridge) { + library->SetNeedsNativeBridge(); + sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr); + } else { + sym = dlsym(handle, "JNI_OnLoad"); + } + if (sym == nullptr) { + VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; + was_successful = true; + } else { + // Call JNI_OnLoad. We have to override the current class + // loader, which will always be "null" since the stuff at the + // top of the stack is around Runtime.loadLibrary(). (See + // the comments in the JNI FindClass function.) + ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); + self->SetClassLoaderOverride(class_loader); + + VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; + typedef int (*JNI_OnLoadFn)(JavaVM*, void*); + JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym); + int version = (*jni_on_load)(this, nullptr); + + self->SetClassLoaderOverride(old_class_loader.get()); + + if (version == JNI_ERR) { + StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); + } else if (IsBadJniVersion(version)) { + StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d", + path.c_str(), version); + // It's unwise to call dlclose() here, but we can mark it + // as bad and ensure that future load attempts will fail. + // We don't know how far JNI_OnLoad got, so there could + // be some partially-initialized stuff accessible through + // newly-registered native method calls. We could try to + // unregister them, but that doesn't seem worthwhile. + } else { + was_successful = true; + } + VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure") + << " from JNI_OnLoad in \"" << path << "\"]"; + } + + library->SetResult(was_successful); + return was_successful; +} + +void* JavaVMExt::FindCodeForNativeMethod(mirror::ArtMethod* m) { + CHECK(m->IsNative()); + mirror::Class* c = m->GetDeclaringClass(); + // If this is a static method, it could be called before the class has been initialized. + CHECK(c->IsInitializing()) << c->GetStatus() << " " << PrettyMethod(m); + std::string detail; + void* native_method; + Thread* self = Thread::Current(); + { + MutexLock mu(self, *Locks::jni_libraries_lock_); + native_method = libraries_->FindNativeMethod(m, detail); + } + // Throwing can cause libraries_lock to be reacquired. + if (native_method == nullptr) { + ThrowLocation throw_location = self->GetCurrentLocationForThrow(); + self->ThrowNewException(throw_location, "Ljava/lang/UnsatisfiedLinkError;", detail.c_str()); + } + return native_method; +} + +void JavaVMExt::SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg) { + MutexLock mu(Thread::Current(), weak_globals_lock_); + for (mirror::Object** entry : weak_globals_) { + // Since this is called by the GC, we don't need a read barrier. + mirror::Object* obj = *entry; + mirror::Object* new_obj = callback(obj, arg); + if (new_obj == nullptr) { + new_obj = kClearedJniWeakGlobal; + } + *entry = new_obj; + } +} + +void JavaVMExt::VisitRoots(RootCallback* callback, void* arg) { + Thread* self = Thread::Current(); + { + ReaderMutexLock mu(self, globals_lock_); + globals_.VisitRoots(callback, arg, 0, kRootJNIGlobal); + } + { + MutexLock mu(self, pins_lock_); + pin_table_.VisitRoots(callback, arg, 0, kRootVMInternal); + } + // The weak_globals table is visited by the GC itself (because it mutates the table). +} + +// JNI Invocation interface. + +extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { + const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args); + if (IsBadJniVersion(args->version)) { + LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version; + return JNI_EVERSION; + } + RuntimeOptions options; + for (int i = 0; i < args->nOptions; ++i) { + JavaVMOption* option = &args->options[i]; + options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo)); + } + bool ignore_unrecognized = args->ignoreUnrecognized; + if (!Runtime::Create(options, ignore_unrecognized)) { + return JNI_ERR; + } + Runtime* runtime = Runtime::Current(); + bool started = runtime->Start(); + if (!started) { + delete Thread::Current()->GetJniEnv(); + delete runtime->GetJavaVM(); + LOG(WARNING) << "CreateJavaVM failed"; + return JNI_ERR; + } + *p_env = Thread::Current()->GetJniEnv(); + *p_vm = runtime->GetJavaVM(); + return JNI_OK; +} + +extern "C" jint JNI_GetCreatedJavaVMs(JavaVM** vms, jsize, jsize* vm_count) { + Runtime* runtime = Runtime::Current(); + if (runtime == nullptr) { + *vm_count = 0; + } else { + *vm_count = 1; + vms[0] = runtime->GetJavaVM(); + } + return JNI_OK; +} + +// Historically unsupported. +extern "C" jint JNI_GetDefaultJavaVMInitArgs(void* /*vm_args*/) { + return JNI_ERR; +} + +} // namespace art diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h new file mode 100644 index 0000000000..da0b8e3257 --- /dev/null +++ b/runtime/java_vm_ext.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef ART_RUNTIME_JAVA_VM_EXT_H_ +#define ART_RUNTIME_JAVA_VM_EXT_H_ + +#include "jni.h" + +#include "base/macros.h" +#include "base/mutex.h" +#include "indirect_reference_table.h" +#include "reference_table.h" + +namespace art { + +namespace mirror { + class ArtMethod; + class Array; +} // namespace mirror + +class Libraries; +class ParsedOptions; +class Runtime; + +class JavaVMExt : public JavaVM { + public: + JavaVMExt(Runtime* runtime, ParsedOptions* options); + ~JavaVMExt(); + + bool ForceCopy() const { + return force_copy_; + } + + bool IsCheckJniEnabled() const { + return check_jni_; + } + + bool IsTracingEnabled() const { + return tracing_enabled_; + } + + Runtime* GetRuntime() const { + return runtime_; + } + + void SetCheckJniAbortHook(void (*hook)(void*, const std::string&), void* data) { + check_jni_abort_hook_ = hook; + check_jni_abort_hook_data_ = data; + } + + // Aborts execution unless there is an abort handler installed in which case it will return. Its + // therefore important that callers return after aborting as otherwise code following the abort + // will be executed in the abort handler case. + void JniAbort(const char* jni_function_name, const char* msg); + + void JniAbortV(const char* jni_function_name, const char* fmt, va_list ap); + + void JniAbortF(const char* jni_function_name, const char* fmt, ...) + __attribute__((__format__(__printf__, 3, 4))); + + // If both "-Xcheck:jni" and "-Xjnitrace:" are enabled, we print trace messages + // when a native method that matches the -Xjnitrace argument calls a JNI function + // such as NewByteArray. + // If -verbose:third-party-jni is on, we want to log any JNI function calls + // made by a third-party native method. + bool ShouldTrace(mirror::ArtMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + /** + * Loads the given shared library. 'path' is an absolute pathname. + * + * Returns 'true' on success. On failure, sets 'detail' to a + * human-readable description of the error. + */ + bool LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject javaLoader, + std::string* error_msg); + + /** + * Returns a pointer to the code for the native method 'm', found + * using dlsym(3) on every native library that's been loaded so far. + */ + void* FindCodeForNativeMethod(mirror::ArtMethod* m) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void DumpForSigQuit(std::ostream& os) + LOCKS_EXCLUDED(Locks::jni_libraries_lock_, globals_lock_, weak_globals_lock_, pins_lock_); + + void DumpReferenceTables(std::ostream& os) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool SetCheckJniEnabled(bool enabled); + + void VisitRoots(RootCallback* callback, void* arg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void DisallowNewWeakGlobals() EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_); + + void AllowNewWeakGlobals() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + jobject AddGlobalRef(Thread* self, mirror::Object* obj) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + jweak AddWeakGlobalRef(Thread* self, mirror::Object* obj) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void DeleteGlobalRef(Thread* self, jobject obj); + + void DeleteWeakGlobalRef(Thread* self, jweak obj); + + void SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::Object* DecodeGlobal(Thread* self, IndirectRef ref) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + mirror::Object* DecodeWeakGlobal(Thread* self, IndirectRef ref) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void PinPrimitiveArray(Thread* self, mirror::Array* array) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + LOCKS_EXCLUDED(pins_lock_); + + void UnpinPrimitiveArray(Thread* self, mirror::Array* array) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + LOCKS_EXCLUDED(pins_lock_); + + const JNIInvokeInterface* GetUncheckedFunctions() const { + return unchecked_functions_; + } + + private: + Runtime* const runtime_; + + // Used for testing. By default, we'll LOG(FATAL) the reason. + void (*check_jni_abort_hook_)(void* data, const std::string& reason); + void* check_jni_abort_hook_data_; + + // Extra checking. + bool check_jni_; + bool force_copy_; + const bool tracing_enabled_; + + // Extra diagnostics. + const std::string trace_; + + // Used to hold references to pinned primitive arrays. + Mutex pins_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + ReferenceTable pin_table_ GUARDED_BY(pins_lock_); + + // JNI global references. + ReaderWriterMutex globals_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + // Not guarded by globals_lock since we sometimes use SynchronizedGet in Thread::DecodeJObject. + IndirectReferenceTable globals_; + + std::unique_ptr<Libraries> libraries_ GUARDED_BY(Locks::jni_libraries_lock_); + + // Used by -Xcheck:jni. + const JNIInvokeInterface* const unchecked_functions_; + + // JNI weak global references. + Mutex weak_globals_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + // Since weak_globals_ contain weak roots, be careful not to + // directly access the object references in it. Use Get() with the + // read barrier enabled. + IndirectReferenceTable weak_globals_ GUARDED_BY(weak_globals_lock_); + bool allow_new_weak_globals_ GUARDED_BY(weak_globals_lock_); + ConditionVariable weak_globals_add_condition_ GUARDED_BY(weak_globals_lock_); + + DISALLOW_COPY_AND_ASSIGN(JavaVMExt); +}; + +} // namespace art + +#endif // ART_RUNTIME_JAVA_VM_EXT_H_ diff --git a/runtime/jni_internal-inl.h b/runtime/jni_env_ext-inl.h index 6cf9a61896..dc6a3e8f62 100644 --- a/runtime/jni_internal-inl.h +++ b/runtime/jni_env_ext-inl.h @@ -14,10 +14,10 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_JNI_INTERNAL_INL_H_ -#define ART_RUNTIME_JNI_INTERNAL_INL_H_ +#ifndef ART_RUNTIME_JNI_ENV_EXT_INL_H_ +#define ART_RUNTIME_JNI_ENV_EXT_INL_H_ -#include "jni_internal.h" +#include "jni_env_ext.h" #include "utils.h" @@ -44,4 +44,4 @@ inline T JNIEnvExt::AddLocalReference(mirror::Object* obj) { } // namespace art -#endif // ART_RUNTIME_JNI_INTERNAL_INL_H_ +#endif // ART_RUNTIME_JNI_ENV_EXT_INL_H_ diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc new file mode 100644 index 0000000000..180e3d7865 --- /dev/null +++ b/runtime/jni_env_ext.cc @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 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_env_ext.h" + +#include "check_jni.h" +#include "indirect_reference_table.h" +#include "java_vm_ext.h" +#include "jni_internal.h" + +namespace art { + +static constexpr size_t kMonitorsInitial = 32; // Arbitrary. +static constexpr size_t kMonitorsMax = 4096; // Arbitrary sanity check. + +static constexpr size_t kLocalsInitial = 64; // Arbitrary. + +JNIEnvExt::JNIEnvExt(Thread* self, JavaVMExt* vm) + : self(self), + vm(vm), + local_ref_cookie(IRT_FIRST_SEGMENT), + locals(kLocalsInitial, kLocalsMax, kLocal), + check_jni(false), + critical(0), + monitors("monitors", kMonitorsInitial, kMonitorsMax) { + functions = unchecked_functions = GetJniNativeInterface(); + if (vm->IsCheckJniEnabled()) { + SetCheckJniEnabled(true); + } +} + +JNIEnvExt::~JNIEnvExt() { +} + +jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (obj == nullptr) { + return nullptr; + } + return reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj)); +} + +void JNIEnvExt::DeleteLocalRef(jobject obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (obj != nullptr) { + locals.Remove(local_ref_cookie, reinterpret_cast<IndirectRef>(obj)); + } +} + +void JNIEnvExt::SetCheckJniEnabled(bool enabled) { + check_jni = enabled; + functions = enabled ? GetCheckJniNativeInterface() : GetJniNativeInterface(); +} + +void JNIEnvExt::DumpReferenceTables(std::ostream& os) { + locals.Dump(os); + monitors.Dump(os); +} + +void JNIEnvExt::PushFrame(int capacity) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + UNUSED(capacity); // cpplint gets confused with (int) and thinks its a cast. + // TODO: take 'capacity' into account. + stacked_local_ref_cookies.push_back(local_ref_cookie); + local_ref_cookie = locals.GetSegmentState(); +} + +void JNIEnvExt::PopFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + locals.SetSegmentState(local_ref_cookie); + local_ref_cookie = stacked_local_ref_cookies.back(); + stacked_local_ref_cookies.pop_back(); +} + +Offset JNIEnvExt::SegmentStateOffset() { + return Offset(OFFSETOF_MEMBER(JNIEnvExt, locals) + + IndirectReferenceTable::SegmentStateOffset().Int32Value()); +} + +} // namespace art diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h new file mode 100644 index 0000000000..af87cb4226 --- /dev/null +++ b/runtime/jni_env_ext.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef ART_RUNTIME_JNI_ENV_EXT_H_ +#define ART_RUNTIME_JNI_ENV_EXT_H_ + +#include <jni.h> + +#include "base/macros.h" +#include "base/mutex.h" +#include "indirect_reference_table.h" +#include "object_callbacks.h" +#include "reference_table.h" + +namespace art { + +class JavaVMExt; + +// Maximum number of local references in the indirect reference table. The value is arbitrary but +// low enough that it forces sanity checks. +static constexpr size_t kLocalsMax = 512; + +struct JNIEnvExt : public JNIEnv { + JNIEnvExt(Thread* self, JavaVMExt* vm); + ~JNIEnvExt(); + + void DumpReferenceTables(std::ostream& os) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + void SetCheckJniEnabled(bool enabled); + + void PushFrame(int capacity); + void PopFrame(); + + template<typename T> + T AddLocalReference(mirror::Object* obj) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + static Offset SegmentStateOffset(); + + static Offset LocalRefCookieOffset() { + return Offset(OFFSETOF_MEMBER(JNIEnvExt, local_ref_cookie)); + } + + static Offset SelfOffset() { + return Offset(OFFSETOF_MEMBER(JNIEnvExt, self)); + } + + jobject NewLocalRef(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void DeleteLocalRef(jobject obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + Thread* const self; + JavaVMExt* const vm; + + // Cookie used when using the local indirect reference table. + uint32_t local_ref_cookie; + + // JNI local references. + IndirectReferenceTable locals GUARDED_BY(Locks::mutator_lock_); + + // Stack of cookies corresponding to PushLocalFrame/PopLocalFrame calls. + // TODO: to avoid leaks (and bugs), we need to clear this vector on entry (or return) + // to a native method. + std::vector<uint32_t> stacked_local_ref_cookies; + + // Frequently-accessed fields cached from JavaVM. + bool check_jni; + + // How many nested "critical" JNI calls are we in? + int critical; + + // Entered JNI monitors, for bulk exit on thread detach. + ReferenceTable monitors; + + // Used by -Xcheck:jni. + const JNINativeInterface* unchecked_functions; +}; + +// Used to save and restore the JNIEnvExt state when not going through code created by the JNI +// compiler. +class ScopedJniEnvLocalRefState { + public: + explicit ScopedJniEnvLocalRefState(JNIEnvExt* env) : env_(env) { + saved_local_ref_cookie_ = env->local_ref_cookie; + env->local_ref_cookie = env->locals.GetSegmentState(); + } + + ~ScopedJniEnvLocalRefState() { + env_->locals.SetSegmentState(env_->local_ref_cookie); + env_->local_ref_cookie = saved_local_ref_cookie_; + } + + private: + JNIEnvExt* const env_; + uint32_t saved_local_ref_cookie_; + + DISALLOW_COPY_AND_ASSIGN(ScopedJniEnvLocalRefState); +}; + +} // namespace art + +#endif // ART_RUNTIME_JNI_ENV_EXT_H_ diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index 43b99127ec..d5e92a40cb 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -33,7 +33,8 @@ #include "gc/accounting/card_table-inl.h" #include "indirect_reference_table-inl.h" #include "interpreter/interpreter.h" -#include "jni.h" +#include "jni_env_ext.h" +#include "java_vm_ext.h" #include "mirror/art_field-inl.h" #include "mirror/art_method-inl.h" #include "mirror/class-inl.h" @@ -55,31 +56,6 @@ namespace art { -static const size_t kMonitorsInitial = 32; // Arbitrary. -static const size_t kMonitorsMax = 4096; // Arbitrary sanity check. - -static const size_t kLocalsInitial = 64; // Arbitrary. -static const size_t kLocalsMax = 512; // Arbitrary sanity check. - -static const size_t kPinTableInitial = 16; // Arbitrary. -static const size_t kPinTableMax = 1024; // Arbitrary sanity check. - -static size_t gGlobalsInitial = 512; // Arbitrary. -static size_t gGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.) - -static const size_t kWeakGlobalsInitial = 16; // Arbitrary. -static const size_t kWeakGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.) - -static jweak AddWeakGlobalReference(ScopedObjectAccess& soa, mirror::Object* obj) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - return soa.Vm()->AddWeakGlobalReference(soa.Self(), obj); -} - -static bool IsBadJniVersion(int version) { - // We don't support JNI_VERSION_1_1. These are the only other valid versions. - return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6; -} - // Section 12.3.2 of the JNI spec describes JNI class descriptors. They're // separated with slashes but aren't wrapped with "L;" like regular descriptors // (i.e. "a/b/C" rather than "La/b/C;"). Arrays of reference types are an @@ -169,7 +145,7 @@ static mirror::ClassLoader* GetClassLoader(const ScopedObjectAccess& soa) mirror::ArtMethod* method = soa.Self()->GetCurrentMethod(nullptr); // If we are running Runtime.nativeLoad, use the overriding ClassLoader it set. if (method == soa.DecodeMethod(WellKnownClasses::java_lang_Runtime_nativeLoad)) { - return soa.Self()->GetClassLoaderOverride(); + return soa.Decode<mirror::ClassLoader*>(soa.Self()->GetClassLoaderOverride()); } // If we have a method, use its ClassLoader for context. if (method != nullptr) { @@ -182,7 +158,7 @@ static mirror::ClassLoader* GetClassLoader(const ScopedObjectAccess& soa) return class_loader; } // See if the override ClassLoader is set for gtests. - class_loader = soa.Self()->GetClassLoaderOverride(); + class_loader = soa.Decode<mirror::ClassLoader*>(soa.Self()->GetClassLoaderOverride()); if (class_loader != nullptr) { // If so, CommonCompilerTest should have set UseCompileTimeClassPath. CHECK(Runtime::Current()->UseCompileTimeClassPath()); @@ -240,20 +216,6 @@ static jfieldID FindFieldID(const ScopedObjectAccess& soa, jclass jni_class, con return soa.EncodeField(field); } -static void PinPrimitiveArray(const ScopedObjectAccess& soa, mirror::Array* array) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - JavaVMExt* vm = soa.Vm(); - MutexLock mu(soa.Self(), vm->pins_lock); - vm->pin_table.Add(array); -} - -static void UnpinPrimitiveArray(const ScopedObjectAccess& soa, mirror::Array* array) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - JavaVMExt* vm = soa.Vm(); - MutexLock mu(soa.Self(), vm->pins_lock); - vm->pin_table.Remove(array); -} - static void ThrowAIOOBE(ScopedObjectAccess& soa, mirror::Array* array, jsize start, jsize length, const char* identifier) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { @@ -316,255 +278,10 @@ int ThrowNewException(JNIEnv* env, jclass exception_class, const char* msg, jobj return JNI_OK; } -static jint JII_AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* raw_args, bool as_daemon) { - if (vm == nullptr || p_env == nullptr) { - return JNI_ERR; - } - - // Return immediately if we're already attached. - Thread* self = Thread::Current(); - if (self != nullptr) { - *p_env = self->GetJniEnv(); - return JNI_OK; - } - - Runtime* runtime = reinterpret_cast<JavaVMExt*>(vm)->runtime; - - // No threads allowed in zygote mode. - if (runtime->IsZygote()) { - LOG(ERROR) << "Attempt to attach a thread in the zygote"; - return JNI_ERR; - } - - JavaVMAttachArgs* args = static_cast<JavaVMAttachArgs*>(raw_args); - const char* thread_name = nullptr; - jobject thread_group = nullptr; - if (args != nullptr) { - if (IsBadJniVersion(args->version)) { - LOG(ERROR) << "Bad JNI version passed to " - << (as_daemon ? "AttachCurrentThreadAsDaemon" : "AttachCurrentThread") << ": " - << args->version; - return JNI_EVERSION; - } - thread_name = args->name; - thread_group = args->group; - } - - if (!runtime->AttachCurrentThread(thread_name, as_daemon, thread_group, !runtime->IsCompiler())) { - *p_env = nullptr; - return JNI_ERR; - } else { - *p_env = Thread::Current()->GetJniEnv(); - return JNI_OK; - } +static JavaVMExt* JavaVmExtFromEnv(JNIEnv* env) { + return reinterpret_cast<JNIEnvExt*>(env)->vm; } -class SharedLibrary { - public: - SharedLibrary(const std::string& path, void* handle, mirror::Object* class_loader) - : path_(path), - handle_(handle), - needs_native_bridge_(false), - class_loader_(GcRoot<mirror::Object>(class_loader)), - jni_on_load_lock_("JNI_OnLoad lock"), - jni_on_load_cond_("JNI_OnLoad condition variable", jni_on_load_lock_), - jni_on_load_thread_id_(Thread::Current()->GetThreadId()), - jni_on_load_result_(kPending) { - } - - mirror::Object* GetClassLoader() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - return class_loader_.Read(); - } - - std::string GetPath() { - return path_; - } - - /* - * Check the result of an earlier call to JNI_OnLoad on this library. - * If the call has not yet finished in another thread, wait for it. - */ - bool CheckOnLoadResult() - LOCKS_EXCLUDED(jni_on_load_lock_) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - Thread* self = Thread::Current(); - self->TransitionFromRunnableToSuspended(kWaitingForJniOnLoad); - bool okay; - { - MutexLock mu(self, jni_on_load_lock_); - - if (jni_on_load_thread_id_ == self->GetThreadId()) { - // Check this so we don't end up waiting for ourselves. We need to return "true" so the - // caller can continue. - LOG(INFO) << *self << " recursive attempt to load library " << "\"" << path_ << "\""; - okay = true; - } else { - while (jni_on_load_result_ == kPending) { - VLOG(jni) << "[" << *self << " waiting for \"" << path_ << "\" " << "JNI_OnLoad...]"; - jni_on_load_cond_.Wait(self); - } - - okay = (jni_on_load_result_ == kOkay); - VLOG(jni) << "[Earlier JNI_OnLoad for \"" << path_ << "\" " - << (okay ? "succeeded" : "failed") << "]"; - } - } - self->TransitionFromSuspendedToRunnable(); - return okay; - } - - void SetResult(bool result) LOCKS_EXCLUDED(jni_on_load_lock_) { - Thread* self = Thread::Current(); - MutexLock mu(self, jni_on_load_lock_); - - jni_on_load_result_ = result ? kOkay : kFailed; - jni_on_load_thread_id_ = 0; - - // Broadcast a wakeup to anybody sleeping on the condition variable. - jni_on_load_cond_.Broadcast(self); - } - - void SetNeedsNativeBridge() { - needs_native_bridge_ = true; - } - - bool NeedsNativeBridge() const { - return needs_native_bridge_; - } - - void* FindSymbol(const std::string& symbol_name) { - return dlsym(handle_, symbol_name.c_str()); - } - - void* FindSymbolWithNativeBridge(const std::string& symbol_name, mirror::ArtMethod* m) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - CHECK(NeedsNativeBridge()); - - uint32_t len = 0; - const char* shorty = nullptr; - if (m != nullptr) { - shorty = m->GetShorty(&len); - } - return NativeBridge::GetTrampoline(handle_, symbol_name.c_str(), shorty, len); - } - - void VisitRoots(RootCallback* visitor, void* arg) { - if (!class_loader_.IsNull()) { - class_loader_.VisitRoot(visitor, arg, 0, kRootVMInternal); - } - } - - private: - enum JNI_OnLoadState { - kPending, - kFailed, - kOkay, - }; - - // Path to library "/system/lib/libjni.so". - std::string path_; - - // The void* returned by dlopen(3). - void* handle_; - - // True if a native bridge is required. - bool needs_native_bridge_; - - // The ClassLoader this library is associated with. - GcRoot<mirror::Object> class_loader_; - - // Guards remaining items. - Mutex jni_on_load_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; - // Wait for JNI_OnLoad in other thread. - ConditionVariable jni_on_load_cond_ GUARDED_BY(jni_on_load_lock_); - // Recursive invocation guard. - uint32_t jni_on_load_thread_id_ GUARDED_BY(jni_on_load_lock_); - // Result of earlier JNI_OnLoad call. - JNI_OnLoadState jni_on_load_result_ GUARDED_BY(jni_on_load_lock_); -}; - -// This exists mainly to keep implementation details out of the header file. -class Libraries { - public: - Libraries() { - } - - ~Libraries() { - STLDeleteValues(&libraries_); - } - - void Dump(std::ostream& os) const { - bool first = true; - for (const auto& library : libraries_) { - if (!first) { - os << ' '; - } - first = false; - os << library.first; - } - } - - size_t size() const { - return libraries_.size(); - } - - SharedLibrary* Get(const std::string& path) { - auto it = libraries_.find(path); - return (it == libraries_.end()) ? nullptr : it->second; - } - - void Put(const std::string& path, SharedLibrary* library) { - libraries_.Put(path, library); - } - - // See section 11.3 "Linking Native Methods" of the JNI spec. - void* FindNativeMethod(mirror::ArtMethod* m, std::string& detail) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - std::string jni_short_name(JniShortName(m)); - std::string jni_long_name(JniLongName(m)); - const mirror::ClassLoader* declaring_class_loader = m->GetDeclaringClass()->GetClassLoader(); - for (const auto& lib : libraries_) { - SharedLibrary* library = lib.second; - if (library->GetClassLoader() != declaring_class_loader) { - // We only search libraries loaded by the appropriate ClassLoader. - continue; - } - // Try the short name then the long name... - void* fn = nullptr; - if (UNLIKELY(library->NeedsNativeBridge())) { - fn = library->FindSymbolWithNativeBridge(jni_short_name, m); - if (fn == nullptr) { - fn = library->FindSymbolWithNativeBridge(jni_long_name, m); - } - } else { - fn = library->FindSymbol(jni_short_name); - if (fn == nullptr) { - fn = library->FindSymbol(jni_long_name); - } - } - if (fn != nullptr) { - VLOG(jni) << "[Found native code for " << PrettyMethod(m) - << " in \"" << library->GetPath() << "\"]"; - return fn; - } - } - detail += "No implementation found for "; - detail += PrettyMethod(m); - detail += " (tried " + jni_short_name + " and " + jni_long_name + ")"; - LOG(ERROR) << detail; - return nullptr; - } - - void VisitRoots(RootCallback* callback, void* arg) { - for (auto& lib_pair : libraries_) { - lib_pair.second->VisitRoots(callback, arg); - } - } - - private: - SafeMap<std::string, SharedLibrary*> libraries_; -}; - #define CHECK_NON_NULL_ARGUMENT(value) \ CHECK_NON_NULL_ARGUMENT_FN_NAME(__FUNCTION__, value, nullptr) @@ -579,13 +296,13 @@ class Libraries { #define CHECK_NON_NULL_ARGUMENT_FN_NAME(name, value, return_val) \ if (UNLIKELY(value == nullptr)) { \ - JniAbortF(name, #value " == null"); \ + JavaVmExtFromEnv(env)->JniAbortF(name, #value " == null"); \ return return_val; \ } #define CHECK_NON_NULL_MEMCPY_ARGUMENT(length, value) \ if (UNLIKELY(length != 0 && value == nullptr)) { \ - JniAbortF(__FUNCTION__, #value " == null"); \ + JavaVmExtFromEnv(env)->JniAbortF(__FUNCTION__, #value " == null"); \ return; \ } @@ -786,10 +503,10 @@ class JNI { static jint PushLocalFrame(JNIEnv* env, jint capacity) { // TODO: SOA may not be necessary but I do it to please lock annotations. ScopedObjectAccess soa(env); - if (EnsureLocalCapacity(soa, capacity, "PushLocalFrame") != JNI_OK) { + if (EnsureLocalCapacityInternal(soa, capacity, "PushLocalFrame") != JNI_OK) { return JNI_ERR; } - static_cast<JNIEnvExt*>(env)->PushFrame(capacity); + down_cast<JNIEnvExt*>(env)->PushFrame(capacity); return JNI_OK; } @@ -803,48 +520,31 @@ class JNI { static jint EnsureLocalCapacity(JNIEnv* env, jint desired_capacity) { // TODO: SOA may not be necessary but I do it to please lock annotations. ScopedObjectAccess soa(env); - return EnsureLocalCapacity(soa, desired_capacity, "EnsureLocalCapacity"); + return EnsureLocalCapacityInternal(soa, desired_capacity, "EnsureLocalCapacity"); } static jobject NewGlobalRef(JNIEnv* env, jobject obj) { ScopedObjectAccess soa(env); mirror::Object* decoded_obj = soa.Decode<mirror::Object*>(obj); - // Check for null after decoding the object to handle cleared weak globals. - if (decoded_obj == nullptr) { - return nullptr; - } - JavaVMExt* vm = soa.Vm(); - IndirectReferenceTable& globals = vm->globals; - WriterMutexLock mu(soa.Self(), vm->globals_lock); - IndirectRef ref = globals.Add(IRT_FIRST_SEGMENT, decoded_obj); - return reinterpret_cast<jobject>(ref); + return soa.Vm()->AddGlobalRef(soa.Self(), decoded_obj); } static void DeleteGlobalRef(JNIEnv* env, jobject obj) { - if (obj == nullptr) { - return; - } - JavaVMExt* vm = reinterpret_cast<JNIEnvExt*>(env)->vm; - IndirectReferenceTable& globals = vm->globals; - Thread* self = reinterpret_cast<JNIEnvExt*>(env)->self; - WriterMutexLock mu(self, vm->globals_lock); - - if (!globals.Remove(IRT_FIRST_SEGMENT, obj)) { - LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") " - << "failed to find entry"; - } + JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm; + Thread* self = down_cast<JNIEnvExt*>(env)->self; + vm->DeleteGlobalRef(self, obj); } static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) { ScopedObjectAccess soa(env); - return AddWeakGlobalReference(soa, soa.Decode<mirror::Object*>(obj)); + mirror::Object* decoded_obj = soa.Decode<mirror::Object*>(obj); + return soa.Vm()->AddWeakGlobalRef(soa.Self(), decoded_obj); } static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) { - if (obj != nullptr) { - ScopedObjectAccess soa(env); - soa.Vm()->DeleteWeakGlobalRef(soa.Self(), obj); - } + JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm; + Thread* self = down_cast<JNIEnvExt*>(env)->self; + vm->DeleteWeakGlobalRef(self, obj); } static jobject NewLocalRef(JNIEnv* env, jobject obj) { @@ -861,7 +561,6 @@ class JNI { if (obj == nullptr) { return; } - ScopedObjectAccess soa(env); IndirectReferenceTable& locals = reinterpret_cast<JNIEnvExt*>(env)->locals; uint32_t cookie = reinterpret_cast<JNIEnvExt*>(env)->local_ref_cookie; @@ -1927,11 +1626,11 @@ class JNI { static jstring NewString(JNIEnv* env, const jchar* chars, jsize char_count) { if (UNLIKELY(char_count < 0)) { - JniAbortF("NewString", "char_count < 0: %d", char_count); + JavaVmExtFromEnv(env)->JniAbortF("NewString", "char_count < 0: %d", char_count); return nullptr; } if (UNLIKELY(chars == nullptr && char_count > 0)) { - JniAbortF("NewString", "chars == null && char_count > 0"); + JavaVmExtFromEnv(env)->JniAbortF("NewString", "chars == null && char_count > 0"); return nullptr; } ScopedObjectAccess soa(env); @@ -1993,7 +1692,7 @@ class JNI { ScopedObjectAccess soa(env); mirror::String* s = soa.Decode<mirror::String*>(java_string); mirror::CharArray* chars = s->GetCharArray(); - PinPrimitiveArray(soa, chars); + soa.Vm()->PinPrimitiveArray(soa.Self(), chars); gc::Heap* heap = Runtime::Current()->GetHeap(); if (heap->IsMovableObject(chars)) { if (is_copy != nullptr) { @@ -2022,7 +1721,7 @@ class JNI { if (chars != (s_chars->GetData() + s->GetOffset())) { delete[] chars; } - UnpinPrimitiveArray(soa, s->GetCharArray()); + soa.Vm()->UnpinPrimitiveArray(soa.Self(), s->GetCharArray()); } static const jchar* GetStringCritical(JNIEnv* env, jstring java_string, jboolean* is_copy) { @@ -2031,7 +1730,7 @@ class JNI { mirror::String* s = soa.Decode<mirror::String*>(java_string); mirror::CharArray* chars = s->GetCharArray(); int32_t offset = s->GetOffset(); - PinPrimitiveArray(soa, chars); + soa.Vm()->PinPrimitiveArray(soa.Self(), chars); gc::Heap* heap = Runtime::Current()->GetHeap(); if (heap->IsMovableObject(chars)) { StackHandleScope<1> hs(soa.Self()); @@ -2047,7 +1746,8 @@ class JNI { static void ReleaseStringCritical(JNIEnv* env, jstring java_string, const jchar* chars) { CHECK_NON_NULL_ARGUMENT_RETURN_VOID(java_string); ScopedObjectAccess soa(env); - UnpinPrimitiveArray(soa, soa.Decode<mirror::String*>(java_string)->GetCharArray()); + soa.Vm()->UnpinPrimitiveArray(soa.Self(), + soa.Decode<mirror::String*>(java_string)->GetCharArray()); gc::Heap* heap = Runtime::Current()->GetHeap(); mirror::String* s = soa.Decode<mirror::String*>(java_string); mirror::CharArray* s_chars = s->GetCharArray(); @@ -2083,7 +1783,8 @@ class JNI { ScopedObjectAccess soa(env); mirror::Object* obj = soa.Decode<mirror::Object*>(java_array); if (UNLIKELY(!obj->IsArrayInstance())) { - JniAbortF("GetArrayLength", "not an array: %s", PrettyTypeOf(obj).c_str()); + soa.Vm()->JniAbortF("GetArrayLength", "not an array: %s", PrettyTypeOf(obj).c_str()); + return 0; } mirror::Array* array = obj->AsArray(); return array->GetLength(); @@ -2138,7 +1839,7 @@ class JNI { static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass element_jclass, jobject initial_element) { if (UNLIKELY(length < 0)) { - JniAbortF("NewObjectArray", "negative array length: %d", length); + JavaVmExtFromEnv(env)->JniAbortF("NewObjectArray", "negative array length: %d", length); return nullptr; } CHECK_NON_NULL_ARGUMENT(element_jclass); @@ -2149,8 +1850,8 @@ class JNI { { mirror::Class* element_class = soa.Decode<mirror::Class*>(element_jclass); if (UNLIKELY(element_class->IsPrimitive())) { - JniAbortF("NewObjectArray", "not an object type: %s", - PrettyDescriptor(element_class).c_str()); + soa.Vm()->JniAbortF("NewObjectArray", "not an object type: %s", + PrettyDescriptor(element_class).c_str()); return nullptr; } ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); @@ -2168,10 +1869,11 @@ class JNI { if (initial_object != nullptr) { mirror::Class* element_class = result->GetClass()->GetComponentType(); if (UNLIKELY(!element_class->IsAssignableFrom(initial_object->GetClass()))) { - JniAbortF("NewObjectArray", "cannot assign object of type '%s' to array with element " - "type of '%s'", PrettyDescriptor(initial_object->GetClass()).c_str(), - PrettyDescriptor(element_class).c_str()); - + soa.Vm()->JniAbortF("NewObjectArray", "cannot assign object of type '%s' to array with " + "element type of '%s'", + PrettyDescriptor(initial_object->GetClass()).c_str(), + PrettyDescriptor(element_class).c_str()); + return nullptr; } else { for (jsize i = 0; i < length; ++i) { result->SetWithoutChecks<false>(i, initial_object); @@ -2191,8 +1893,8 @@ class JNI { ScopedObjectAccess soa(env); mirror::Array* array = soa.Decode<mirror::Array*>(java_array); if (UNLIKELY(!array->GetClass()->IsPrimitiveArray())) { - JniAbortF("GetPrimitiveArrayCritical", "expected primitive array, given %s", - PrettyDescriptor(array->GetClass()).c_str()); + soa.Vm()->JniAbortF("GetPrimitiveArrayCritical", "expected primitive array, given %s", + PrettyDescriptor(array->GetClass()).c_str()); return nullptr; } gc::Heap* heap = Runtime::Current()->GetHeap(); @@ -2201,7 +1903,7 @@ class JNI { // Re-decode in case the object moved since IncrementDisableGC waits for GC to complete. array = soa.Decode<mirror::Array*>(java_array); } - PinPrimitiveArray(soa, array); + soa.Vm()->PinPrimitiveArray(soa.Self(), array); if (is_copy != nullptr) { *is_copy = JNI_FALSE; } @@ -2214,8 +1916,8 @@ class JNI { ScopedObjectAccess soa(env); mirror::Array* array = soa.Decode<mirror::Array*>(java_array); if (UNLIKELY(!array->GetClass()->IsPrimitiveArray())) { - JniAbortF("ReleasePrimitiveArrayCritical", "expected primitive array, given %s", - PrettyDescriptor(array->GetClass()).c_str()); + soa.Vm()->JniAbortF("ReleasePrimitiveArrayCritical", "expected primitive array, given %s", + PrettyDescriptor(array->GetClass()).c_str()); return; } const size_t component_size = array->GetClass()->GetComponentSize(); @@ -2387,8 +2089,9 @@ class JNI { static jint RegisterNativeMethods(JNIEnv* env, jclass java_class, const JNINativeMethod* methods, jint method_count, bool return_errors) { if (UNLIKELY(method_count < 0)) { - JniAbortF("RegisterNatives", "negative method count: %d", method_count); - return JNI_ERR; // Not reached. + JavaVmExtFromEnv(env)->JniAbortF("RegisterNatives", "negative method count: %d", + method_count); + return JNI_ERR; // Not reached except in unit tests. } CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", java_class, JNI_ERR); ScopedObjectAccess soa(env); @@ -2512,17 +2215,21 @@ class JNI { static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) { if (capacity < 0) { - JniAbortF("NewDirectByteBuffer", "negative buffer capacity: %" PRId64, capacity); + JavaVmExtFromEnv(env)->JniAbortF("NewDirectByteBuffer", "negative buffer capacity: %" PRId64, + capacity); return nullptr; } if (address == nullptr && capacity != 0) { - JniAbortF("NewDirectByteBuffer", "non-zero capacity for nullptr pointer: %" PRId64, capacity); + JavaVmExtFromEnv(env)->JniAbortF("NewDirectByteBuffer", + "non-zero capacity for nullptr pointer: %" PRId64, capacity); return nullptr; } // At the moment, the capacity of DirectByteBuffer is limited to a signed int. if (capacity > INT_MAX) { - JniAbortF("NewDirectByteBuffer", "buffer capacity greater than maximum jint: %" PRId64, capacity); + JavaVmExtFromEnv(env)->JniAbortF("NewDirectByteBuffer", + "buffer capacity greater than maximum jint: %" PRId64, + capacity); return nullptr; } jlong address_arg = reinterpret_cast<jlong>(address); @@ -2576,8 +2283,9 @@ class JNI { } private: - static jint EnsureLocalCapacity(ScopedObjectAccess& soa, jint desired_capacity, - const char* caller) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + static jint EnsureLocalCapacityInternal(ScopedObjectAccess& soa, jint desired_capacity, + const char* caller) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { // TODO: we should try to expand the table if necessary. if (desired_capacity < 0 || desired_capacity > static_cast<jint>(kLocalsMax)) { LOG(ERROR) << "Invalid capacity given to " << caller << ": " << desired_capacity; @@ -2594,11 +2302,11 @@ class JNI { template<typename JniT, typename ArtT> static JniT NewPrimitiveArray(JNIEnv* env, jsize length) { + ScopedObjectAccess soa(env); if (UNLIKELY(length < 0)) { - JniAbortF("NewPrimitiveArray", "negative array length: %d", length); + soa.Vm()->JniAbortF("NewPrimitiveArray", "negative array length: %d", length); return nullptr; } - ScopedObjectAccess soa(env); ArtT* result = ArtT::Alloc(soa.Self(), length); return soa.AddLocalReference<JniT>(result); } @@ -2609,9 +2317,11 @@ class JNI { SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { ArtArrayT* array = soa.Decode<ArtArrayT*>(java_array); if (UNLIKELY(ArtArrayT::GetArrayClass() != array->GetClass())) { - JniAbortF(fn_name, "attempt to %s %s primitive array elements with an object of type %s", - operation, PrettyDescriptor(ArtArrayT::GetArrayClass()->GetComponentType()).c_str(), - PrettyDescriptor(array->GetClass()).c_str()); + soa.Vm()->JniAbortF(fn_name, + "attempt to %s %s primitive array elements with an object of type %s", + operation, + PrettyDescriptor(ArtArrayT::GetArrayClass()->GetComponentType()).c_str(), + PrettyDescriptor(array->GetClass()).c_str()); return nullptr; } DCHECK_EQ(sizeof(ElementT), array->GetClass()->GetComponentSize()); @@ -2628,7 +2338,7 @@ class JNI { if (UNLIKELY(array == nullptr)) { return nullptr; } - PinPrimitiveArray(soa, array); + soa.Vm()->PinPrimitiveArray(soa.Self(), array); // Only make a copy if necessary. if (Runtime::Current()->GetHeap()->IsMovableObject(array)) { if (is_copy != nullptr) { @@ -2674,8 +2384,9 @@ class JNI { // heap address. TODO: This might be slow to check, may be worth keeping track of which // copies we make? if (heap->IsNonDiscontinuousSpaceHeapAddress(reinterpret_cast<mirror::Object*>(elements))) { - JniAbortF("ReleaseArrayElements", "invalid element pointer %p, array elements are %p", - reinterpret_cast<void*>(elements), array_data); + soa.Vm()->JniAbortF("ReleaseArrayElements", + "invalid element pointer %p, array elements are %p", + reinterpret_cast<void*>(elements), array_data); return; } } @@ -2690,7 +2401,7 @@ class JNI { // Non copy to a movable object must means that we had disabled the moving GC. heap->DecrementDisableMovingGC(soa.Self()); } - UnpinPrimitiveArray(soa, array); + soa.Vm()->UnpinPrimitiveArray(soa.Self(), array); } } @@ -2971,487 +2682,8 @@ const JNINativeInterface gJniNativeInterface = { JNI::GetObjectRefType, }; -JNIEnvExt::JNIEnvExt(Thread* self, JavaVMExt* vm) - : self(self), - vm(vm), - local_ref_cookie(IRT_FIRST_SEGMENT), - locals(kLocalsInitial, kLocalsMax, kLocal), - check_jni(false), - critical(0), - monitors("monitors", kMonitorsInitial, kMonitorsMax) { - functions = unchecked_functions = &gJniNativeInterface; - if (vm->check_jni) { - SetCheckJniEnabled(true); - } -} - -JNIEnvExt::~JNIEnvExt() { -} - -jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - if (obj == nullptr) { - return nullptr; - } - return reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj)); -} - -void JNIEnvExt::DeleteLocalRef(jobject obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - if (obj != nullptr) { - locals.Remove(local_ref_cookie, reinterpret_cast<IndirectRef>(obj)); - } -} -void JNIEnvExt::SetCheckJniEnabled(bool enabled) { - check_jni = enabled; - functions = enabled ? GetCheckJniNativeInterface() : &gJniNativeInterface; -} - -void JNIEnvExt::DumpReferenceTables(std::ostream& os) { - locals.Dump(os); - monitors.Dump(os); -} - -void JNIEnvExt::PushFrame(int capacity) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - UNUSED(capacity); // cpplint gets confused with (int) and thinks its a cast. - // TODO: take 'capacity' into account. - stacked_local_ref_cookies.push_back(local_ref_cookie); - local_ref_cookie = locals.GetSegmentState(); -} - -void JNIEnvExt::PopFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - locals.SetSegmentState(local_ref_cookie); - local_ref_cookie = stacked_local_ref_cookies.back(); - stacked_local_ref_cookies.pop_back(); -} - -Offset JNIEnvExt::SegmentStateOffset() { - return Offset(OFFSETOF_MEMBER(JNIEnvExt, locals) + - IndirectReferenceTable::SegmentStateOffset().Int32Value()); -} - -// JNI Invocation interface. - -extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { - const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args); - if (IsBadJniVersion(args->version)) { - LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version; - return JNI_EVERSION; - } - RuntimeOptions options; - for (int i = 0; i < args->nOptions; ++i) { - JavaVMOption* option = &args->options[i]; - options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo)); - } - bool ignore_unrecognized = args->ignoreUnrecognized; - if (!Runtime::Create(options, ignore_unrecognized)) { - return JNI_ERR; - } - Runtime* runtime = Runtime::Current(); - bool started = runtime->Start(); - if (!started) { - delete Thread::Current()->GetJniEnv(); - delete runtime->GetJavaVM(); - LOG(WARNING) << "CreateJavaVM failed"; - return JNI_ERR; - } - *p_env = Thread::Current()->GetJniEnv(); - *p_vm = runtime->GetJavaVM(); - return JNI_OK; -} - -extern "C" jint JNI_GetCreatedJavaVMs(JavaVM** vms, jsize, jsize* vm_count) { - Runtime* runtime = Runtime::Current(); - if (runtime == nullptr) { - *vm_count = 0; - } else { - *vm_count = 1; - vms[0] = runtime->GetJavaVM(); - } - return JNI_OK; -} - -// Historically unsupported. -extern "C" jint JNI_GetDefaultJavaVMInitArgs(void* /*vm_args*/) { - return JNI_ERR; -} - -class JII { - public: - static jint DestroyJavaVM(JavaVM* vm) { - if (vm == nullptr) { - return JNI_ERR; - } - JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); - delete raw_vm->runtime; - return JNI_OK; - } - - static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) { - return JII_AttachCurrentThread(vm, p_env, thr_args, false); - } - - static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) { - return JII_AttachCurrentThread(vm, p_env, thr_args, true); - } - - static jint DetachCurrentThread(JavaVM* vm) { - if (vm == nullptr || Thread::Current() == nullptr) { - return JNI_ERR; - } - JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); - Runtime* runtime = raw_vm->runtime; - runtime->DetachCurrentThread(); - return JNI_OK; - } - - static jint GetEnv(JavaVM* vm, void** env, jint version) { - // GetEnv always returns a JNIEnv* for the most current supported JNI version, - // and unlike other calls that take a JNI version doesn't care if you supply - // JNI_VERSION_1_1, which we don't otherwise support. - if (IsBadJniVersion(version) && version != JNI_VERSION_1_1) { - LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version; - return JNI_EVERSION; - } - if (vm == nullptr || env == nullptr) { - return JNI_ERR; - } - Thread* thread = Thread::Current(); - if (thread == nullptr) { - *env = nullptr; - return JNI_EDETACHED; - } - *env = thread->GetJniEnv(); - return JNI_OK; - } -}; - -const JNIInvokeInterface gJniInvokeInterface = { - nullptr, // reserved0 - nullptr, // reserved1 - nullptr, // reserved2 - JII::DestroyJavaVM, - JII::AttachCurrentThread, - JII::DetachCurrentThread, - JII::GetEnv, - JII::AttachCurrentThreadAsDaemon -}; - -JavaVMExt::JavaVMExt(Runtime* runtime, ParsedOptions* options) - : runtime(runtime), - check_jni_abort_hook(nullptr), - check_jni_abort_hook_data(nullptr), - check_jni(false), - force_copy(false), // TODO: add a way to enable this - trace(options->jni_trace_), - pins_lock("JNI pin table lock", kPinTableLock), - pin_table("pin table", kPinTableInitial, kPinTableMax), - globals_lock("JNI global reference table lock"), - globals(gGlobalsInitial, gGlobalsMax, kGlobal), - libraries_lock("JNI shared libraries map lock", kLoadLibraryLock), - libraries(new Libraries), - weak_globals_lock_("JNI weak global reference table lock"), - weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal), - allow_new_weak_globals_(true), - weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) { - functions = unchecked_functions = &gJniInvokeInterface; - if (options->check_jni_) { - SetCheckJniEnabled(true); - } -} - -JavaVMExt::~JavaVMExt() { - delete libraries; -} - -jweak JavaVMExt::AddWeakGlobalReference(Thread* self, mirror::Object* obj) { - if (obj == nullptr) { - return nullptr; - } - MutexLock mu(self, weak_globals_lock_); - while (UNLIKELY(!allow_new_weak_globals_)) { - weak_globals_add_condition_.WaitHoldingLocks(self); - } - IndirectRef ref = weak_globals_.Add(IRT_FIRST_SEGMENT, obj); - return reinterpret_cast<jweak>(ref); -} - -void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) { - MutexLock mu(self, weak_globals_lock_); - if (!weak_globals_.Remove(IRT_FIRST_SEGMENT, obj)) { - LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") " - << "failed to find entry"; - } -} - -void JavaVMExt::SetCheckJniEnabled(bool enabled) { - check_jni = enabled; - functions = enabled ? GetCheckJniInvokeInterface() : &gJniInvokeInterface; -} - -void JavaVMExt::DumpForSigQuit(std::ostream& os) { - os << "JNI: CheckJNI is " << (check_jni ? "on" : "off"); - if (force_copy) { - os << " (with forcecopy)"; - } - Thread* self = Thread::Current(); - { - MutexLock mu(self, pins_lock); - os << "; pins=" << pin_table.Size(); - } - { - ReaderMutexLock mu(self, globals_lock); - os << "; globals=" << globals.Capacity(); - } - { - MutexLock mu(self, weak_globals_lock_); - if (weak_globals_.Capacity() > 0) { - os << " (plus " << weak_globals_.Capacity() << " weak)"; - } - } - os << '\n'; - - { - MutexLock mu(self, libraries_lock); - os << "Libraries: " << Dumpable<Libraries>(*libraries) << " (" << libraries->size() << ")\n"; - } -} - -void JavaVMExt::DisallowNewWeakGlobals() { - MutexLock mu(Thread::Current(), weak_globals_lock_); - allow_new_weak_globals_ = false; -} - -void JavaVMExt::AllowNewWeakGlobals() { - Thread* self = Thread::Current(); - MutexLock mu(self, weak_globals_lock_); - allow_new_weak_globals_ = true; - weak_globals_add_condition_.Broadcast(self); -} - -mirror::Object* JavaVMExt::DecodeWeakGlobal(Thread* self, IndirectRef ref) { - MutexLock mu(self, weak_globals_lock_); - while (UNLIKELY(!allow_new_weak_globals_)) { - weak_globals_add_condition_.WaitHoldingLocks(self); - } - return weak_globals_.Get(ref); -} - -void JavaVMExt::DumpReferenceTables(std::ostream& os) { - Thread* self = Thread::Current(); - { - ReaderMutexLock mu(self, globals_lock); - globals.Dump(os); - } - { - MutexLock mu(self, weak_globals_lock_); - weak_globals_.Dump(os); - } - { - MutexLock mu(self, pins_lock); - pin_table.Dump(os); - } -} - -bool JavaVMExt::LoadNativeLibrary(const std::string& path, - Handle<mirror::ClassLoader> class_loader, - std::string* detail) { - detail->clear(); - - // See if we've already loaded this library. If we have, and the class loader - // matches, return successfully without doing anything. - // TODO: for better results we should canonicalize the pathname (or even compare - // inodes). This implementation is fine if everybody is using System.loadLibrary. - SharedLibrary* library; - Thread* self = Thread::Current(); - { - // TODO: move the locking (and more of this logic) into Libraries. - MutexLock mu(self, libraries_lock); - library = libraries->Get(path); - } - if (library != nullptr) { - if (library->GetClassLoader() != class_loader.Get()) { - // The library will be associated with class_loader. The JNI - // spec says we can't load the same library into more than one - // class loader. - StringAppendF(detail, "Shared library \"%s\" already opened by " - "ClassLoader %p; can't open in ClassLoader %p", - path.c_str(), library->GetClassLoader(), class_loader.Get()); - LOG(WARNING) << detail; - return false; - } - VLOG(jni) << "[Shared library \"" << path << "\" already loaded in " - << "ClassLoader " << class_loader.Get() << "]"; - if (!library->CheckOnLoadResult()) { - StringAppendF(detail, "JNI_OnLoad failed on a previous attempt " - "to load \"%s\"", path.c_str()); - return false; - } - return true; - } - - // Open the shared library. Because we're using a full path, the system - // doesn't have to search through LD_LIBRARY_PATH. (It may do so to - // resolve this library's dependencies though.) - - // Failures here are expected when java.library.path has several entries - // and we have to hunt for the lib. - - // Below we dlopen but there is no paired dlclose, this would be necessary if we supported - // class unloading. Libraries will only be unloaded when the reference count (incremented by - // dlopen) becomes zero from dlclose. - - // This can execute slowly for a large library on a busy system, so we - // want to switch from kRunnable while it executes. This allows the GC to ignore us. - self->TransitionFromRunnableToSuspended(kWaitingForJniOnLoad); - const char* path_str = path.empty() ? nullptr : path.c_str(); - void* handle = dlopen(path_str, RTLD_LAZY); - bool needs_native_bridge = false; - if (handle == nullptr) { - if (NativeBridge::IsSupported(path_str)) { - handle = NativeBridge::LoadLibrary(path_str, RTLD_LAZY); - needs_native_bridge = true; - } - } - self->TransitionFromSuspendedToRunnable(); - - VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]"; - - if (handle == nullptr) { - *detail = dlerror(); - LOG(ERROR) << "dlopen(\"" << path << "\", RTLD_LAZY) failed: " << *detail; - return false; - } - - // Create a new entry. - // TODO: move the locking (and more of this logic) into Libraries. - bool created_library = false; - { - MutexLock mu(self, libraries_lock); - library = libraries->Get(path); - if (library == nullptr) { // We won race to get libraries_lock - library = new SharedLibrary(path, handle, class_loader.Get()); - libraries->Put(path, library); - created_library = true; - } - } - if (!created_library) { - LOG(INFO) << "WOW: we lost a race to add shared library: " - << "\"" << path << "\" ClassLoader=" << class_loader.Get(); - return library->CheckOnLoadResult(); - } - - VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader.Get() - << "]"; - - bool was_successful = false; - void* sym = nullptr; - if (UNLIKELY(needs_native_bridge)) { - library->SetNeedsNativeBridge(); - sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr); - } else { - sym = dlsym(handle, "JNI_OnLoad"); - } - - if (sym == nullptr) { - VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; - was_successful = true; - } else { - // Call JNI_OnLoad. We have to override the current class - // loader, which will always be "null" since the stuff at the - // top of the stack is around Runtime.loadLibrary(). (See - // the comments in the JNI FindClass function.) - typedef int (*JNI_OnLoadFn)(JavaVM*, void*); - JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym); - StackHandleScope<1> hs(self); - Handle<mirror::ClassLoader> old_class_loader(hs.NewHandle(self->GetClassLoaderOverride())); - self->SetClassLoaderOverride(class_loader.Get()); - - int version = 0; - { - ScopedThreadStateChange tsc(self, kNative); - VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; - version = (*jni_on_load)(this, nullptr); - } - - self->SetClassLoaderOverride(old_class_loader.Get()); - - if (version == JNI_ERR) { - StringAppendF(detail, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); - } else if (IsBadJniVersion(version)) { - StringAppendF(detail, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d", - path.c_str(), version); - // It's unwise to call dlclose() here, but we can mark it - // as bad and ensure that future load attempts will fail. - // We don't know how far JNI_OnLoad got, so there could - // be some partially-initialized stuff accessible through - // newly-registered native method calls. We could try to - // unregister them, but that doesn't seem worthwhile. - } else { - was_successful = true; - } - VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure") - << " from JNI_OnLoad in \"" << path << "\"]"; - } - - library->SetResult(was_successful); - return was_successful; -} - -void* JavaVMExt::FindCodeForNativeMethod(mirror::ArtMethod* m) { - CHECK(m->IsNative()); - mirror::Class* c = m->GetDeclaringClass(); - // If this is a static method, it could be called before the class has been initialized. - if (m->IsStatic()) { - c = EnsureInitialized(Thread::Current(), c); - if (c == nullptr) { - return nullptr; - } - } else { - CHECK(c->IsInitializing()) << c->GetStatus() << " " << PrettyMethod(m); - } - std::string detail; - void* native_method; - Thread* self = Thread::Current(); - { - MutexLock mu(self, libraries_lock); - native_method = libraries->FindNativeMethod(m, detail); - } - // Throwing can cause libraries_lock to be reacquired. - if (native_method == nullptr) { - ThrowLocation throw_location = self->GetCurrentLocationForThrow(); - self->ThrowNewException(throw_location, "Ljava/lang/UnsatisfiedLinkError;", detail.c_str()); - } - return native_method; -} - -void JavaVMExt::SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg) { - MutexLock mu(Thread::Current(), weak_globals_lock_); - for (mirror::Object** entry : weak_globals_) { - // Since this is called by the GC, we don't need a read barrier. - mirror::Object* obj = *entry; - mirror::Object* new_obj = callback(obj, arg); - if (new_obj == nullptr) { - new_obj = kClearedJniWeakGlobal; - } - *entry = new_obj; - } -} - -void JavaVMExt::VisitRoots(RootCallback* callback, void* arg) { - Thread* self = Thread::Current(); - { - ReaderMutexLock mu(self, globals_lock); - globals.VisitRoots(callback, arg, 0, kRootJNIGlobal); - } - { - MutexLock mu(self, pins_lock); - pin_table.VisitRoots(callback, arg, 0, kRootVMInternal); - } - { - MutexLock mu(self, libraries_lock); - // Libraries contains shared libraries which hold a pointer to a class loader. - libraries->VisitRoots(callback, arg); - } - // The weak_globals table is visited by the GC itself (because it mutates the table). +const JNINativeInterface* GetJniNativeInterface() { + return &gJniNativeInterface; } void RegisterNativeMethods(JNIEnv* env, const char* jni_class_name, const JNINativeMethod* methods, diff --git a/runtime/jni_internal.h b/runtime/jni_internal.h index ac502e6f0b..48b10f5825 100644 --- a/runtime/jni_internal.h +++ b/runtime/jni_internal.h @@ -17,16 +17,8 @@ #ifndef ART_RUNTIME_JNI_INTERNAL_H_ #define ART_RUNTIME_JNI_INTERNAL_H_ -#include "jni.h" - -#include "base/macros.h" -#include "base/mutex.h" -#include "indirect_reference_table.h" -#include "object_callbacks.h" -#include "reference_table.h" - +#include <jni.h> #include <iosfwd> -#include <string> #ifndef NATIVE_METHOD #define NATIVE_METHOD(className, functionName, signature) \ @@ -36,189 +28,18 @@ RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods)) namespace art { -namespace mirror { - class ArtField; - class ArtMethod; - class ClassLoader; -} // namespace mirror -union JValue; -class Libraries; -class ParsedOptions; -class Runtime; -class ScopedObjectAccess; -template<class T> class Handle; -class Thread; -void JniAbortF(const char* jni_function_name, const char* fmt, ...) - __attribute__((__format__(__printf__, 2, 3))); +const JNINativeInterface* GetJniNativeInterface(); + +// Similar to RegisterNatives except its passed a descriptor for a class name and failures are +// fatal. void RegisterNativeMethods(JNIEnv* env, const char* jni_class_name, const JNINativeMethod* methods, jint method_count); int ThrowNewException(JNIEnv* env, jclass exception_class, const char* msg, jobject cause); -class JavaVMExt : public JavaVM { - public: - JavaVMExt(Runtime* runtime, ParsedOptions* options); - ~JavaVMExt(); - - /** - * Loads the given shared library. 'path' is an absolute pathname. - * - * Returns 'true' on success. On failure, sets 'detail' to a - * human-readable description of the error. - */ - bool LoadNativeLibrary(const std::string& path, Handle<mirror::ClassLoader> class_loader, - std::string* detail) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - /** - * Returns a pointer to the code for the native method 'm', found - * using dlsym(3) on every native library that's been loaded so far. - */ - void* FindCodeForNativeMethod(mirror::ArtMethod* m) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - void DumpForSigQuit(std::ostream& os); - - void DumpReferenceTables(std::ostream& os) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - void SetCheckJniEnabled(bool enabled); - - void VisitRoots(RootCallback* callback, void* arg) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - void DisallowNewWeakGlobals() EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_); - void AllowNewWeakGlobals() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - jweak AddWeakGlobalReference(Thread* self, mirror::Object* obj) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - void DeleteWeakGlobalRef(Thread* self, jweak obj) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - void SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - mirror::Object* DecodeWeakGlobal(Thread* self, IndirectRef ref) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - Runtime* runtime; - - // Used for testing. By default, we'll LOG(FATAL) the reason. - void (*check_jni_abort_hook)(void* data, const std::string& reason); - void* check_jni_abort_hook_data; - - // Extra checking. - bool check_jni; - bool force_copy; - - // Extra diagnostics. - std::string trace; - - // Used to hold references to pinned primitive arrays. - Mutex pins_lock DEFAULT_MUTEX_ACQUIRED_AFTER; - ReferenceTable pin_table GUARDED_BY(pins_lock); - - // JNI global references. - ReaderWriterMutex globals_lock DEFAULT_MUTEX_ACQUIRED_AFTER; - // Not guarded by globals_lock since we sometimes use SynchronizedGet in Thread::DecodeJObject. - IndirectReferenceTable globals; - - Mutex libraries_lock DEFAULT_MUTEX_ACQUIRED_AFTER; - Libraries* libraries GUARDED_BY(libraries_lock); - - // Used by -Xcheck:jni. - const JNIInvokeInterface* unchecked_functions; - - private: - // TODO: Make the other members of this class also private. - // JNI weak global references. - Mutex weak_globals_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; - // Since weak_globals_ contain weak roots, be careful not to - // directly access the object references in it. Use Get() with the - // read barrier enabled. - IndirectReferenceTable weak_globals_ GUARDED_BY(weak_globals_lock_); - bool allow_new_weak_globals_ GUARDED_BY(weak_globals_lock_); - ConditionVariable weak_globals_add_condition_ GUARDED_BY(weak_globals_lock_); -}; - -struct JNIEnvExt : public JNIEnv { - JNIEnvExt(Thread* self, JavaVMExt* vm); - ~JNIEnvExt(); - - void DumpReferenceTables(std::ostream& os) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - void SetCheckJniEnabled(bool enabled); - - void PushFrame(int capacity); - void PopFrame(); - - template<typename T> - T AddLocalReference(mirror::Object* obj) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - static Offset SegmentStateOffset(); - - static Offset LocalRefCookieOffset() { - return Offset(OFFSETOF_MEMBER(JNIEnvExt, local_ref_cookie)); - } - - static Offset SelfOffset() { - return Offset(OFFSETOF_MEMBER(JNIEnvExt, self)); - } - - jobject NewLocalRef(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - void DeleteLocalRef(jobject obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - Thread* const self; - JavaVMExt* vm; - - // Cookie used when using the local indirect reference table. - uint32_t local_ref_cookie; - - // JNI local references. - IndirectReferenceTable locals GUARDED_BY(Locks::mutator_lock_); - - // Stack of cookies corresponding to PushLocalFrame/PopLocalFrame calls. - // TODO: to avoid leaks (and bugs), we need to clear this vector on entry (or return) - // to a native method. - std::vector<uint32_t> stacked_local_ref_cookies; - - // Frequently-accessed fields cached from JavaVM. - bool check_jni; - - // How many nested "critical" JNI calls are we in? - int critical; - - // Entered JNI monitors, for bulk exit on thread detach. - ReferenceTable monitors; - - // Used by -Xcheck:jni. - const JNINativeInterface* unchecked_functions; -}; - -const JNINativeInterface* GetCheckJniNativeInterface(); -const JNIInvokeInterface* GetCheckJniInvokeInterface(); - -// Used to save and restore the JNIEnvExt state when not going through code created by the JNI -// compiler -class ScopedJniEnvLocalRefState { - public: - explicit ScopedJniEnvLocalRefState(JNIEnvExt* env) : env_(env) { - saved_local_ref_cookie_ = env->local_ref_cookie; - env->local_ref_cookie = env->locals.GetSegmentState(); - } - - ~ScopedJniEnvLocalRefState() { - env_->locals.SetSegmentState(env_->local_ref_cookie); - env_->local_ref_cookie = saved_local_ref_cookie_; - } - - private: - JNIEnvExt* env_; - uint32_t saved_local_ref_cookie_; - DISALLOW_COPY_AND_ASSIGN(ScopedJniEnvLocalRefState); -}; - } // namespace art std::ostream& operator<<(std::ostream& os, const jobjectRefType& rhs); + #endif // ART_RUNTIME_JNI_INTERNAL_H_ diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc index da3080f218..844d14a063 100644 --- a/runtime/jni_internal_test.cc +++ b/runtime/jni_internal_test.cc @@ -17,6 +17,7 @@ #include "jni_internal.h" #include "common_compiler_test.h" +#include "java_vm_ext.h" #include "mirror/art_method-inl.h" #include "mirror/string-inl.h" #include "scoped_thread_state_change.h" @@ -53,24 +54,15 @@ class JniInternalTest : public CommonCompilerTest { } void ExpectException(jclass exception_class) { - EXPECT_TRUE(env_->ExceptionCheck()); + ScopedObjectAccess soa(env_); + EXPECT_TRUE(env_->ExceptionCheck()) + << PrettyDescriptor(soa.Decode<mirror::Class*>(exception_class)); jthrowable exception = env_->ExceptionOccurred(); EXPECT_NE(nullptr, exception); env_->ExceptionClear(); EXPECT_TRUE(env_->IsInstanceOf(exception, exception_class)); } - void ExpectClassFound(const char* name) { - EXPECT_NE(env_->FindClass(name), nullptr) << name; - EXPECT_FALSE(env_->ExceptionCheck()) << name; - } - - void ExpectClassNotFound(const char* name) { - EXPECT_EQ(env_->FindClass(name), nullptr) << name; - EXPECT_TRUE(env_->ExceptionCheck()) << name; - env_->ExceptionClear(); - } - void CleanUpJniEnv() { if (aioobe_ != nullptr) { env_->DeleteGlobalRef(aioobe_); @@ -98,6 +90,510 @@ class JniInternalTest : public CommonCompilerTest { return soa.AddLocalReference<jclass>(c); } + void ExpectClassFound(const char* name) { + EXPECT_NE(env_->FindClass(name), nullptr) << name; + EXPECT_FALSE(env_->ExceptionCheck()) << name; + } + + void ExpectClassNotFound(const char* name, bool check_jni, const char* check_jni_msg, + CheckJniAbortCatcher* abort_catcher) { + EXPECT_EQ(env_->FindClass(name), nullptr) << name; + if (!check_jni || check_jni_msg == nullptr) { + EXPECT_TRUE(env_->ExceptionCheck()) << name; + env_->ExceptionClear(); + } else { + abort_catcher->Check(check_jni_msg); + } + } + + void FindClassTest(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher check_jni_abort_catcher; + + // Null argument is always an abort. + env_->FindClass(nullptr); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "name == null"); + + // Reference types... + ExpectClassFound("java/lang/String"); + // ...for arrays too, where you must include "L;". + ExpectClassFound("[Ljava/lang/String;"); + // Primitive arrays are okay too, if the primitive type is valid. + ExpectClassFound("[C"); + + // But primitive types aren't allowed... + ExpectClassNotFound("C", check_jni, nullptr, &check_jni_abort_catcher); + ExpectClassNotFound("V", check_jni, nullptr, &check_jni_abort_catcher); + ExpectClassNotFound("K", check_jni, nullptr, &check_jni_abort_catcher); + + if (check_jni) { + // Check JNI will reject invalid class names as aborts but without pending exceptions. + EXPECT_EQ(env_->FindClass("java.lang.String"), nullptr); + EXPECT_FALSE(env_->ExceptionCheck()); + check_jni_abort_catcher.Check("illegal class name 'java.lang.String'"); + + EXPECT_EQ(env_->FindClass("[Ljava.lang.String;"), nullptr); + EXPECT_FALSE(env_->ExceptionCheck()); + check_jni_abort_catcher.Check("illegal class name '[Ljava.lang.String;'"); + } else { + // Without check JNI we're tolerant and replace '.' with '/'. + ExpectClassFound("java.lang.String"); + ExpectClassFound("[Ljava.lang.String;"); + } + + ExpectClassNotFound("Ljava.lang.String;", check_jni, "illegal class name 'Ljava.lang.String;'", + &check_jni_abort_catcher); + ExpectClassNotFound("[java.lang.String", check_jni, "illegal class name '[java.lang.String'", + &check_jni_abort_catcher); + + // You can't include the "L;" in a JNI class descriptor. + ExpectClassNotFound("Ljava/lang/String;", check_jni, "illegal class name 'Ljava/lang/String;'", + &check_jni_abort_catcher); + + // But you must include it for an array of any reference type. + ExpectClassNotFound("[java/lang/String", check_jni, "illegal class name '[java/lang/String'", + &check_jni_abort_catcher); + + ExpectClassNotFound("[K", check_jni, "illegal class name '[K'", &check_jni_abort_catcher); + + // Void arrays aren't allowed. + ExpectClassNotFound("[V", check_jni, "illegal class name '[V'", &check_jni_abort_catcher); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void GetFieldIdBadArgumentTest(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher check_jni_abort_catcher; + + jclass c = env_->FindClass("java/lang/String"); + ASSERT_NE(c, nullptr); + + jfieldID fid = env_->GetFieldID(nullptr, "count", "I"); + EXPECT_EQ(nullptr, fid); + check_jni_abort_catcher.Check(check_jni ? "GetFieldID received NULL jclass" + : "java_class == null"); + fid = env_->GetFieldID(c, nullptr, "I"); + EXPECT_EQ(nullptr, fid); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "name == null"); + fid = env_->GetFieldID(c, "count", nullptr); + EXPECT_EQ(nullptr, fid); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "sig == null"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void GetStaticFieldIdBadArgumentTest(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher check_jni_abort_catcher; + + jclass c = env_->FindClass("java/lang/String"); + ASSERT_NE(c, nullptr); + + jfieldID fid = env_->GetStaticFieldID(nullptr, "CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;"); + EXPECT_EQ(nullptr, fid); + check_jni_abort_catcher.Check(check_jni ? "GetStaticFieldID received NULL jclass" + : "java_class == null"); + fid = env_->GetStaticFieldID(c, nullptr, "Ljava/util/Comparator;"); + EXPECT_EQ(nullptr, fid); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "name == null"); + fid = env_->GetStaticFieldID(c, "CASE_INSENSITIVE_ORDER", nullptr); + EXPECT_EQ(nullptr, fid); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "sig == null"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void GetMethodIdBadArgumentTest(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher check_jni_abort_catcher; + + jmethodID method = env_->GetMethodID(nullptr, "<init>", "(Ljava/lang/String;)V"); + EXPECT_EQ(nullptr, method); + check_jni_abort_catcher.Check(check_jni ? "GetMethodID received NULL jclass" + : "java_class == null"); + jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError"); + ASSERT_TRUE(jlnsme != nullptr); + method = env_->GetMethodID(jlnsme, nullptr, "(Ljava/lang/String;)V"); + EXPECT_EQ(nullptr, method); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "name == null"); + method = env_->GetMethodID(jlnsme, "<init>", nullptr); + EXPECT_EQ(nullptr, method); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "sig == null"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void GetStaticMethodIdBadArgumentTest(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher check_jni_abort_catcher; + + jmethodID method = env_->GetStaticMethodID(nullptr, "valueOf", "(I)Ljava/lang/String;"); + EXPECT_EQ(nullptr, method); + check_jni_abort_catcher.Check(check_jni ? "GetStaticMethodID received NULL jclass" + : "java_class == null"); + jclass jlstring = env_->FindClass("java/lang/String"); + method = env_->GetStaticMethodID(jlstring, nullptr, "(I)Ljava/lang/String;"); + EXPECT_EQ(nullptr, method); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "name == null"); + method = env_->GetStaticMethodID(jlstring, "valueOf", nullptr); + EXPECT_EQ(nullptr, method); + check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL" + : "sig == null"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void GetFromReflectedField_ToReflectedFieldBadArgumentTest(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher check_jni_abort_catcher; + + jclass c = env_->FindClass("java/lang/String"); + ASSERT_NE(c, nullptr); + jfieldID fid = env_->GetFieldID(c, "count", "I"); + ASSERT_NE(fid, nullptr); + + // Check class argument for null argument, not checked in non-check JNI. + jobject field = env_->ToReflectedField(nullptr, fid, JNI_FALSE); + if (check_jni) { + EXPECT_EQ(field, nullptr); + check_jni_abort_catcher.Check("ToReflectedField received NULL jclass"); + } else { + EXPECT_NE(field, nullptr); + } + + field = env_->ToReflectedField(c, nullptr, JNI_FALSE); + EXPECT_EQ(field, nullptr); + check_jni_abort_catcher.Check(check_jni ? "jfieldID was NULL" + : "fid == null"); + + fid = env_->FromReflectedField(nullptr); + ASSERT_EQ(fid, nullptr); + check_jni_abort_catcher.Check(check_jni ? "expected non-null java.lang.reflect.Field" + : "jlr_field == null"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void GetFromReflectedMethod_ToReflectedMethodBadArgumentTest(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher check_jni_abort_catcher; + + jclass c = env_->FindClass("java/lang/String"); + ASSERT_NE(c, nullptr); + jmethodID mid = env_->GetMethodID(c, "<init>", "()V"); + ASSERT_NE(mid, nullptr); + + // Check class argument for null argument, not checked in non-check JNI. + jobject method = env_->ToReflectedMethod(nullptr, mid, JNI_FALSE); + if (check_jni) { + EXPECT_EQ(method, nullptr); + check_jni_abort_catcher.Check("ToReflectedMethod received NULL jclass"); + } else { + EXPECT_NE(method, nullptr); + } + + method = env_->ToReflectedMethod(c, nullptr, JNI_FALSE); + EXPECT_EQ(method, nullptr); + check_jni_abort_catcher.Check(check_jni ? "jmethodID was NULL" + : "mid == null"); + mid = env_->FromReflectedMethod(method); + ASSERT_EQ(mid, nullptr); + check_jni_abort_catcher.Check(check_jni ? "expected non-null method" : "jlr_method == null"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void RegisterAndUnregisterNativesBadArguments(bool check_jni, + CheckJniAbortCatcher* check_jni_abort_catcher) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + // Passing a class of null is a failure. + { + JNINativeMethod methods[] = { }; + EXPECT_EQ(env_->RegisterNatives(nullptr, methods, 0), JNI_ERR); + check_jni_abort_catcher->Check(check_jni ? "RegisterNatives received NULL jclass" + : "java_class == null"); + } + + // Passing methods as null is a failure. + jclass jlobject = env_->FindClass("java/lang/Object"); + EXPECT_EQ(env_->RegisterNatives(jlobject, nullptr, 1), JNI_ERR); + check_jni_abort_catcher->Check("methods == null"); + + // Unregisters null is a failure. + EXPECT_EQ(env_->UnregisterNatives(nullptr), JNI_ERR); + check_jni_abort_catcher->Check(check_jni ? "UnregisterNatives received NULL jclass" + : "java_class == null"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + + void GetPrimitiveArrayElementsOfWrongType(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher jni_abort_catcher; + + jbooleanArray array = env_->NewBooleanArray(10); + jboolean is_copy; + EXPECT_EQ(env_->GetByteArrayElements(reinterpret_cast<jbyteArray>(array), &is_copy), nullptr); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected byte[]" + : "attempt to get byte primitive array elements with an object of type boolean[]"); + EXPECT_EQ(env_->GetShortArrayElements(reinterpret_cast<jshortArray>(array), &is_copy), nullptr); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected short[]" + : "attempt to get short primitive array elements with an object of type boolean[]"); + EXPECT_EQ(env_->GetCharArrayElements(reinterpret_cast<jcharArray>(array), &is_copy), nullptr); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected char[]" + : "attempt to get char primitive array elements with an object of type boolean[]"); + EXPECT_EQ(env_->GetIntArrayElements(reinterpret_cast<jintArray>(array), &is_copy), nullptr); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected int[]" + : "attempt to get int primitive array elements with an object of type boolean[]"); + EXPECT_EQ(env_->GetLongArrayElements(reinterpret_cast<jlongArray>(array), &is_copy), nullptr); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected long[]" + : "attempt to get long primitive array elements with an object of type boolean[]"); + EXPECT_EQ(env_->GetFloatArrayElements(reinterpret_cast<jfloatArray>(array), &is_copy), nullptr); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected float[]" + : "attempt to get float primitive array elements with an object of type boolean[]"); + EXPECT_EQ(env_->GetDoubleArrayElements(reinterpret_cast<jdoubleArray>(array), &is_copy), nullptr); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected double[]" + : "attempt to get double primitive array elements with an object of type boolean[]"); + jbyteArray array2 = env_->NewByteArray(10); + EXPECT_EQ(env_->GetBooleanArrayElements(reinterpret_cast<jbooleanArray>(array2), &is_copy), + nullptr); + jni_abort_catcher.Check( + check_jni ? "incompatible array type byte[] expected boolean[]" + : "attempt to get boolean primitive array elements with an object of type byte[]"); + jobject object = env_->NewStringUTF("Test String"); + EXPECT_EQ(env_->GetBooleanArrayElements(reinterpret_cast<jbooleanArray>(object), &is_copy), + nullptr); + jni_abort_catcher.Check( + check_jni ? "jarray argument has non-array type: java.lang.String" + : "attempt to get boolean primitive array elements with an object of type java.lang.String"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void ReleasePrimitiveArrayElementsOfWrongType(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher jni_abort_catcher; + + jbooleanArray array = env_->NewBooleanArray(10); + ASSERT_TRUE(array != nullptr); + jboolean is_copy; + jboolean* elements = env_->GetBooleanArrayElements(array, &is_copy); + ASSERT_TRUE(elements != nullptr); + env_->ReleaseByteArrayElements(reinterpret_cast<jbyteArray>(array), + reinterpret_cast<jbyte*>(elements), 0); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected byte[]" + : "attempt to release byte primitive array elements with an object of type boolean[]"); + env_->ReleaseShortArrayElements(reinterpret_cast<jshortArray>(array), + reinterpret_cast<jshort*>(elements), 0); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected short[]" + : "attempt to release short primitive array elements with an object of type boolean[]"); + env_->ReleaseCharArrayElements(reinterpret_cast<jcharArray>(array), + reinterpret_cast<jchar*>(elements), 0); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected char[]" + : "attempt to release char primitive array elements with an object of type boolean[]"); + env_->ReleaseIntArrayElements(reinterpret_cast<jintArray>(array), + reinterpret_cast<jint*>(elements), 0); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected int[]" + : "attempt to release int primitive array elements with an object of type boolean[]"); + env_->ReleaseLongArrayElements(reinterpret_cast<jlongArray>(array), + reinterpret_cast<jlong*>(elements), 0); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected long[]" + : "attempt to release long primitive array elements with an object of type boolean[]"); + env_->ReleaseFloatArrayElements(reinterpret_cast<jfloatArray>(array), + reinterpret_cast<jfloat*>(elements), 0); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected float[]" + : "attempt to release float primitive array elements with an object of type boolean[]"); + env_->ReleaseDoubleArrayElements(reinterpret_cast<jdoubleArray>(array), + reinterpret_cast<jdouble*>(elements), 0); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected double[]" + : "attempt to release double primitive array elements with an object of type boolean[]"); + jbyteArray array2 = env_->NewByteArray(10); + env_->ReleaseBooleanArrayElements(reinterpret_cast<jbooleanArray>(array2), elements, 0); + jni_abort_catcher.Check( + check_jni ? "incompatible array type byte[] expected boolean[]" + : "attempt to release boolean primitive array elements with an object of type byte[]"); + jobject object = env_->NewStringUTF("Test String"); + env_->ReleaseBooleanArrayElements(reinterpret_cast<jbooleanArray>(object), elements, 0); + jni_abort_catcher.Check( + check_jni ? "jarray argument has non-array type: java.lang.String" + : "attempt to release boolean primitive array elements with an object of type " + "java.lang.String"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void GetReleasePrimitiveArrayCriticalOfWrongType(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher jni_abort_catcher; + + jobject object = env_->NewStringUTF("Test String"); + jboolean is_copy; + void* elements = env_->GetPrimitiveArrayCritical(reinterpret_cast<jarray>(object), &is_copy); + jni_abort_catcher.Check(check_jni ? "jarray argument has non-array type: java.lang.String" + : "expected primitive array, given java.lang.String"); + env_->ReleasePrimitiveArrayCritical(reinterpret_cast<jarray>(object), elements, 0); + jni_abort_catcher.Check(check_jni ? "jarray argument has non-array type: java.lang.String" + : "expected primitive array, given java.lang.String"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void GetPrimitiveArrayRegionElementsOfWrongType(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher jni_abort_catcher; + constexpr size_t kLength = 10; + jbooleanArray array = env_->NewBooleanArray(kLength); + ASSERT_TRUE(array != nullptr); + jboolean elements[kLength]; + env_->GetByteArrayRegion(reinterpret_cast<jbyteArray>(array), 0, kLength, + reinterpret_cast<jbyte*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected byte[]" + : "attempt to get region of byte primitive array elements with an object of type boolean[]"); + env_->GetShortArrayRegion(reinterpret_cast<jshortArray>(array), 0, kLength, + reinterpret_cast<jshort*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected short[]" + : "attempt to get region of short primitive array elements with an object of type boolean[]"); + env_->GetCharArrayRegion(reinterpret_cast<jcharArray>(array), 0, kLength, + reinterpret_cast<jchar*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected char[]" + : "attempt to get region of char primitive array elements with an object of type boolean[]"); + env_->GetIntArrayRegion(reinterpret_cast<jintArray>(array), 0, kLength, + reinterpret_cast<jint*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected int[]" + : "attempt to get region of int primitive array elements with an object of type boolean[]"); + env_->GetLongArrayRegion(reinterpret_cast<jlongArray>(array), 0, kLength, + reinterpret_cast<jlong*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected long[]" + : "attempt to get region of long primitive array elements with an object of type boolean[]"); + env_->GetFloatArrayRegion(reinterpret_cast<jfloatArray>(array), 0, kLength, + reinterpret_cast<jfloat*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected float[]" + : "attempt to get region of float primitive array elements with an object of type boolean[]"); + env_->GetDoubleArrayRegion(reinterpret_cast<jdoubleArray>(array), 0, kLength, + reinterpret_cast<jdouble*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected double[]" + : "attempt to get region of double primitive array elements with an object of type boolean[]"); + jbyteArray array2 = env_->NewByteArray(10); + env_->GetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(array2), 0, kLength, + reinterpret_cast<jboolean*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type byte[] expected boolean[]" + : "attempt to get region of boolean primitive array elements with an object of type byte[]"); + jobject object = env_->NewStringUTF("Test String"); + env_->GetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(object), 0, kLength, + reinterpret_cast<jboolean*>(elements)); + jni_abort_catcher.Check(check_jni ? "jarray argument has non-array type: java.lang.String" + : "attempt to get region of boolean primitive array elements with an object of type " + "java.lang.String"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void SetPrimitiveArrayRegionElementsOfWrongType(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher jni_abort_catcher; + constexpr size_t kLength = 10; + jbooleanArray array = env_->NewBooleanArray(kLength); + ASSERT_TRUE(array != nullptr); + jboolean elements[kLength]; + env_->SetByteArrayRegion(reinterpret_cast<jbyteArray>(array), 0, kLength, + reinterpret_cast<jbyte*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected byte[]" + : "attempt to set region of byte primitive array elements with an object of type boolean[]"); + env_->SetShortArrayRegion(reinterpret_cast<jshortArray>(array), 0, kLength, + reinterpret_cast<jshort*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected short[]" + : "attempt to set region of short primitive array elements with an object of type boolean[]"); + env_->SetCharArrayRegion(reinterpret_cast<jcharArray>(array), 0, kLength, + reinterpret_cast<jchar*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected char[]" + : "attempt to set region of char primitive array elements with an object of type boolean[]"); + env_->SetIntArrayRegion(reinterpret_cast<jintArray>(array), 0, kLength, + reinterpret_cast<jint*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected int[]" + : "attempt to set region of int primitive array elements with an object of type boolean[]"); + env_->SetLongArrayRegion(reinterpret_cast<jlongArray>(array), 0, kLength, + reinterpret_cast<jlong*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected long[]" + : "attempt to set region of long primitive array elements with an object of type boolean[]"); + env_->SetFloatArrayRegion(reinterpret_cast<jfloatArray>(array), 0, kLength, + reinterpret_cast<jfloat*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected float[]" + : "attempt to set region of float primitive array elements with an object of type boolean[]"); + env_->SetDoubleArrayRegion(reinterpret_cast<jdoubleArray>(array), 0, kLength, + reinterpret_cast<jdouble*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type boolean[] expected double[]" + : "attempt to set region of double primitive array elements with an object of type boolean[]"); + jbyteArray array2 = env_->NewByteArray(10); + env_->SetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(array2), 0, kLength, + reinterpret_cast<jboolean*>(elements)); + jni_abort_catcher.Check( + check_jni ? "incompatible array type byte[] expected boolean[]" + : "attempt to set region of boolean primitive array elements with an object of type byte[]"); + jobject object = env_->NewStringUTF("Test String"); + env_->SetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(object), 0, kLength, + reinterpret_cast<jboolean*>(elements)); + jni_abort_catcher.Check(check_jni ? "jarray argument has non-array type: java.lang.String" + : "attempt to set region of boolean primitive array elements with an object of type " + "java.lang.String"); + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + + void NewObjectArrayBadArguments(bool check_jni) { + bool old_check_jni = vm_->SetCheckJniEnabled(check_jni); + CheckJniAbortCatcher jni_abort_catcher; + + jclass element_class = env_->FindClass("java/lang/String"); + ASSERT_NE(element_class, nullptr); + + env_->NewObjectArray(-1, element_class, nullptr); + jni_abort_catcher.Check(check_jni ? "negative jsize: -1" : "negative array length: -1"); + + env_->NewObjectArray(std::numeric_limits<jint>::min(), element_class, nullptr); + jni_abort_catcher.Check(check_jni ? "negative jsize: -2147483648" + : "negative array length: -2147483648"); + + EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); + } + JavaVMExt* vm_; JNIEnv* env_; jclass aioobe_; @@ -125,48 +621,8 @@ TEST_F(JniInternalTest, GetVersion) { } TEST_F(JniInternalTest, FindClass) { - // Reference types... - ExpectClassFound("java/lang/String"); - // ...for arrays too, where you must include "L;". - ExpectClassFound("[Ljava/lang/String;"); - // Primitive arrays are okay too, if the primitive type is valid. - ExpectClassFound("[C"); - - { - CheckJniAbortCatcher check_jni_abort_catcher; - env_->FindClass(nullptr); - check_jni_abort_catcher.Check("name == null"); - - // We support . as well as / for compatibility, if -Xcheck:jni is off. - ExpectClassFound("java.lang.String"); - check_jni_abort_catcher.Check("illegal class name 'java.lang.String'"); - ExpectClassNotFound("Ljava.lang.String;"); - check_jni_abort_catcher.Check("illegal class name 'Ljava.lang.String;'"); - ExpectClassFound("[Ljava.lang.String;"); - check_jni_abort_catcher.Check("illegal class name '[Ljava.lang.String;'"); - ExpectClassNotFound("[java.lang.String"); - check_jni_abort_catcher.Check("illegal class name '[java.lang.String'"); - - // You can't include the "L;" in a JNI class descriptor. - ExpectClassNotFound("Ljava/lang/String;"); - check_jni_abort_catcher.Check("illegal class name 'Ljava/lang/String;'"); - - // But you must include it for an array of any reference type. - ExpectClassNotFound("[java/lang/String"); - check_jni_abort_catcher.Check("illegal class name '[java/lang/String'"); - - ExpectClassNotFound("[K"); - check_jni_abort_catcher.Check("illegal class name '[K'"); - - // Void arrays aren't allowed. - ExpectClassNotFound("[V"); - check_jni_abort_catcher.Check("illegal class name '[V'"); - } - - // But primitive types aren't allowed... - ExpectClassNotFound("C"); - ExpectClassNotFound("V"); - ExpectClassNotFound("K"); + FindClassTest(false); + FindClassTest(true); } TEST_F(JniInternalTest, GetFieldID) { @@ -208,16 +664,8 @@ TEST_F(JniInternalTest, GetFieldID) { ExpectException(jlnsfe); // Bad arguments. - CheckJniAbortCatcher check_jni_abort_catcher; - fid = env_->GetFieldID(nullptr, "count", "I"); - EXPECT_EQ(nullptr, fid); - check_jni_abort_catcher.Check("java_class == null"); - fid = env_->GetFieldID(c, nullptr, "I"); - EXPECT_EQ(nullptr, fid); - check_jni_abort_catcher.Check("name == null"); - fid = env_->GetFieldID(c, "count", nullptr); - EXPECT_EQ(nullptr, fid); - check_jni_abort_catcher.Check("sig == null"); + GetFieldIdBadArgumentTest(false); + GetFieldIdBadArgumentTest(true); } TEST_F(JniInternalTest, GetStaticFieldID) { @@ -253,16 +701,8 @@ TEST_F(JniInternalTest, GetStaticFieldID) { ExpectException(jlnsfe); // Bad arguments. - CheckJniAbortCatcher check_jni_abort_catcher; - fid = env_->GetStaticFieldID(nullptr, "CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;"); - EXPECT_EQ(nullptr, fid); - check_jni_abort_catcher.Check("java_class == null"); - fid = env_->GetStaticFieldID(c, nullptr, "Ljava/util/Comparator;"); - EXPECT_EQ(nullptr, fid); - check_jni_abort_catcher.Check("name == null"); - fid = env_->GetStaticFieldID(c, "CASE_INSENSITIVE_ORDER", nullptr); - EXPECT_EQ(nullptr, fid); - check_jni_abort_catcher.Check("sig == null"); + GetStaticFieldIdBadArgumentTest(false); + GetStaticFieldIdBadArgumentTest(true); } TEST_F(JniInternalTest, GetMethodID) { @@ -302,16 +742,8 @@ TEST_F(JniInternalTest, GetMethodID) { EXPECT_FALSE(env_->ExceptionCheck()); // Bad arguments. - CheckJniAbortCatcher check_jni_abort_catcher; - method = env_->GetMethodID(nullptr, "<init>", "(Ljava/lang/String;)V"); - EXPECT_EQ(nullptr, method); - check_jni_abort_catcher.Check("java_class == null"); - method = env_->GetMethodID(jlnsme, nullptr, "(Ljava/lang/String;)V"); - EXPECT_EQ(nullptr, method); - check_jni_abort_catcher.Check("name == null"); - method = env_->GetMethodID(jlnsme, "<init>", nullptr); - EXPECT_EQ(nullptr, method); - check_jni_abort_catcher.Check("sig == null"); + GetMethodIdBadArgumentTest(false); + GetMethodIdBadArgumentTest(true); } TEST_F(JniInternalTest, GetStaticMethodID) { @@ -340,16 +772,8 @@ TEST_F(JniInternalTest, GetStaticMethodID) { EXPECT_FALSE(env_->ExceptionCheck()); // Bad arguments. - CheckJniAbortCatcher check_jni_abort_catcher; - method = env_->GetStaticMethodID(nullptr, "valueOf", "(I)Ljava/lang/String;"); - EXPECT_EQ(nullptr, method); - check_jni_abort_catcher.Check("java_class == null"); - method = env_->GetStaticMethodID(jlstring, nullptr, "(I)Ljava/lang/String;"); - EXPECT_EQ(nullptr, method); - check_jni_abort_catcher.Check("name == null"); - method = env_->GetStaticMethodID(jlstring, "valueOf", nullptr); - EXPECT_EQ(nullptr, method); - check_jni_abort_catcher.Check("sig == null"); + GetStaticMethodIdBadArgumentTest(false); + GetStaticMethodIdBadArgumentTest(true); } TEST_F(JniInternalTest, FromReflectedField_ToReflectedField) { @@ -370,13 +794,8 @@ TEST_F(JniInternalTest, FromReflectedField_ToReflectedField) { ASSERT_EQ(4, env_->GetIntField(s, fid2)); // Bad arguments. - CheckJniAbortCatcher check_jni_abort_catcher; - field = env_->ToReflectedField(c, nullptr, JNI_FALSE); - EXPECT_EQ(field, nullptr); - check_jni_abort_catcher.Check("fid == null"); - fid2 = env_->FromReflectedField(nullptr); - ASSERT_EQ(fid2, nullptr); - check_jni_abort_catcher.Check("jlr_field == null"); + GetFromReflectedField_ToReflectedFieldBadArgumentTest(false); + GetFromReflectedField_ToReflectedFieldBadArgumentTest(true); } TEST_F(JniInternalTest, FromReflectedMethod_ToReflectedMethod) { @@ -417,13 +836,8 @@ TEST_F(JniInternalTest, FromReflectedMethod_ToReflectedMethod) { ASSERT_EQ(4, env_->CallIntMethod(s, mid2)); // Bad arguments. - CheckJniAbortCatcher check_jni_abort_catcher; - method = env_->ToReflectedMethod(c, nullptr, JNI_FALSE); - EXPECT_EQ(method, nullptr); - check_jni_abort_catcher.Check("mid == null"); - mid2 = env_->FromReflectedMethod(method); - ASSERT_EQ(mid2, nullptr); - check_jni_abort_catcher.Check("jlr_method == null"); + GetFromReflectedMethod_ToReflectedMethodBadArgumentTest(false); + GetFromReflectedMethod_ToReflectedMethodBadArgumentTest(true); } static void BogusMethod() { @@ -498,23 +912,11 @@ TEST_F(JniInternalTest, RegisterAndUnregisterNatives) { } EXPECT_FALSE(env_->ExceptionCheck()); - // Passing a class of null is a failure. - { - JNINativeMethod methods[] = { }; - EXPECT_EQ(env_->RegisterNatives(nullptr, methods, 0), JNI_ERR); - check_jni_abort_catcher.Check("java_class == null"); - } - - // Passing methods as null is a failure. - EXPECT_EQ(env_->RegisterNatives(jlobject, nullptr, 1), JNI_ERR); - check_jni_abort_catcher.Check("methods == null"); - - // Unregisters null is a failure. - EXPECT_EQ(env_->UnregisterNatives(nullptr), JNI_ERR); - check_jni_abort_catcher.Check("java_class == null"); - // Unregistering a class with no natives is a warning. EXPECT_EQ(env_->UnregisterNatives(jlnsme), JNI_OK); + + RegisterAndUnregisterNativesBadArguments(false, &check_jni_abort_catcher); + RegisterAndUnregisterNativesBadArguments(true, &check_jni_abort_catcher); } #define EXPECT_PRIMITIVE_ARRAY(new_fn, \ @@ -528,6 +930,7 @@ TEST_F(JniInternalTest, RegisterAndUnregisterNatives) { \ { \ CheckJniAbortCatcher jni_abort_catcher; \ + down_cast<JNIEnvExt*>(env_)->SetCheckJniEnabled(false); \ /* Allocate an negative sized array and check it has the right failure type. */ \ EXPECT_EQ(env_->new_fn(-1), nullptr); \ jni_abort_catcher.Check("negative array length: -1"); \ @@ -550,6 +953,7 @@ TEST_F(JniInternalTest, RegisterAndUnregisterNatives) { jni_abort_catcher.Check("buf == null"); \ env_->set_region_fn(a, 0, size, nullptr); \ jni_abort_catcher.Check("buf == null"); \ + down_cast<JNIEnvExt*>(env_)->SetCheckJniEnabled(true); \ } \ /* Allocate an array and check it has the right type and length. */ \ scalar_type ## Array a = env_->new_fn(size); \ @@ -654,189 +1058,28 @@ TEST_F(JniInternalTest, ShortArrays) { } TEST_F(JniInternalTest, GetPrimitiveArrayElementsOfWrongType) { - CheckJniAbortCatcher jni_abort_catcher; - jbooleanArray array = env_->NewBooleanArray(10); - jboolean is_copy; - EXPECT_EQ(env_->GetByteArrayElements(reinterpret_cast<jbyteArray>(array), &is_copy), nullptr); - jni_abort_catcher.Check( - "attempt to get byte primitive array elements with an object of type boolean[]"); - EXPECT_EQ(env_->GetShortArrayElements(reinterpret_cast<jshortArray>(array), &is_copy), nullptr); - jni_abort_catcher.Check( - "attempt to get short primitive array elements with an object of type boolean[]"); - EXPECT_EQ(env_->GetCharArrayElements(reinterpret_cast<jcharArray>(array), &is_copy), nullptr); - jni_abort_catcher.Check( - "attempt to get char primitive array elements with an object of type boolean[]"); - EXPECT_EQ(env_->GetIntArrayElements(reinterpret_cast<jintArray>(array), &is_copy), nullptr); - jni_abort_catcher.Check( - "attempt to get int primitive array elements with an object of type boolean[]"); - EXPECT_EQ(env_->GetLongArrayElements(reinterpret_cast<jlongArray>(array), &is_copy), nullptr); - jni_abort_catcher.Check( - "attempt to get long primitive array elements with an object of type boolean[]"); - EXPECT_EQ(env_->GetFloatArrayElements(reinterpret_cast<jfloatArray>(array), &is_copy), nullptr); - jni_abort_catcher.Check( - "attempt to get float primitive array elements with an object of type boolean[]"); - EXPECT_EQ(env_->GetDoubleArrayElements(reinterpret_cast<jdoubleArray>(array), &is_copy), nullptr); - jni_abort_catcher.Check( - "attempt to get double primitive array elements with an object of type boolean[]"); - jbyteArray array2 = env_->NewByteArray(10); - EXPECT_EQ(env_->GetBooleanArrayElements(reinterpret_cast<jbooleanArray>(array2), &is_copy), - nullptr); - jni_abort_catcher.Check( - "attempt to get boolean primitive array elements with an object of type byte[]"); - jobject object = env_->NewStringUTF("Test String"); - EXPECT_EQ(env_->GetBooleanArrayElements(reinterpret_cast<jbooleanArray>(object), &is_copy), - nullptr); - jni_abort_catcher.Check( - "attempt to get boolean primitive array elements with an object of type java.lang.String"); + GetPrimitiveArrayElementsOfWrongType(false); + GetPrimitiveArrayElementsOfWrongType(true); } TEST_F(JniInternalTest, ReleasePrimitiveArrayElementsOfWrongType) { - CheckJniAbortCatcher jni_abort_catcher; - jbooleanArray array = env_->NewBooleanArray(10); - ASSERT_TRUE(array != nullptr); - jboolean is_copy; - jboolean* elements = env_->GetBooleanArrayElements(array, &is_copy); - ASSERT_TRUE(elements != nullptr); - env_->ReleaseByteArrayElements(reinterpret_cast<jbyteArray>(array), - reinterpret_cast<jbyte*>(elements), 0); - jni_abort_catcher.Check( - "attempt to release byte primitive array elements with an object of type boolean[]"); - env_->ReleaseShortArrayElements(reinterpret_cast<jshortArray>(array), - reinterpret_cast<jshort*>(elements), 0); - jni_abort_catcher.Check( - "attempt to release short primitive array elements with an object of type boolean[]"); - env_->ReleaseCharArrayElements(reinterpret_cast<jcharArray>(array), - reinterpret_cast<jchar*>(elements), 0); - jni_abort_catcher.Check( - "attempt to release char primitive array elements with an object of type boolean[]"); - env_->ReleaseIntArrayElements(reinterpret_cast<jintArray>(array), - reinterpret_cast<jint*>(elements), 0); - jni_abort_catcher.Check( - "attempt to release int primitive array elements with an object of type boolean[]"); - env_->ReleaseLongArrayElements(reinterpret_cast<jlongArray>(array), - reinterpret_cast<jlong*>(elements), 0); - jni_abort_catcher.Check( - "attempt to release long primitive array elements with an object of type boolean[]"); - env_->ReleaseFloatArrayElements(reinterpret_cast<jfloatArray>(array), - reinterpret_cast<jfloat*>(elements), 0); - jni_abort_catcher.Check( - "attempt to release float primitive array elements with an object of type boolean[]"); - env_->ReleaseDoubleArrayElements(reinterpret_cast<jdoubleArray>(array), - reinterpret_cast<jdouble*>(elements), 0); - jni_abort_catcher.Check( - "attempt to release double primitive array elements with an object of type boolean[]"); - jbyteArray array2 = env_->NewByteArray(10); - env_->ReleaseBooleanArrayElements(reinterpret_cast<jbooleanArray>(array2), elements, 0); - jni_abort_catcher.Check( - "attempt to release boolean primitive array elements with an object of type byte[]"); - jobject object = env_->NewStringUTF("Test String"); - env_->ReleaseBooleanArrayElements(reinterpret_cast<jbooleanArray>(object), elements, 0); - jni_abort_catcher.Check( - "attempt to release boolean primitive array elements with an object of type " - "java.lang.String"); + ReleasePrimitiveArrayElementsOfWrongType(false); + ReleasePrimitiveArrayElementsOfWrongType(true); } + TEST_F(JniInternalTest, GetReleasePrimitiveArrayCriticalOfWrongType) { - CheckJniAbortCatcher jni_abort_catcher; - jobject object = env_->NewStringUTF("Test String"); - jboolean is_copy; - void* elements = env_->GetPrimitiveArrayCritical(reinterpret_cast<jarray>(object), &is_copy); - jni_abort_catcher.Check("expected primitive array, given java.lang.String"); - env_->ReleasePrimitiveArrayCritical(reinterpret_cast<jarray>(object), elements, 0); - jni_abort_catcher.Check("expected primitive array, given java.lang.String"); + GetReleasePrimitiveArrayCriticalOfWrongType(false); + GetReleasePrimitiveArrayCriticalOfWrongType(true); } TEST_F(JniInternalTest, GetPrimitiveArrayRegionElementsOfWrongType) { - CheckJniAbortCatcher jni_abort_catcher; - constexpr size_t kLength = 10; - jbooleanArray array = env_->NewBooleanArray(kLength); - ASSERT_TRUE(array != nullptr); - jboolean elements[kLength]; - env_->GetByteArrayRegion(reinterpret_cast<jbyteArray>(array), 0, kLength, - reinterpret_cast<jbyte*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of byte primitive array elements with an object of type boolean[]"); - env_->GetShortArrayRegion(reinterpret_cast<jshortArray>(array), 0, kLength, - reinterpret_cast<jshort*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of short primitive array elements with an object of type boolean[]"); - env_->GetCharArrayRegion(reinterpret_cast<jcharArray>(array), 0, kLength, - reinterpret_cast<jchar*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of char primitive array elements with an object of type boolean[]"); - env_->GetIntArrayRegion(reinterpret_cast<jintArray>(array), 0, kLength, - reinterpret_cast<jint*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of int primitive array elements with an object of type boolean[]"); - env_->GetLongArrayRegion(reinterpret_cast<jlongArray>(array), 0, kLength, - reinterpret_cast<jlong*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of long primitive array elements with an object of type boolean[]"); - env_->GetFloatArrayRegion(reinterpret_cast<jfloatArray>(array), 0, kLength, - reinterpret_cast<jfloat*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of float primitive array elements with an object of type boolean[]"); - env_->GetDoubleArrayRegion(reinterpret_cast<jdoubleArray>(array), 0, kLength, - reinterpret_cast<jdouble*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of double primitive array elements with an object of type boolean[]"); - jbyteArray array2 = env_->NewByteArray(10); - env_->GetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(array2), 0, kLength, - reinterpret_cast<jboolean*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of boolean primitive array elements with an object of type byte[]"); - jobject object = env_->NewStringUTF("Test String"); - env_->GetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(object), 0, kLength, - reinterpret_cast<jboolean*>(elements)); - jni_abort_catcher.Check( - "attempt to get region of boolean primitive array elements with an object of type " - "java.lang.String"); + GetPrimitiveArrayRegionElementsOfWrongType(false); + GetPrimitiveArrayRegionElementsOfWrongType(true); } TEST_F(JniInternalTest, SetPrimitiveArrayRegionElementsOfWrongType) { - CheckJniAbortCatcher jni_abort_catcher; - constexpr size_t kLength = 10; - jbooleanArray array = env_->NewBooleanArray(kLength); - ASSERT_TRUE(array != nullptr); - jboolean elements[kLength]; - env_->SetByteArrayRegion(reinterpret_cast<jbyteArray>(array), 0, kLength, - reinterpret_cast<jbyte*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of byte primitive array elements with an object of type boolean[]"); - env_->SetShortArrayRegion(reinterpret_cast<jshortArray>(array), 0, kLength, - reinterpret_cast<jshort*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of short primitive array elements with an object of type boolean[]"); - env_->SetCharArrayRegion(reinterpret_cast<jcharArray>(array), 0, kLength, - reinterpret_cast<jchar*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of char primitive array elements with an object of type boolean[]"); - env_->SetIntArrayRegion(reinterpret_cast<jintArray>(array), 0, kLength, - reinterpret_cast<jint*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of int primitive array elements with an object of type boolean[]"); - env_->SetLongArrayRegion(reinterpret_cast<jlongArray>(array), 0, kLength, - reinterpret_cast<jlong*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of long primitive array elements with an object of type boolean[]"); - env_->SetFloatArrayRegion(reinterpret_cast<jfloatArray>(array), 0, kLength, - reinterpret_cast<jfloat*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of float primitive array elements with an object of type boolean[]"); - env_->SetDoubleArrayRegion(reinterpret_cast<jdoubleArray>(array), 0, kLength, - reinterpret_cast<jdouble*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of double primitive array elements with an object of type boolean[]"); - jbyteArray array2 = env_->NewByteArray(10); - env_->SetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(array2), 0, kLength, - reinterpret_cast<jboolean*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of boolean primitive array elements with an object of type byte[]"); - jobject object = env_->NewStringUTF("Test String"); - env_->SetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(object), 0, kLength, - reinterpret_cast<jboolean*>(elements)); - jni_abort_catcher.Check( - "attempt to set region of boolean primitive array elements with an object of type " - "java.lang.String"); + SetPrimitiveArrayRegionElementsOfWrongType(false); + SetPrimitiveArrayRegionElementsOfWrongType(true); } TEST_F(JniInternalTest, NewObjectArray) { @@ -857,12 +1100,8 @@ TEST_F(JniInternalTest, NewObjectArray) { EXPECT_TRUE(env_->IsSameObject(env_->GetObjectArrayElement(a, 0), nullptr)); // Negative array length checks. - CheckJniAbortCatcher jni_abort_catcher; - env_->NewObjectArray(-1, element_class, nullptr); - jni_abort_catcher.Check("negative array length: -1"); - - env_->NewObjectArray(std::numeric_limits<jint>::min(), element_class, nullptr); - jni_abort_catcher.Check("negative array length: -2147483648"); + NewObjectArrayBadArguments(false); + NewObjectArrayBadArguments(true); } TEST_F(JniInternalTest, NewObjectArrayWithPrimitiveClasses) { @@ -872,6 +1111,7 @@ TEST_F(JniInternalTest, NewObjectArrayWithPrimitiveClasses) { }; ASSERT_EQ(strlen(primitive_descriptors), arraysize(primitive_names)); + bool old_check_jni = vm_->SetCheckJniEnabled(false); CheckJniAbortCatcher jni_abort_catcher; for (size_t i = 0; i < strlen(primitive_descriptors); ++i) { env_->NewObjectArray(0, nullptr, nullptr); @@ -881,6 +1121,16 @@ TEST_F(JniInternalTest, NewObjectArrayWithPrimitiveClasses) { std::string error_msg(StringPrintf("not an object type: %s", primitive_names[i])); jni_abort_catcher.Check(error_msg.c_str()); } + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); + for (size_t i = 0; i < strlen(primitive_descriptors); ++i) { + env_->NewObjectArray(0, nullptr, nullptr); + jni_abort_catcher.Check("NewObjectArray received NULL jclass"); + jclass primitive_class = GetPrimitiveClass(primitive_descriptors[i]); + env_->NewObjectArray(1, primitive_class, nullptr); + std::string error_msg(StringPrintf("not an object type: %s", primitive_names[i])); + jni_abort_catcher.Check(error_msg.c_str()); + } + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } TEST_F(JniInternalTest, NewObjectArrayWithInitialValue) { @@ -940,8 +1190,13 @@ TEST_F(JniInternalTest, GetSuperclass) { // Null as class should fail. CheckJniAbortCatcher jni_abort_catcher; + bool old_check_jni = vm_->SetCheckJniEnabled(false); EXPECT_EQ(env_->GetSuperclass(nullptr), nullptr); jni_abort_catcher.Check("java_class == null"); + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); + EXPECT_EQ(env_->GetSuperclass(nullptr), nullptr); + jni_abort_catcher.Check("GetSuperclass received NULL jclass"); + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } TEST_F(JniInternalTest, IsAssignableFrom) { @@ -975,10 +1230,17 @@ TEST_F(JniInternalTest, IsAssignableFrom) { // Null as either class should fail. CheckJniAbortCatcher jni_abort_catcher; + bool old_check_jni = vm_->SetCheckJniEnabled(false); EXPECT_EQ(env_->IsAssignableFrom(nullptr, string_class), JNI_FALSE); jni_abort_catcher.Check("java_class1 == null"); EXPECT_EQ(env_->IsAssignableFrom(object_class, nullptr), JNI_FALSE); jni_abort_catcher.Check("java_class2 == null"); + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); + EXPECT_EQ(env_->IsAssignableFrom(nullptr, string_class), JNI_FALSE); + jni_abort_catcher.Check("IsAssignableFrom received NULL jclass"); + EXPECT_EQ(env_->IsAssignableFrom(object_class, nullptr), JNI_FALSE); + jni_abort_catcher.Check("IsAssignableFrom received NULL jclass"); + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } TEST_F(JniInternalTest, GetObjectRefType) { @@ -1063,10 +1325,17 @@ TEST_F(JniInternalTest, NewStringNullCharsNonzeroLength) { TEST_F(JniInternalTest, NewStringNegativeLength) { CheckJniAbortCatcher jni_abort_catcher; + bool old_check_jni = vm_->SetCheckJniEnabled(false); env_->NewString(nullptr, -1); jni_abort_catcher.Check("char_count < 0: -1"); env_->NewString(nullptr, std::numeric_limits<jint>::min()); jni_abort_catcher.Check("char_count < 0: -2147483648"); + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); + env_->NewString(nullptr, -1); + jni_abort_catcher.Check("negative jsize: -1"); + env_->NewString(nullptr, std::numeric_limits<jint>::min()); + jni_abort_catcher.Check("negative jsize: -2147483648"); + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } TEST_F(JniInternalTest, GetStringLength_GetStringUTFLength) { @@ -1124,10 +1393,17 @@ TEST_F(JniInternalTest, GetStringRegion_GetStringUTFRegion) { TEST_F(JniInternalTest, GetStringUTFChars_ReleaseStringUTFChars) { // Passing in a nullptr jstring is ignored normally, but caught by -Xcheck:jni. + bool old_check_jni = vm_->SetCheckJniEnabled(false); { CheckJniAbortCatcher check_jni_abort_catcher; EXPECT_EQ(env_->GetStringUTFChars(nullptr, nullptr), nullptr); - check_jni_abort_catcher.Check("GetStringUTFChars received null jstring"); + } + { + CheckJniAbortCatcher check_jni_abort_catcher; + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); + EXPECT_EQ(env_->GetStringUTFChars(nullptr, nullptr), nullptr); + check_jni_abort_catcher.Check("GetStringUTFChars received NULL jstring"); + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } jstring s = env_->NewStringUTF("hello"); @@ -1222,10 +1498,17 @@ TEST_F(JniInternalTest, GetObjectArrayElement_SetObjectArrayElement) { // Null as array should fail. CheckJniAbortCatcher jni_abort_catcher; + bool old_check_jni = vm_->SetCheckJniEnabled(false); EXPECT_EQ(nullptr, env_->GetObjectArrayElement(nullptr, 0)); jni_abort_catcher.Check("java_array == null"); env_->SetObjectArrayElement(nullptr, 0, nullptr); jni_abort_catcher.Check("java_array == null"); + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); + EXPECT_EQ(nullptr, env_->GetObjectArrayElement(nullptr, 0)); + jni_abort_catcher.Check("jarray was NULL"); + env_->SetObjectArrayElement(nullptr, 0, nullptr); + jni_abort_catcher.Check("jarray was NULL"); + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } #define EXPECT_STATIC_PRIMITIVE_FIELD(type, field_name, sig, value1, value2) \ @@ -1237,15 +1520,28 @@ TEST_F(JniInternalTest, GetObjectArrayElement_SetObjectArrayElement) { env_->SetStatic ## type ## Field(c, fid, value2); \ EXPECT_EQ(value2, env_->GetStatic ## type ## Field(c, fid)); \ \ + bool old_check_jni = vm_->SetCheckJniEnabled(false); \ + { \ + CheckJniAbortCatcher jni_abort_catcher; \ + env_->GetStatic ## type ## Field(nullptr, fid); \ + env_->SetStatic ## type ## Field(nullptr, fid, value1); \ + } \ CheckJniAbortCatcher jni_abort_catcher; \ - env_->GetStatic ## type ## Field(nullptr, fid); \ - jni_abort_catcher.Check("received null jclass"); \ - env_->SetStatic ## type ## Field(nullptr, fid, value1); \ - jni_abort_catcher.Check("received null jclass"); \ env_->GetStatic ## type ## Field(c, nullptr); \ jni_abort_catcher.Check("fid == null"); \ env_->SetStatic ## type ## Field(c, nullptr, value1); \ jni_abort_catcher.Check("fid == null"); \ + \ + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); \ + env_->GetStatic ## type ## Field(nullptr, fid); \ + jni_abort_catcher.Check("received NULL jclass"); \ + env_->SetStatic ## type ## Field(nullptr, fid, value1); \ + jni_abort_catcher.Check("received NULL jclass"); \ + env_->GetStatic ## type ## Field(c, nullptr); \ + jni_abort_catcher.Check("jfieldID was NULL"); \ + env_->SetStatic ## type ## Field(c, nullptr, value1); \ + jni_abort_catcher.Check("jfieldID was NULL"); \ + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); \ } while (false) #define EXPECT_PRIMITIVE_FIELD(instance, type, field_name, sig, value1, value2) \ @@ -1257,6 +1553,7 @@ TEST_F(JniInternalTest, GetObjectArrayElement_SetObjectArrayElement) { env_->Set ## type ## Field(instance, fid, value2); \ EXPECT_EQ(value2, env_->Get ## type ## Field(instance, fid)); \ \ + bool old_check_jni = vm_->SetCheckJniEnabled(false); \ CheckJniAbortCatcher jni_abort_catcher; \ env_->Get ## type ## Field(nullptr, fid); \ jni_abort_catcher.Check("obj == null"); \ @@ -1266,6 +1563,16 @@ TEST_F(JniInternalTest, GetObjectArrayElement_SetObjectArrayElement) { jni_abort_catcher.Check("fid == null"); \ env_->Set ## type ## Field(instance, nullptr, value1); \ jni_abort_catcher.Check("fid == null"); \ + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); \ + env_->Get ## type ## Field(nullptr, fid); \ + jni_abort_catcher.Check("field operation on NULL object:"); \ + env_->Set ## type ## Field(nullptr, fid, value1); \ + jni_abort_catcher.Check("field operation on NULL object:"); \ + env_->Get ## type ## Field(instance, nullptr); \ + jni_abort_catcher.Check("jfieldID was NULL"); \ + env_->Set ## type ## Field(instance, nullptr, value1); \ + jni_abort_catcher.Check("jfieldID was NULL"); \ + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); \ } while (false) @@ -1357,12 +1664,17 @@ TEST_F(JniInternalTest, DeleteLocalRef) { // Currently, deleting an already-deleted reference is just a CheckJNI warning. { + bool old_check_jni = vm_->SetCheckJniEnabled(false); + { + CheckJniAbortCatcher check_jni_abort_catcher; + env_->DeleteLocalRef(s); + } CheckJniAbortCatcher check_jni_abort_catcher; + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); env_->DeleteLocalRef(s); - - std::string expected(StringPrintf("native code passing in reference to " - "invalid 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)); } s = env_->NewStringUTF(""); @@ -1453,12 +1765,17 @@ TEST_F(JniInternalTest, DeleteGlobalRef) { // Currently, deleting an already-deleted reference is just a CheckJNI warning. { + bool old_check_jni = vm_->SetCheckJniEnabled(false); + { + CheckJniAbortCatcher check_jni_abort_catcher; + env_->DeleteGlobalRef(o); + } CheckJniAbortCatcher check_jni_abort_catcher; + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); env_->DeleteGlobalRef(o); - - std::string expected(StringPrintf("native code passing in reference to " - "invalid 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)); } jobject o1 = env_->NewGlobalRef(s); @@ -1498,12 +1815,17 @@ TEST_F(JniInternalTest, DeleteWeakGlobalRef) { // Currently, deleting an already-deleted reference is just a CheckJNI warning. { + bool old_check_jni = vm_->SetCheckJniEnabled(false); + { + CheckJniAbortCatcher check_jni_abort_catcher; + env_->DeleteWeakGlobalRef(o); + } CheckJniAbortCatcher check_jni_abort_catcher; + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); env_->DeleteWeakGlobalRef(o); - - std::string expected(StringPrintf("native code passing in reference to " - "invalid 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)); } jobject o1 = env_->NewWeakGlobalRef(s); @@ -1522,8 +1844,6 @@ TEST_F(JniInternalTest, ExceptionDescribe) { } TEST_F(JniInternalTest, Throw) { - EXPECT_EQ(JNI_ERR, env_->Throw(nullptr)); - jclass exception_class = env_->FindClass("java/lang/RuntimeException"); ASSERT_TRUE(exception_class != nullptr); jthrowable exception = reinterpret_cast<jthrowable>(env_->AllocObject(exception_class)); @@ -1534,11 +1854,18 @@ TEST_F(JniInternalTest, Throw) { jthrowable thrown_exception = env_->ExceptionOccurred(); env_->ExceptionClear(); EXPECT_TRUE(env_->IsSameObject(exception, thrown_exception)); -} -TEST_F(JniInternalTest, ThrowNew) { + // Bad argument. + bool old_check_jni = vm_->SetCheckJniEnabled(false); EXPECT_EQ(JNI_ERR, env_->Throw(nullptr)); + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); + CheckJniAbortCatcher check_jni_abort_catcher; + EXPECT_EQ(JNI_ERR, env_->Throw(nullptr)); + check_jni_abort_catcher.Check("Throw received NULL jthrowable"); + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); +} +TEST_F(JniInternalTest, ThrowNew) { jclass exception_class = env_->FindClass("java/lang/RuntimeException"); ASSERT_TRUE(exception_class != nullptr); @@ -1555,6 +1882,16 @@ TEST_F(JniInternalTest, ThrowNew) { thrown_exception = env_->ExceptionOccurred(); env_->ExceptionClear(); EXPECT_TRUE(env_->IsInstanceOf(thrown_exception, exception_class)); + + // Bad argument. + bool old_check_jni = vm_->SetCheckJniEnabled(false); + CheckJniAbortCatcher check_jni_abort_catcher; + EXPECT_EQ(JNI_ERR, env_->ThrowNew(nullptr, nullptr)); + check_jni_abort_catcher.Check("c == null"); + EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); + EXPECT_EQ(JNI_ERR, env_->ThrowNew(nullptr, nullptr)); + check_jni_abort_catcher.Check("ThrowNew received NULL jclass"); + EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); } TEST_F(JniInternalTest, NewDirectBuffer_GetDirectBufferAddress_GetDirectBufferCapacity) { diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 1e254fad61..519685a92b 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -449,8 +449,14 @@ class MANAGED Class FINAL : public Object { bool IsObjectClass() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { return !IsPrimitive() && GetSuperClass() == NULL; } + + bool IsInstantiableNonArray() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + return !IsPrimitive() && !IsInterface() && !IsAbstract() && !IsArrayClass(); + } + bool IsInstantiable() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - return (!IsPrimitive() && !IsInterface() && !IsAbstract()) || ((IsAbstract()) && IsArrayClass()); + return (!IsPrimitive() && !IsInterface() && !IsAbstract()) || + ((IsAbstract()) && IsArrayClass()); } template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> diff --git a/runtime/mirror/object_test.cc b/runtime/mirror/object_test.cc index a7ea6c9c08..da3c36cb06 100644 --- a/runtime/mirror/object_test.cc +++ b/runtime/mirror/object_test.cc @@ -73,7 +73,12 @@ class ObjectTest : public CommonRuntimeTest { } }; -// Keep the assembly code in sync +// Keep constants in sync. +TEST_F(ObjectTest, Constants) { + EXPECT_EQ(kObjectReferenceSize, sizeof(mirror::HeapReference<mirror::Object>)); +} + +// Keep the assembly code constats in sync. TEST_F(ObjectTest, AsmConstants) { EXPECT_EQ(CLASS_OFFSET, Object::ClassOffset().Int32Value()); EXPECT_EQ(LOCK_WORD_OFFSET, Object::MonitorOffset().Int32Value()); diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc index b0b64aac16..a9ef8fc32e 100644 --- a/runtime/native/dalvik_system_VMRuntime.cc +++ b/runtime/native/dalvik_system_VMRuntime.cc @@ -167,7 +167,7 @@ static jboolean VMRuntime_is64Bit(JNIEnv* env, jobject) { } static jboolean VMRuntime_isCheckJniEnabled(JNIEnv* env, jobject) { - return Runtime::Current()->GetJavaVM()->check_jni ? JNI_TRUE : JNI_FALSE; + return Runtime::Current()->GetJavaVM()->IsCheckJniEnabled() ? JNI_TRUE : JNI_FALSE; } static void VMRuntime_setTargetSdkVersionNative(JNIEnv*, jobject, jint target_sdk_version) { diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index 820bd0420f..df6055dac3 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -17,6 +17,7 @@ #include <stdlib.h> #include "debugger.h" +#include "java_vm_ext.h" #include "jni_internal.h" #include "JNIHelp.h" #include "thread-inl.h" @@ -47,7 +48,7 @@ static void EnableDebugger() { } static void EnableDebugFeatures(uint32_t debug_flags) { - // Must match values in dalvik.system.Zygote. + // Must match values in com.android.internal.os.Zygote. enum { DEBUG_ENABLE_DEBUGGER = 1, DEBUG_ENABLE_CHECKJNI = 1 << 1, @@ -59,7 +60,7 @@ static void EnableDebugFeatures(uint32_t debug_flags) { if ((debug_flags & DEBUG_ENABLE_CHECKJNI) != 0) { Runtime* runtime = Runtime::Current(); JavaVMExt* vm = runtime->GetJavaVM(); - if (!vm->check_jni) { + if (!vm->IsCheckJniEnabled()) { LOG(INFO) << "Late-enabling -Xcheck:jni"; vm->SetCheckJniEnabled(true); // There's only one thread running at this point, so only one JNIEnv to fix up. diff --git a/runtime/native/java_lang_DexCache.cc b/runtime/native/java_lang_DexCache.cc index 51cd5b80d5..c1c6c26047 100644 --- a/runtime/native/java_lang_DexCache.cc +++ b/runtime/native/java_lang_DexCache.cc @@ -15,6 +15,7 @@ */ #include "dex_file.h" +#include "jni_internal.h" #include "mirror/dex_cache.h" #include "mirror/object-inl.h" #include "scoped_fast_native_object_access.h" diff --git a/runtime/native/java_lang_Runtime.cc b/runtime/native/java_lang_Runtime.cc index fb708a24df..a85eec7464 100644 --- a/runtime/native/java_lang_Runtime.cc +++ b/runtime/native/java_lang_Runtime.cc @@ -43,7 +43,10 @@ static void Runtime_nativeExit(JNIEnv*, jclass, jint status) { exit(status); } -static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPath) { +static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, + jstring javaLdLibraryPath) { + // TODO: returns NULL on success or an error message describing the failure on failure. This + // should be refactored in terms of suppressed exceptions. ScopedUtfChars filename(env, javaFilename); if (filename.c_str() == NULL) { return NULL; @@ -64,14 +67,10 @@ static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, job } } - std::string detail; + std::string error_msg; { - ScopedObjectAccess soa(env); - StackHandleScope<1> hs(soa.Self()); - Handle<mirror::ClassLoader> classLoader( - hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader))); JavaVMExt* vm = Runtime::Current()->GetJavaVM(); - bool success = vm->LoadNativeLibrary(filename.c_str(), classLoader, &detail); + bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg); if (success) { return nullptr; } @@ -79,7 +78,7 @@ static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, job // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF. env->ExceptionClear(); - return env->NewStringUTF(detail.c_str()); + return env->NewStringUTF(error_msg.c_str()); } static jlong Runtime_maxMemory(JNIEnv*, jclass) { diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc index 163ae20628..8b2aecbbb1 100644 --- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc +++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc @@ -16,6 +16,7 @@ #include "base/logging.h" #include "debugger.h" +#include "jni_internal.h" #include "scoped_fast_native_object_access.h" #include "ScopedPrimitiveArray.h" diff --git a/runtime/native_bridge.cc b/runtime/native_bridge.cc index ad26ee4e9b..18532e2e33 100644 --- a/runtime/native_bridge.cc +++ b/runtime/native_bridge.cc @@ -53,6 +53,17 @@ static constexpr const char* kPropEnableNativeBridge = "persist.enable.native.br // The symbol name exposed by native-bridge with the type of NativeBridgeCallbacks. static constexpr const char* kNativeBridgeInterfaceSymbol = "NativeBridgeItf"; +// The library name we are supposed to load. +static std::string native_bridge_library_string = ""; + +// Whether a native bridge is available (loaded and ready). +static bool available = false; +// Whether we have already initialized (or tried to). +static bool initialized = false; + +struct NativeBridgeCallbacks; +static NativeBridgeCallbacks* callbacks = nullptr; + // ART interfaces to native-bridge. struct NativeBridgeArtCallbacks { // Get shorty of a Java method. The shorty is supposed to be persistent in memory. @@ -71,7 +82,7 @@ struct NativeBridgeArtCallbacks { // clazz [IN] Java class object. // Returns: // number of native methods. - int (*getNativeMethodCount)(JNIEnv* env, jclass clazz); + uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz); // Get at most 'method_count' native methods for specified class 'clazz'. Results are outputed // via 'methods' [OUT]. The signature pointer in JNINativeMethod is reused as the method shorty. @@ -83,7 +94,8 @@ struct NativeBridgeArtCallbacks { // method_count [IN] max number of elements in methods. // Returns: // number of method it actually wrote to methods. - int (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods, uint32_t method_count); + uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods, + uint32_t method_count); }; // Native-bridge interfaces to ART @@ -135,52 +147,62 @@ static const char* GetMethodShorty(JNIEnv* env, jmethodID mid) { return mh.GetShorty(); } -static int GetNativeMethodCount(JNIEnv* env, jclass clazz) { +static uint32_t GetNativeMethodCount(JNIEnv* env, jclass clazz) { if (clazz == nullptr) return 0; ScopedObjectAccess soa(env); mirror::Class* c = soa.Decode<mirror::Class*>(clazz); - size_t method_count = 0; - for (size_t i = 0; i < c->NumDirectMethods(); ++i) { + uint32_t native_method_count = 0; + for (uint32_t i = 0; i < c->NumDirectMethods(); ++i) { mirror::ArtMethod* m = c->GetDirectMethod(i); - if (m->IsNative()) - method_count++; + if (m->IsNative()) { + native_method_count++; + } } - for (size_t i = 0; i < c->NumVirtualMethods(); ++i) { + for (uint32_t i = 0; i < c->NumVirtualMethods(); ++i) { mirror::ArtMethod* m = c->GetVirtualMethod(i); - if (m->IsNative()) - method_count++; + if (m->IsNative()) { + native_method_count++; + } } - return method_count; + return native_method_count; } -static int GetNativeMethods(JNIEnv* env, jclass clazz, JNINativeMethod* methods, - uint32_t method_count) { - if ((clazz == nullptr) || (methods == nullptr)) +static uint32_t GetNativeMethods(JNIEnv* env, jclass clazz, JNINativeMethod* methods, + uint32_t method_count) { + if ((clazz == nullptr) || (methods == nullptr)) { return 0; - + } ScopedObjectAccess soa(env); mirror::Class* c = soa.Decode<mirror::Class*>(clazz); - size_t count = 0; - for (size_t i = 0; i < c->NumDirectMethods(); ++i) { + uint32_t count = 0; + for (uint32_t i = 0; i < c->NumDirectMethods(); ++i) { mirror::ArtMethod* m = c->GetDirectMethod(i); - if (m->IsNative() && count < method_count) { - methods[count].name = m->GetName(); - methods[count].signature = m->GetShorty(); - methods[count].fnPtr = const_cast<void*>(m->GetNativeMethod()); - count++; + if (m->IsNative()) { + if (count < method_count) { + methods[count].name = m->GetName(); + methods[count].signature = m->GetShorty(); + methods[count].fnPtr = const_cast<void*>(m->GetNativeMethod()); + count++; + } else { + LOG(WARNING) << "Output native method array too small. Skipping " << PrettyMethod(m); + } } } - for (size_t i = 0; i < c->NumVirtualMethods(); ++i) { + for (uint32_t i = 0; i < c->NumVirtualMethods(); ++i) { mirror::ArtMethod* m = c->GetVirtualMethod(i); - if (m->IsNative() && count < method_count) { - methods[count].name = m->GetName(); - methods[count].signature = m->GetShorty(); - methods[count].fnPtr = const_cast<void*>(m->GetNativeMethod()); - count++; + if (m->IsNative()) { + if (count < method_count) { + methods[count].name = m->GetName(); + methods[count].signature = m->GetShorty(); + methods[count].fnPtr = const_cast<void*>(m->GetNativeMethod()); + count++; + } else { + LOG(WARNING) << "Output native method array too small. Skipping " << PrettyMethod(m); + } } } return count; @@ -192,30 +214,32 @@ NativeBridgeArtCallbacks NativeBridgeArtItf = { GetNativeMethods }; -void NativeBridge::SetNativeBridgeLibraryString(std::string& native_bridge_library_string) { - native_bridge_library_string_ = native_bridge_library_string; +void SetNativeBridgeLibraryString(const std::string& nb_library_string) { + native_bridge_library_string = nb_library_string; // TODO: when given an empty string, set initialized_ to true and available_ to false. This // change is dependent on the property removal in Initialize(). } -bool NativeBridge::Initialize() { +bool NativeBridgeInitialize() { if (!kNativeBridgeEnabled) { return false; } + // TODO: Missing annotalysis static lock ordering of DEFAULT_MUTEX_ACQUIRED, place lock into + // global order or remove. + static Mutex lock("native bridge lock"); + MutexLock mu(Thread::Current(), lock); - MutexLock mu(Thread::Current(), lock_); - - if (initialized_) { + if (initialized) { // Somebody did it before. - return available_; + return available; } - available_ = false; + available = false; const char* libnb_path; - if (!native_bridge_library_string_.empty()) { - libnb_path = native_bridge_library_string_.c_str(); + if (!native_bridge_library_string.empty()) { + libnb_path = native_bridge_library_string.c_str(); } else { // TODO: Remove this once the frameworks side is completely implemented. @@ -224,7 +248,7 @@ bool NativeBridge::Initialize() { char prop_buf[PROP_VALUE_MAX]; property_get(kPropEnableNativeBridge, prop_buf, "false"); if (strcmp(prop_buf, "true") != 0) { - initialized_ = true; + initialized = true; return false; } @@ -237,46 +261,43 @@ bool NativeBridge::Initialize() { void* handle = dlopen(libnb_path, RTLD_LAZY); if (handle != nullptr) { - callbacks_ = reinterpret_cast<NativeBridgeCallbacks*>(dlsym(handle, - kNativeBridgeInterfaceSymbol)); + callbacks = reinterpret_cast<NativeBridgeCallbacks*>(dlsym(handle, + kNativeBridgeInterfaceSymbol)); - if (callbacks_ != nullptr) { - available_ = callbacks_->initialize(&NativeBridgeArtItf); + if (callbacks != nullptr) { + available = callbacks->initialize(&NativeBridgeArtItf); } - if (!available_) { + if (!available) { dlclose(handle); } } - initialized_ = true; + initialized = true; - return available_; + return available; } -void* NativeBridge::LoadLibrary(const char* libpath, int flag) { - if (Initialize()) - return callbacks_->loadLibrary(libpath, flag); +void* NativeBridgeLoadLibrary(const char* libpath, int flag) { + if (NativeBridgeInitialize()) { + return callbacks->loadLibrary(libpath, flag); + } return nullptr; } -void* NativeBridge::GetTrampoline(void* handle, const char* name, const char* shorty, +void* NativeBridgeGetTrampoline(void* handle, const char* name, const char* shorty, uint32_t len) { - if (Initialize()) - return callbacks_->getTrampoline(handle, name, shorty, len); + if (NativeBridgeInitialize()) { + return callbacks->getTrampoline(handle, name, shorty, len); + } return nullptr; } -bool NativeBridge::IsSupported(const char* libpath) { - if (Initialize()) - return callbacks_->isSupported(libpath); +bool NativeBridgeIsSupported(const char* libpath) { + if (NativeBridgeInitialize()) { + return callbacks->isSupported(libpath); + } return false; } -bool NativeBridge::available_ = false; -bool NativeBridge::initialized_ = false; -Mutex NativeBridge::lock_("native bridge lock"); -std::string NativeBridge::native_bridge_library_string_ = ""; -NativeBridgeCallbacks* NativeBridge::callbacks_ = nullptr; - }; // namespace art diff --git a/runtime/native_bridge.h b/runtime/native_bridge.h index 3d20fe43f8..be647fc1eb 100644 --- a/runtime/native_bridge.h +++ b/runtime/native_bridge.h @@ -17,42 +17,22 @@ #ifndef ART_RUNTIME_NATIVE_BRIDGE_H_ #define ART_RUNTIME_NATIVE_BRIDGE_H_ -#include "base/mutex.h" - #include <string> namespace art { -struct NativeBridgeCallbacks; - -class NativeBridge { - public: - // Initialize the native bridge, if any. Should be called by Runtime::Init(). An empty string - // signals that we do not want to load a native bridge. - static void SetNativeBridgeLibraryString(std::string& native_bridge_library_string); - - // Load a shared library that is supported by the native-bridge. - static void* LoadLibrary(const char* libpath, int flag); - // Get a native-bridge trampoline for specified native method. - static void* GetTrampoline(void* handle, const char* name, const char* shorty, uint32_t len); - // True if native library is valid and is for an ABI that is supported by native-bridge. - static bool IsSupported(const char* libpath); - - private: - static bool Initialize(); - - // The library name we are supposed to load. - static std::string native_bridge_library_string_; +// Initialize the native bridge, if any. Should be called by Runtime::Init(). An empty string +// signals that we do not want to load a native bridge. +void SetNativeBridgeLibraryString(const std::string& native_bridge_library_string); - // Whether we have already initialized (or tried to). - static bool initialized_ GUARDED_BY(lock_); - static Mutex lock_; +// Load a shared library that is supported by the native-bridge. +void* NativeBridgeLoadLibrary(const char* libpath, int flag); - // Whether a native bridge is available (loaded and ready). - static bool available_; +// Get a native-bridge trampoline for specified native method. +void* NativeBridgeGetTrampoline(void* handle, const char* name, const char* shorty, uint32_t len); - static NativeBridgeCallbacks* callbacks_; -}; +// True if native library is valid and is for an ABI that is supported by native-bridge. +bool NativeBridgeIsSupported(const char* libpath); }; // namespace art diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 49f658507d..12f9f33f5f 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -177,6 +177,7 @@ bool ParsedOptions::Parse(const RuntimeOptions& options, bool ignore_unrecognize } // -Xcheck:jni is off by default for regular builds but on by default in debug builds. check_jni_ = kIsDebugBuild; + force_copy_ = false; heap_initial_size_ = gc::Heap::kDefaultInitialSize; heap_maximum_size_ = gc::Heap::kDefaultMaximumSize; @@ -300,6 +301,8 @@ bool ParsedOptions::Parse(const RuntimeOptions& options, bool ignore_unrecognize } } else if (StartsWith(option, "-Xcheck:jni")) { check_jni_ = true; + } else if (StartsWith(option, "-Xjniopts:forcecopy")) { + force_copy_ = true; } else if (StartsWith(option, "-Xrunjdwp:") || StartsWith(option, "-agentlib:jdwp=")) { std::string tail(option.substr(option[1] == 'X' ? 10 : 15)); // TODO: move parsing logic out of Dbg @@ -613,7 +616,6 @@ bool ParsedOptions::Parse(const RuntimeOptions& options, bool ignore_unrecognize StartsWith(option, "-Xint:") || StartsWith(option, "-Xdexopt:") || (option == "-Xnoquithandler") || - StartsWith(option, "-Xjniopts:") || StartsWith(option, "-Xjnigreflimit:") || (option == "-Xgenregmap") || (option == "-Xnogenregmap") || diff --git a/runtime/parsed_options.h b/runtime/parsed_options.h index 3dbe26f302..c328ca7ef5 100644 --- a/runtime/parsed_options.h +++ b/runtime/parsed_options.h @@ -44,6 +44,7 @@ class ParsedOptions { std::string class_path_string_; std::string image_; bool check_jni_; + bool force_copy_; std::string jni_trace_; std::string native_bridge_library_string_; CompilerCallbacks* compiler_callbacks_; diff --git a/runtime/primitive.cc b/runtime/primitive.cc index 16ca0fe1f6..a639f93f45 100644 --- a/runtime/primitive.cc +++ b/runtime/primitive.cc @@ -30,6 +30,7 @@ static const char* kTypeNames[] = { "PrimDouble", "PrimVoid", }; + std::ostream& operator<<(std::ostream& os, const Primitive::Type& type) { int32_t int_type = static_cast<int32_t>(type); if (type >= Primitive::kPrimNot && type <= Primitive::kPrimVoid) { diff --git a/runtime/primitive.h b/runtime/primitive.h index b436bd2165..a36e9cb31b 100644 --- a/runtime/primitive.h +++ b/runtime/primitive.h @@ -21,12 +21,10 @@ #include "base/logging.h" #include "base/macros.h" -#include "mirror/object_reference.h" namespace art { -namespace mirror { -class Object; -} // namespace mirror + +static constexpr size_t kObjectReferenceSize = 4; class Primitive { public: @@ -79,7 +77,7 @@ class Primitive { case kPrimFloat: return 4; case kPrimLong: case kPrimDouble: return 8; - case kPrimNot: return sizeof(mirror::HeapReference<mirror::Object>); + case kPrimNot: return kObjectReferenceSize; default: LOG(FATAL) << "Invalid type " << static_cast<int>(type); return 0; diff --git a/runtime/reflection.cc b/runtime/reflection.cc index 758c1bbd1f..0169cccbf0 100644 --- a/runtime/reflection.cc +++ b/runtime/reflection.cc @@ -347,7 +347,7 @@ class ArgArray { std::unique_ptr<uint32_t[]> large_arg_array_; }; -static void CheckMethodArguments(mirror::ArtMethod* m, uint32_t* args) +static void CheckMethodArguments(JavaVMExt* vm, mirror::ArtMethod* m, uint32_t* args) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { const DexFile::TypeList* params = m->GetParameterTypeList(); if (params == nullptr) { @@ -375,11 +375,11 @@ static void CheckMethodArguments(mirror::ArtMethod* m, uint32_t* args) self->ClearException(); ++error_count; } else if (!param_type->IsPrimitive()) { - // TODO: check primitives are in range. // TODO: There is a compaction bug here since GetClassFromTypeIdx can cause thread suspension, // this is a hard to fix problem since the args can contain Object*, we need to save and // restore them by using a visitor similar to the ones used in the trampoline entrypoints. - mirror::Object* argument = reinterpret_cast<mirror::Object*>(args[i + offset]); + mirror::Object* argument = + (reinterpret_cast<StackReference<mirror::Object>*>(&args[i + offset]))->AsMirrorPtr(); if (argument != nullptr && !argument->InstanceOf(param_type)) { LOG(ERROR) << "JNI ERROR (app bug): attempt to pass an instance of " << PrettyTypeOf(argument) << " as argument " << (i + 1) @@ -388,13 +388,40 @@ static void CheckMethodArguments(mirror::ArtMethod* m, uint32_t* args) } } else if (param_type->IsPrimitiveLong() || param_type->IsPrimitiveDouble()) { offset++; + } else { + int32_t arg = static_cast<int32_t>(args[i + offset]); + if (param_type->IsPrimitiveBoolean()) { + if (arg != JNI_TRUE && arg != JNI_FALSE) { + LOG(ERROR) << "JNI ERROR (app bug): expected jboolean (0/1) but got value of " + << arg << " as argument " << (i + 1) << " to " << PrettyMethod(h_m.Get()); + ++error_count; + } + } else if (param_type->IsPrimitiveByte()) { + if (arg < -128 || arg > 127) { + LOG(ERROR) << "JNI ERROR (app bug): expected jbyte but got value of " + << arg << " as argument " << (i + 1) << " to " << PrettyMethod(h_m.Get()); + ++error_count; + } + } else if (param_type->IsPrimitiveChar()) { + if (args[i + offset] > 0xFFFF) { + LOG(ERROR) << "JNI ERROR (app bug): expected jchar but got value of " + << arg << " as argument " << (i + 1) << " to " << PrettyMethod(h_m.Get()); + ++error_count; + } + } else if (param_type->IsPrimitiveShort()) { + if (arg < -32768 || arg > 0x7FFF) { + LOG(ERROR) << "JNI ERROR (app bug): expected jshort but got value of " + << arg << " as argument " << (i + 1) << " to " << PrettyMethod(h_m.Get()); + ++error_count; + } + } } } - if (error_count > 0) { + if (UNLIKELY(error_count > 0)) { // TODO: pass the JNI function name (such as "CallVoidMethodV") through so we can call JniAbort // with an argument. - JniAbortF(nullptr, "bad arguments passed to %s (see above for details)", - PrettyMethod(h_m.Get()).c_str()); + vm->JniAbortF(nullptr, "bad arguments passed to %s (see above for details)", + PrettyMethod(h_m.Get()).c_str()); } } @@ -411,7 +438,7 @@ static void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa, SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { uint32_t* args = arg_array->GetArray(); if (UNLIKELY(soa.Env()->check_jni)) { - CheckMethodArguments(method, args); + CheckMethodArguments(soa.Vm(), method, args); } method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty); } @@ -567,11 +594,6 @@ bool VerifyObjectIsClass(mirror::Object* o, mirror::Class* c) { return true; } -static std::string PrettyDescriptor(Primitive::Type type) { - std::string descriptor_string(Primitive::Descriptor(type)); - return PrettyDescriptor(descriptor_string); -} - bool ConvertPrimitiveValue(const ThrowLocation* throw_location, bool unbox_for_result, Primitive::Type srcType, Primitive::Type dstType, const JValue& src, JValue* dst) { diff --git a/runtime/reflection.h b/runtime/reflection.h index 2c54c067fd..61370c650e 100644 --- a/runtime/reflection.h +++ b/runtime/reflection.h @@ -17,6 +17,7 @@ #ifndef ART_RUNTIME_REFLECTION_H_ #define ART_RUNTIME_REFLECTION_H_ +#include "base/mutex.h" #include "jni.h" #include "primitive.h" diff --git a/runtime/runtime.cc b/runtime/runtime.cc index e0c0d63efe..d677729b7e 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -339,7 +339,7 @@ jobject CreateSystemClassLoader() { ScopedObjectAccess soa(Thread::Current()); ClassLinker* cl = Runtime::Current()->GetClassLinker(); - StackHandleScope<3> hs(soa.Self()); + StackHandleScope<2> hs(soa.Self()); Handle<mirror::Class> class_loader_class( hs.NewHandle(soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_ClassLoader))); CHECK(cl->EnsureInitialized(class_loader_class, true, true)); @@ -349,15 +349,12 @@ jobject CreateSystemClassLoader() { CHECK(getSystemClassLoader != NULL); JValue result = InvokeWithJValues(soa, nullptr, soa.EncodeMethod(getSystemClassLoader), nullptr); - Handle<mirror::ClassLoader> class_loader( - hs.NewHandle(down_cast<mirror::ClassLoader*>(result.GetL()))); - CHECK(class_loader.Get() != nullptr); JNIEnv* env = soa.Self()->GetJniEnv(); ScopedLocalRef<jobject> system_class_loader(env, - soa.AddLocalReference<jobject>(class_loader.Get())); + soa.AddLocalReference<jobject>(result.GetL())); CHECK(system_class_loader.get() != nullptr); - soa.Self()->SetClassLoaderOverride(class_loader.Get()); + soa.Self()->SetClassLoaderOverride(system_class_loader.get()); Handle<mirror::Class> thread_class( hs.NewHandle(soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_Thread))); @@ -368,7 +365,8 @@ jobject CreateSystemClassLoader() { CHECK(contextClassLoader != NULL); // We can't run in a transaction yet. - contextClassLoader->SetObject<false>(soa.Self()->GetPeer(), class_loader.Get()); + contextClassLoader->SetObject<false>(soa.Self()->GetPeer(), + soa.Decode<mirror::ClassLoader*>(system_class_loader.get())); return env->NewGlobalRef(system_class_loader.get()); } @@ -708,7 +706,7 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) self->ClearException(); // Look for a native bridge. - NativeBridge::SetNativeBridgeLibraryString(options->native_bridge_library_string_); + SetNativeBridgeLibraryString(options->native_bridge_library_string_); VLOG(startup) << "Runtime::Init exiting"; return true; @@ -736,13 +734,9 @@ void Runtime::InitNativeMethods() { { std::string mapped_name(StringPrintf(OS_SHARED_LIB_FORMAT_STR, "javacore")); std::string reason; - self->TransitionFromSuspendedToRunnable(); - StackHandleScope<1> hs(self); - auto class_loader(hs.NewHandle<mirror::ClassLoader>(nullptr)); - if (!instance_->java_vm_->LoadNativeLibrary(mapped_name, class_loader, &reason)) { + if (!instance_->java_vm_->LoadNativeLibrary(env, mapped_name, nullptr, &reason)) { LOG(FATAL) << "LoadNativeLibrary failed for \"" << mapped_name << "\": " << reason; } - self->TransitionFromRunnableToSuspended(kNative); } // Initialize well known classes that may invoke runtime native methods. diff --git a/runtime/scoped_thread_state_change.h b/runtime/scoped_thread_state_change.h index 23aca45002..ae3eaf2e22 100644 --- a/runtime/scoped_thread_state_change.h +++ b/runtime/scoped_thread_state_change.h @@ -18,7 +18,8 @@ #define ART_RUNTIME_SCOPED_THREAD_STATE_CHANGE_H_ #include "base/casts.h" -#include "jni_internal-inl.h" +#include "java_vm_ext.h" +#include "jni_env_ext-inl.h" #include "read_barrier.h" #include "thread-inl.h" #include "verify_object.h" @@ -114,6 +115,10 @@ class ScopedObjectAccessAlreadyRunnable { return vm_; } + bool ForceCopy() const { + return vm_->ForceCopy(); + } + /* * Add a local reference for an object to the indirect reference table associated with the * current stack frame. When the native function returns, the reference will be discarded. diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h index a5caa075ee..bd399e7734 100644 --- a/runtime/thread-inl.h +++ b/runtime/thread-inl.h @@ -24,7 +24,7 @@ #include "base/casts.h" #include "base/mutex-inl.h" #include "gc/heap.h" -#include "jni_internal.h" +#include "jni_env_ext.h" namespace art { diff --git a/runtime/thread.cc b/runtime/thread.cc index 18e28ea11c..8e6da74e5e 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -1163,6 +1163,21 @@ void Thread::Destroy() { Thread* self = this; DCHECK_EQ(self, Thread::Current()); + if (tlsPtr_.jni_env != nullptr) { + // On thread detach, all monitors entered with JNI MonitorEnter are automatically exited. + tlsPtr_.jni_env->monitors.VisitRoots(MonitorExitVisitor, self, 0, kRootVMInternal); + // Release locally held global references which releasing may require the mutator lock. + if (tlsPtr_.jpeer != nullptr) { + // If pthread_create fails we don't have a jni env here. + tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.jpeer); + tlsPtr_.jpeer = nullptr; + } + if (tlsPtr_.class_loader_override != nullptr) { + tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.class_loader_override); + tlsPtr_.class_loader_override = nullptr; + } + } + if (tlsPtr_.opeer != nullptr) { ScopedObjectAccess soa(self); // We may need to call user-supplied managed code, do this before final clean-up. @@ -1190,22 +1205,16 @@ void Thread::Destroy() { ObjectLock<mirror::Object> locker(self, h_obj); locker.NotifyAll(); } + tlsPtr_.opeer = nullptr; } - // On thread detach, all monitors entered with JNI MonitorEnter are automatically exited. - if (tlsPtr_.jni_env != nullptr) { - tlsPtr_.jni_env->monitors.VisitRoots(MonitorExitVisitor, self, 0, kRootVMInternal); - } + Runtime::Current()->GetHeap()->RevokeThreadLocalBuffers(this); } Thread::~Thread() { - if (tlsPtr_.jni_env != nullptr && tlsPtr_.jpeer != nullptr) { - // If pthread_create fails we don't have a jni env here. - tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.jpeer); - tlsPtr_.jpeer = nullptr; - } - tlsPtr_.opeer = nullptr; - + CHECK(tlsPtr_.class_loader_override == nullptr); + CHECK(tlsPtr_.jpeer == nullptr); + CHECK(tlsPtr_.opeer == nullptr); bool initialized = (tlsPtr_.jni_env != nullptr); // Did Thread::Init run? if (initialized) { delete tlsPtr_.jni_env; @@ -1237,7 +1246,7 @@ Thread::~Thread() { delete tlsPtr_.name; delete tlsPtr_.stack_trace_sample; - Runtime::Current()->GetHeap()->RevokeThreadLocalBuffers(this); + Runtime::Current()->GetHeap()->AssertThreadLocalBuffersAreRevoked(this); TearDownAlternateSignalStack(); } @@ -1347,11 +1356,10 @@ mirror::Object* Thread::DecodeJObject(jobject obj) const { result = kInvalidIndirectRefObject; } } else if (kind == kGlobal) { - JavaVMExt* const vm = Runtime::Current()->GetJavaVM(); - result = vm->globals.SynchronizedGet(const_cast<Thread*>(this), &vm->globals_lock, ref); + result = tlsPtr_.jni_env->vm->DecodeGlobal(const_cast<Thread*>(this), ref); } else { DCHECK_EQ(kind, kWeakGlobal); - result = Runtime::Current()->GetJavaVM()->DecodeWeakGlobal(const_cast<Thread*>(this), ref); + result = tlsPtr_.jni_env->vm->DecodeWeakGlobal(const_cast<Thread*>(this), ref); if (result == kClearedJniWeakGlobal) { // This is a special case where it's okay to return nullptr. return nullptr; @@ -1359,7 +1367,8 @@ mirror::Object* Thread::DecodeJObject(jobject obj) const { } if (UNLIKELY(result == nullptr)) { - JniAbortF(nullptr, "use of deleted %s %p", ToStr<IndirectRefKind>(kind).c_str(), obj); + tlsPtr_.jni_env->vm->JniAbortF(nullptr, "use of deleted %s %p", + ToStr<IndirectRefKind>(kind).c_str(), obj); } return result; } @@ -1399,6 +1408,13 @@ void Thread::NotifyLocked(Thread* self) { } } +void Thread::SetClassLoaderOverride(jobject class_loader_override) { + if (tlsPtr_.class_loader_override != nullptr) { + GetJniEnv()->DeleteGlobalRef(tlsPtr_.class_loader_override); + } + tlsPtr_.class_loader_override = GetJniEnv()->NewGlobalRef(class_loader_override); +} + class CountStackDepthVisitor : public StackVisitor { public: explicit CountStackDepthVisitor(Thread* thread) @@ -2173,11 +2189,6 @@ class RootCallbackVisitor { const uint32_t tid_; }; -void Thread::SetClassLoaderOverride(mirror::ClassLoader* class_loader_override) { - VerifyObject(class_loader_override); - tlsPtr_.class_loader_override = class_loader_override; -} - void Thread::VisitRoots(RootCallback* visitor, void* arg) { uint32_t thread_id = GetThreadId(); if (tlsPtr_.opeer != nullptr) { @@ -2187,10 +2198,6 @@ void Thread::VisitRoots(RootCallback* visitor, void* arg) { visitor(reinterpret_cast<mirror::Object**>(&tlsPtr_.exception), arg, thread_id, kRootNativeStack); } tlsPtr_.throw_location.VisitRoots(visitor, arg); - if (tlsPtr_.class_loader_override != nullptr) { - visitor(reinterpret_cast<mirror::Object**>(&tlsPtr_.class_loader_override), arg, thread_id, - kRootNativeStack); - } if (tlsPtr_.monitor_enter_object != nullptr) { visitor(&tlsPtr_.monitor_enter_object, arg, thread_id, kRootNativeStack); } diff --git a/runtime/thread.h b/runtime/thread.h index 3e9372fe1a..c2b200bf1a 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -431,12 +431,11 @@ class Thread { tlsPtr_.wait_next = next; } - mirror::ClassLoader* GetClassLoaderOverride() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + jobject GetClassLoaderOverride() { return tlsPtr_.class_loader_override; } - void SetClassLoaderOverride(mirror::ClassLoader* class_loader_override) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void SetClassLoaderOverride(jobject class_loader_override); // Create the internal representation of a stack trace, that is more time // and space efficient to compute than the StackTraceElement[]. @@ -1040,7 +1039,7 @@ class Thread { // Needed to get the right ClassLoader in JNI_OnLoad, but also // useful for testing. - mirror::ClassLoader* class_loader_override; + jobject class_loader_override; // Thread local, lazily allocated, long jump context. Used to deliver exceptions. Context* long_jump_context; diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index 7cf26e664e..5077a89ee9 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -801,6 +801,8 @@ void ThreadList::Register(Thread* self) { void ThreadList::Unregister(Thread* self) { DCHECK_EQ(self, Thread::Current()); + CHECK_NE(self->GetState(), kRunnable); + Locks::mutator_lock_->AssertNotHeld(self); VLOG(threads) << "ThreadList::Unregister() " << *self; @@ -815,14 +817,18 @@ void ThreadList::Unregister(Thread* self) { // Note: deliberately not using MutexLock that could hold a stale self pointer. Locks::thread_list_lock_->ExclusiveLock(self); CHECK(Contains(self)); - // Note: we don't take the thread_suspend_count_lock_ here as to be suspending a thread other - // than yourself you need to hold the thread_list_lock_ (see Thread::ModifySuspendCount). + Locks::thread_suspend_count_lock_->ExclusiveLock(self); + bool removed = false; if (!self->IsSuspended()) { list_.remove(self); + removed = true; + } + Locks::thread_suspend_count_lock_->ExclusiveUnlock(self); + Locks::thread_list_lock_->ExclusiveUnlock(self); + if (removed) { delete self; self = nullptr; } - Locks::thread_list_lock_->ExclusiveUnlock(self); } // Release the thread ID after the thread is finished and deleted to avoid cases where we can // temporarily have multiple threads with the same thread id. When this occurs, it causes diff --git a/runtime/utils.cc b/runtime/utils.cc index f966fbd7ec..48d6cdf263 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -281,6 +281,11 @@ std::string PrettyDescriptor(const std::string& descriptor) { return result; } +std::string PrettyDescriptor(Primitive::Type type) { + std::string descriptor_string(Primitive::Descriptor(type)); + return PrettyDescriptor(descriptor_string); +} + std::string PrettyField(mirror::ArtField* f, bool with_type) { if (f == NULL) { return "null"; diff --git a/runtime/utils.h b/runtime/utils.h index 49bcbf9ea7..f6773be289 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -24,13 +24,10 @@ #include <vector> #include "base/logging.h" +#include "base/mutex.h" #include "globals.h" #include "instruction_set.h" -#include "base/mutex.h" - -#ifdef HAVE_ANDROID_OS -#include "cutils/properties.h" -#endif +#include "primitive.h" namespace art { @@ -279,6 +276,7 @@ std::string PrettyDescriptor(mirror::String* descriptor) std::string PrettyDescriptor(const std::string& descriptor); std::string PrettyDescriptor(mirror::Class* klass) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); +std::string PrettyDescriptor(Primitive::Type type); // Returns a human-readable signature for 'f'. Something like "a.b.C.f" or // "int a.b.C.f" (depending on the value of 'with_type'). diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h index d508fb5ffc..1682d4e5d0 100644 --- a/runtime/verifier/reg_type.h +++ b/runtime/verifier/reg_type.h @@ -25,6 +25,7 @@ #include "jni.h" #include "base/macros.h" +#include "base/mutex.h" #include "gc_root.h" #include "globals.h" #include "object_callbacks.h" diff --git a/test/115-native-bridge/nativebridge.cc b/test/115-native-bridge/nativebridge.cc index 82211a5579..268f0bebce 100644 --- a/test/115-native-bridge/nativebridge.cc +++ b/test/115-native-bridge/nativebridge.cc @@ -32,9 +32,9 @@ struct NativeBridgeArtCallbacks { const char* (*getMethodShorty)(JNIEnv* env, jmethodID mid); - int (*getNativeMethodCount)(JNIEnv* env, jclass clazz); - int (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods, - uint32_t method_count); + uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz); + uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods, + uint32_t method_count); }; struct NativeBridgeCallbacks { |