Have JNI FindClass fall back to system ClassLoader
Bug: 10994325

Change-Id: Id0a46e78eecfe8a9eb91008765c4fff48697cc58
diff --git a/build/Android.libarttest.mk b/build/Android.libarttest.mk
index f7b4d1e..f946d91 100644
--- a/build/Android.libarttest.mk
+++ b/build/Android.libarttest.mk
@@ -15,6 +15,7 @@
 #
 
 LIBARTTEST_COMMON_SRC_FILES := \
+	test/JniTest/jni_test.cc \
 	test/ReferenceMap/stack_walk_refmap_jni.cc \
 	test/StackWalk/stack_walk_jni.cc
 
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 0a00284..6a0990e 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -255,11 +255,28 @@
 static ClassLoader* GetClassLoader(const ScopedObjectAccess& soa)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   ArtMethod* method = soa.Self()->GetCurrentMethod(NULL);
-  if (method == NULL ||
-      method == soa.DecodeMethod(WellKnownClasses::java_lang_Runtime_nativeLoad)) {
+  // 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 method->GetDeclaringClass()->GetClassLoader();
+  // If we have a method, use its ClassLoader for context.
+  if (method != NULL) {
+    return method->GetDeclaringClass()->GetClassLoader();
+  }
+  // We don't have a method, so try to use the system ClassLoader.
+  ClassLoader* class_loader = soa.Decode<ClassLoader*>(Runtime::Current()->GetSystemClassLoader());
+  if (class_loader != NULL) {
+    return class_loader;
+  }
+  // See if the override ClassLoader is set for gtests.
+  class_loader = soa.Self()->GetClassLoaderOverride();
+  if (class_loader != NULL) {
+    // If so, CommonTest should have set UseCompileTimeClassPath.
+    CHECK(Runtime::Current()->UseCompileTimeClassPath());
+    return class_loader;
+  }
+  // Use the BOOTCLASSPATH.
+  return NULL;
 }
 
 static jfieldID FindFieldID(const ScopedObjectAccess& soa, jclass jni_class, const char* name,
@@ -2101,13 +2118,14 @@
     descriptor += ClassHelper(element_class).GetDescriptor();
 
     // Find the class.
-    ScopedLocalRef<jclass> java_array_class(env, FindClass(env, descriptor.c_str()));
-    if (java_array_class.get() == NULL) {
+    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    Class* array_class = class_linker->FindClass(descriptor.c_str(),
+                                                 element_class->GetClassLoader());
+    if (array_class == NULL) {
       return NULL;
     }
 
     // Allocate and initialize if necessary.
-    Class* array_class = soa.Decode<Class*>(java_array_class.get());
     ObjectArray<Object>* result = ObjectArray<Object>::Alloc(soa.Self(), array_class, length);
     if (initial_element != NULL) {
       Object* initial_object = soa.Decode<Object*>(initial_element);
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 09d1447..b9f54e5 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -83,7 +83,6 @@
       java_vm_(NULL),
       pre_allocated_OutOfMemoryError_(NULL),
       resolution_method_(NULL),
-      system_class_loader_(NULL),
       threads_being_born_(0),
       shutdown_cond_(new ConditionVariable("Runtime shutdown", *Locks::runtime_shutdown_lock_)),
       shutting_down_(false),
@@ -99,7 +98,8 @@
       instrumentation_(),
       use_compile_time_class_path_(false),
       main_thread_group_(NULL),
-      system_thread_group_(NULL) {
+      system_thread_group_(NULL),
+      system_class_loader_(NULL) {
   for (int i = 0; i < Runtime::kLastCalleeSaveType; i++) {
     callee_save_methods_[i] = NULL;
   }
@@ -666,9 +666,9 @@
   return true;
 }
 
-static void CreateSystemClassLoader() {
+jobject CreateSystemClassLoader() {
   if (Runtime::Current()->UseCompileTimeClassPath()) {
-    return;
+    return NULL;
   }
 
   ScopedObjectAccess soa(Thread::Current());
@@ -687,6 +687,10 @@
   mirror::ClassLoader* class_loader = down_cast<mirror::ClassLoader*>(result.GetL());
   CHECK(class_loader != NULL);
 
+  JNIEnv* env = soa.Self()->GetJniEnv();
+  ScopedLocalRef<jobject> system_class_loader(env, soa.AddLocalReference<jobject>(class_loader));
+  CHECK(system_class_loader.get() != NULL);
+
   soa.Self()->SetClassLoaderOverride(class_loader);
 
   mirror::Class* thread_class = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_Thread);
@@ -697,6 +701,8 @@
   CHECK(contextClassLoader != NULL);
 
   contextClassLoader->SetObject(soa.Self()->GetPeer(), class_loader);
+
+  return env->NewGlobalRef(system_class_loader.get());
 }
 
 bool Runtime::Start() {
@@ -729,7 +735,7 @@
 
   StartDaemonThreads();
 
-  CreateSystemClassLoader();
+  system_class_loader_ = CreateSystemClassLoader();
 
   self->GetJniEnv()->locals.AssertEmpty();
 
@@ -991,6 +997,11 @@
   return system_thread_group_;
 }
 
+jobject Runtime::GetSystemClassLoader() const {
+  CHECK(system_class_loader_ != NULL || IsCompiler());
+  return system_class_loader_;
+}
+
 void Runtime::RegisterRuntimeNativeMethods(JNIEnv* env) {
 #define REGISTER(FN) extern void FN(JNIEnv*); FN(env)
   // Register Throwable first so that registration of other native methods can throw exceptions
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 365d2d8..bc5c8b0 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -240,6 +240,9 @@
   // Returns the "system" ThreadGroup, used when attaching our internal threads.
   jobject GetSystemThreadGroup() const;
 
+  // Returns the system ClassLoader which represents the CLASSPATH.
+  jobject GetSystemClassLoader() const;
+
   // Attaches the calling native thread to the runtime.
   bool AttachCurrentThread(const char* thread_name, bool as_daemon, jobject thread_group,
                            bool create_peer);
@@ -467,9 +470,6 @@
 
   mirror::ArtMethod* resolution_method_;
 
-  // As returned by ClassLoader.getSystemClassLoader()
-  mirror::ClassLoader* system_class_loader_;
-
   // A non-zero value indicates that a thread has been created but not yet initialized. Guarded by
   // the shutdown lock so that threads aren't born while we're shutting down.
   size_t threads_being_born_ GUARDED_BY(Locks::runtime_shutdown_lock_);
@@ -510,6 +510,9 @@
   jobject main_thread_group_;
   jobject system_thread_group_;
 
+  // As returned by ClassLoader.getSystemClassLoader().
+  jobject system_class_loader_;
+
   DISALLOW_COPY_AND_ASSIGN(Runtime);
 };
 
diff --git a/test/Android.mk b/test/Android.mk
index 6f498e8..da469d7 100644
--- a/test/Android.mk
+++ b/test/Android.mk
@@ -44,6 +44,7 @@
 	Main \
 	HelloWorld \
 	\
+	JniTest \
 	NativeAllocations \
 	ParallelGC \
 	ReferenceMap \
diff --git a/test/JniTest/JniTest.java b/test/JniTest/JniTest.java
new file mode 100644
index 0000000..431056a
--- /dev/null
+++ b/test/JniTest/JniTest.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+class JniTest {
+    public static void main(String[] args) {
+        System.loadLibrary("arttest");
+        testFindClassOnAttachedNativeThread();
+    }
+
+    private static native void testFindClassOnAttachedNativeThread();
+}
diff --git a/test/JniTest/jni_test.cc b/test/JniTest/jni_test.cc
new file mode 100644
index 0000000..ed69d39
--- /dev/null
+++ b/test/JniTest/jni_test.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 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 <assert.h>
+#include <stdio.h>
+#include <pthread.h>
+
+#include "jni.h"
+
+#if defined(NDEBUG)
+#error test code compiled without NDEBUG
+#endif
+
+static JavaVM* jvm = NULL;
+
+extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
+  assert(vm != NULL);
+  assert(jvm == NULL);
+  jvm = vm;
+  return JNI_VERSION_1_6;
+}
+
+static void* testFindClassOnAttachedNativeThread(void*) {
+  assert(jvm != NULL);
+
+  JNIEnv* env = NULL;
+  JavaVMAttachArgs args = { JNI_VERSION_1_6, __FUNCTION__, NULL };
+  int attach_result = jvm->AttachCurrentThread(&env, &args);
+  assert(attach_result == 0);
+
+  jclass clazz = env->FindClass("JniTest");
+  assert(clazz != NULL);
+  assert(!env->ExceptionCheck());
+
+  jobjectArray array = env->NewObjectArray(0, clazz, NULL);
+  assert(array != NULL);
+  assert(!env->ExceptionCheck());
+
+  int detach_result = jvm->DetachCurrentThread();
+  assert(detach_result == 0);
+  return NULL;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_JniTest_testFindClassOnAttachedNativeThread(JNIEnv*,
+                                                                                   jclass) {
+  pthread_t pthread;
+  int pthread_create_result = pthread_create(&pthread,
+                                             NULL,
+                                             testFindClassOnAttachedNativeThread,
+                                             NULL);
+  assert(pthread_create_result == 0);
+  int pthread_join_result = pthread_join(pthread, NULL);
+  assert(pthread_join_result == 0);
+}