summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/912-classes/classes.cc250
-rw-r--r--test/912-classes/src/Main.java75
-rw-r--r--test/Android.run-test.mk1
-rw-r--r--test/testrunner/env.py49
4 files changed, 300 insertions, 75 deletions
diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc
index d13436ebf6..6c12522c98 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -17,9 +17,14 @@
#include <stdio.h>
#include "base/macros.h"
+#include "class_linker.h"
#include "jni.h"
+#include "mirror/class_loader.h"
#include "openjdkjvmti/jvmti.h"
+#include "runtime.h"
#include "ScopedLocalRef.h"
+#include "ScopedUtfChars.h"
+#include "scoped_thread_state_change-inl.h"
#include "thread-inl.h"
#include "ti-agent/common_helper.h"
@@ -278,69 +283,11 @@ static std::string GetClassName(jvmtiEnv* jenv, JNIEnv* jni_env, jclass klass) {
return tmp;
}
-static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) {
- jvmtiThreadInfo info;
- jvmtiError result = jenv->GetThreadInfo(thread, &info);
- if (result != JVMTI_ERROR_NONE) {
- if (jni_env != nullptr) {
- JvmtiErrorToException(jni_env, result);
- } else {
- printf("Failed to get thread name.\n");
- }
- return "";
- }
-
- std::string tmp(info.name);
- jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name));
- jni_env->DeleteLocalRef(info.context_class_loader);
- jni_env->DeleteLocalRef(info.thread_group);
-
- return tmp;
-}
-
-static std::string GetThreadName(Thread* thread) {
- std::string tmp;
- thread->GetThreadName(tmp);
- return tmp;
-}
-
-static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv,
- JNIEnv* jni_env,
- jthread thread,
- jclass klass) {
- std::string name = GetClassName(jenv, jni_env, klass);
- if (name == "") {
- return;
- }
- std::string thread_name = GetThreadName(jenv, jni_env, thread);
- if (thread_name == "") {
- return;
- }
- std::string cur_thread_name = GetThreadName(Thread::Current());
- printf("Prepare: %s on %s (cur=%s)\n",
- name.c_str(),
- thread_name.c_str(),
- cur_thread_name.c_str());
-}
-
-static void JNICALL ClassLoadCallback(jvmtiEnv* jenv,
- JNIEnv* jni_env,
- jthread thread,
- jclass klass) {
- std::string name = GetClassName(jenv, jni_env, klass);
- if (name == "") {
- return;
- }
- std::string thread_name = GetThreadName(jenv, jni_env, thread);
- if (thread_name == "") {
- return;
- }
- printf("Load: %s on %s\n", name.c_str(), thread_name.c_str());
-}
-
-extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents(
- JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
- if (b == JNI_FALSE) {
+static void EnableEvents(JNIEnv* env,
+ jboolean enable,
+ decltype(jvmtiEventCallbacks().ClassLoad) class_load,
+ decltype(jvmtiEventCallbacks().ClassPrepare) class_prepare) {
+ if (enable == JNI_FALSE) {
jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
JVMTI_EVENT_CLASS_LOAD,
nullptr);
@@ -356,8 +303,8 @@ extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents(
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
- callbacks.ClassLoad = ClassLoadCallback;
- callbacks.ClassPrepare = ClassPrepareCallback;
+ callbacks.ClassLoad = class_load;
+ callbacks.ClassPrepare = class_prepare;
jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
if (JvmtiErrorToException(env, ret)) {
return;
@@ -375,5 +322,178 @@ extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents(
JvmtiErrorToException(env, ret);
}
+class ClassLoadPreparePrinter {
+ public:
+ static void JNICALL ClassLoadCallback(jvmtiEnv* jenv,
+ JNIEnv* jni_env,
+ jthread thread,
+ jclass klass) {
+ std::string name = GetClassName(jenv, jni_env, klass);
+ if (name == "") {
+ return;
+ }
+ std::string thread_name = GetThreadName(jenv, jni_env, thread);
+ if (thread_name == "") {
+ return;
+ }
+ printf("Load: %s on %s\n", name.c_str(), thread_name.c_str());
+ }
+
+ static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv,
+ JNIEnv* jni_env,
+ jthread thread,
+ jclass klass) {
+ std::string name = GetClassName(jenv, jni_env, klass);
+ if (name == "") {
+ return;
+ }
+ std::string thread_name = GetThreadName(jenv, jni_env, thread);
+ if (thread_name == "") {
+ return;
+ }
+ std::string cur_thread_name = GetThreadName(Thread::Current());
+ printf("Prepare: %s on %s (cur=%s)\n",
+ name.c_str(),
+ thread_name.c_str(),
+ cur_thread_name.c_str());
+ }
+
+ private:
+ static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) {
+ jvmtiThreadInfo info;
+ jvmtiError result = jenv->GetThreadInfo(thread, &info);
+ if (result != JVMTI_ERROR_NONE) {
+ if (jni_env != nullptr) {
+ JvmtiErrorToException(jni_env, result);
+ } else {
+ printf("Failed to get thread name.\n");
+ }
+ return "";
+ }
+
+ std::string tmp(info.name);
+ jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name));
+ jni_env->DeleteLocalRef(info.context_class_loader);
+ jni_env->DeleteLocalRef(info.thread_group);
+
+ return tmp;
+ }
+
+ static std::string GetThreadName(Thread* thread) {
+ std::string tmp;
+ thread->GetThreadName(tmp);
+ return tmp;
+ }
+};
+
+extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadPreparePrintEvents(
+ JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean enable) {
+ EnableEvents(env,
+ enable,
+ ClassLoadPreparePrinter::ClassLoadCallback,
+ ClassLoadPreparePrinter::ClassPrepareCallback);
+}
+
+struct ClassLoadSeen {
+ static void JNICALL ClassLoadSeenCallback(jvmtiEnv* jenv ATTRIBUTE_UNUSED,
+ JNIEnv* jni_env ATTRIBUTE_UNUSED,
+ jthread thread ATTRIBUTE_UNUSED,
+ jclass klass ATTRIBUTE_UNUSED) {
+ saw_event = true;
+ }
+
+ static bool saw_event;
+};
+bool ClassLoadSeen::saw_event = false;
+
+extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadSeenEvents(
+ JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
+ EnableEvents(env, b, ClassLoadSeen::ClassLoadSeenCallback, nullptr);
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_hadLoadEvent(
+ JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED) {
+ return ClassLoadSeen::saw_event ? JNI_TRUE : JNI_FALSE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isLoadedClass(
+ JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring class_name) {
+ ScopedUtfChars name(env, class_name);
+ ScopedObjectAccess soa(Thread::Current());
+ Runtime* current = Runtime::Current();
+ ClassLinker* class_linker = current->GetClassLinker();
+ bool found =
+ class_linker->LookupClass(
+ soa.Self(),
+ name.c_str(),
+ soa.Decode<mirror::ClassLoader>(current->GetSystemClassLoader())) != nullptr;
+ return found ? JNI_TRUE : JNI_FALSE;
+}
+
+class ClassLoadPrepareEquality {
+ public:
+ static constexpr const char* kClassName = "LMain$ClassE;";
+
+ static void JNICALL ClassLoadCallback(jvmtiEnv* jenv,
+ JNIEnv* jni_env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jclass klass) {
+ std::string name = GetClassName(jenv, jni_env, klass);
+ if (name == kClassName) {
+ found_ = true;
+ stored_class_ = jni_env->NewGlobalRef(klass);
+ weakly_stored_class_ = jni_env->NewWeakGlobalRef(klass);
+ }
+ }
+
+ static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv,
+ JNIEnv* jni_env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jclass klass) {
+ std::string name = GetClassName(jenv, jni_env, klass);
+ if (name == kClassName) {
+ CHECK(stored_class_ != nullptr);
+ CHECK(jni_env->IsSameObject(stored_class_, klass));
+ CHECK(jni_env->IsSameObject(weakly_stored_class_, klass));
+ compared_ = true;
+ }
+ }
+
+ static void CheckFound() {
+ CHECK(found_);
+ CHECK(compared_);
+ }
+
+ static void Free(JNIEnv* env) {
+ if (stored_class_ != nullptr) {
+ env->DeleteGlobalRef(stored_class_);
+ DCHECK(weakly_stored_class_ != nullptr);
+ env->DeleteWeakGlobalRef(weakly_stored_class_);
+ }
+ }
+
+ private:
+ static jobject stored_class_;
+ static jweak weakly_stored_class_;
+ static bool found_;
+ static bool compared_;
+};
+jobject ClassLoadPrepareEquality::stored_class_ = nullptr;
+jweak ClassLoadPrepareEquality::weakly_stored_class_ = nullptr;
+bool ClassLoadPrepareEquality::found_ = false;
+bool ClassLoadPrepareEquality::compared_ = false;
+
+extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadPrepareEqualityEvents(
+ JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
+ EnableEvents(env,
+ b,
+ ClassLoadPrepareEquality::ClassLoadCallback,
+ ClassLoadPrepareEquality::ClassPrepareCallback);
+ if (b == JNI_FALSE) {
+ ClassLoadPrepareEquality::Free(env);
+ ClassLoadPrepareEquality::CheckFound();
+ }
+}
+
} // namespace Test912Classes
} // namespace art
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index 6ad23a4869..c1de679502 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -219,6 +219,15 @@ public class Main {
}
final ClassLoader boot = cl;
+ // The JIT may deeply inline and load some classes. Preload these for test determinism.
+ final String PRELOAD_FOR_JIT[] = {
+ "java.nio.charset.CoderMalfunctionError",
+ "java.util.NoSuchElementException"
+ };
+ for (String s : PRELOAD_FOR_JIT) {
+ Class.forName(s);
+ }
+
Runnable r = new Runnable() {
@Override
public void run() {
@@ -238,7 +247,7 @@ public class Main {
ensureJitCompiled(Main.class, "testClassEvents");
- enableClassLoadEvents(true);
+ enableClassLoadPreparePrintEvents(true);
ClassLoader cl1 = create(boot, DEX1, DEX2);
System.out.println("B, false");
@@ -270,7 +279,47 @@ public class Main {
t.start();
t.join();
- enableClassLoadEvents(false);
+ enableClassLoadPreparePrintEvents(false);
+
+ // Note: the JIT part of this test is about the JIT pulling in a class not yet touched by
+ // anything else in the system. This could be the verifier or the interpreter. We
+ // block the interpreter by calling ensureJitCompiled. The verifier, however, must
+ // run in configurations where dex2oat didn't verify the class itself. So explicitly
+ // check whether the class has been already loaded, and skip then.
+ // TODO: Add multiple configurations to the run script once that becomes easier to do.
+ if (hasJit() && !isLoadedClass("Main$ClassD")) {
+ testClassEventsJit();
+ }
+
+ testClassLoadPrepareEquality();
+ }
+
+ private static void testClassEventsJit() throws Exception {
+ enableClassLoadSeenEvents(true);
+
+ testClassEventsJitImpl();
+
+ enableClassLoadSeenEvents(false);
+
+ if (!hadLoadEvent()) {
+ throw new RuntimeException("Did not get expected load event.");
+ }
+ }
+
+ private static void testClassEventsJitImpl() throws Exception {
+ ensureJitCompiled(Main.class, "testClassEventsJitImpl");
+
+ if (ClassD.x != 1) {
+ throw new RuntimeException("Unexpected value");
+ }
+ }
+
+ private static void testClassLoadPrepareEquality() throws Exception {
+ enableClassLoadPrepareEqualityEvents(true);
+
+ Class.forName("Main$ClassE");
+
+ enableClassLoadPrepareEqualityEvents(false);
}
private static void printClassLoaderClasses(ClassLoader cl) {
@@ -335,9 +384,16 @@ public class Main {
private static native int[] getClassVersion(Class<?> c);
- private static native void enableClassLoadEvents(boolean b);
+ private static native void enableClassLoadPreparePrintEvents(boolean b);
+
+ private static native void ensureJitCompiled(Class<?> c, String name);
- private static native void ensureJitCompiled(Class c, String name);
+ private static native boolean hasJit();
+ private static native boolean isLoadedClass(String name);
+ private static native void enableClassLoadSeenEvents(boolean b);
+ private static native boolean hadLoadEvent();
+
+ private static native void enableClassLoadPrepareEqualityEvents(boolean b);
private static class TestForNonInit {
public static double dummy = Math.random(); // So it can't be compile-time initialized.
@@ -361,6 +417,17 @@ public class Main {
public abstract static class ClassC implements InfA, InfC {
}
+ public static class ClassD {
+ static int x = 1;
+ }
+
+ public static class ClassE {
+ public void foo() {
+ }
+ public void bar() {
+ }
+ }
+
private static final String DEX1 = System.getenv("DEX_LOCATION") + "/912-classes.jar";
private static final String DEX2 = System.getenv("DEX_LOCATION") + "/912-classes-ex.jar";
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index b937c931e7..1938b92db8 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -540,7 +540,6 @@ TEST_ART_BROKEN_JIT_RUN_TESTS := \
629-vdex-speed \
904-object-allocation \
906-iterate-heap \
- 912-classes \
ifneq (,$(filter jit,$(COMPILER_TYPES)))
ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index 278980fd30..1dc8ce552c 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -15,6 +15,7 @@
# limitations under the License.
import os
+import re
import tempfile
import subprocess
@@ -29,14 +30,52 @@ def getEnvBoolean(var, default):
return False
return default
-def get_build_var(var_name):
+_DUMP_MANY_VARS_LIST = ['HOST_2ND_ARCH_PREFIX',
+ 'TARGET_2ND_ARCH',
+ 'TARGET_ARCH',
+ 'HOST_PREFER_32_BIT',
+ 'HOST_OUT_EXECUTABLES']
+_DUMP_MANY_VARS = None # To be set to a dictionary with above list being the keys,
+ # and the build variable being the value.
+def dump_many_vars(var_name):
+ """
+ Reach into the Android build system to dump many build vars simultaneously.
+ Since the make system is so slow, we want to avoid calling into build frequently.
+ """
+ global _DUMP_MANY_VARS
+ global _DUMP_MANY_VARS_LIST
+
+ # Look up var from cache.
+ if _DUMP_MANY_VARS:
+ return _DUMP_MANY_VARS[var_name]
+
+ all_vars=" ".join(_DUMP_MANY_VARS_LIST)
+
# The command is taken from build/envsetup.sh to fetch build variables.
- command = ("CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core "
+ command = ("CALLED_FROM_SETUP=true " # Enable the 'dump-many-vars' make target.
+ "BUILD_SYSTEM=build/core " # Set up lookup path for make includes.
"make --no-print-directory -C \"%s\" -f build/core/config.mk "
- "dumpvar-%s") % (ANDROID_BUILD_TOP, var_name)
+ "dump-many-vars DUMP_MANY_VARS=\"%s\"") % (ANDROID_BUILD_TOP, all_vars)
+
config = subprocess.Popen(command, stdout=subprocess.PIPE,
- shell=True).communicate()[0]
- return config.strip()
+ shell=True).communicate()[0] # read until EOF, select stdin
+ # Prints out something like:
+ # TARGET_ARCH='arm64'
+ # HOST_ARCH='x86_64'
+ _DUMP_MANY_VARS = {}
+ for line in config.split("\n"):
+ # Split out "$key='$value'" via regex.
+ match = re.search("([^=]+)='([^']*)", line)
+ if not match:
+ continue
+ key = match.group(1)
+ value = match.group(2)
+ _DUMP_MANY_VARS[key] = value
+
+ return _DUMP_MANY_VARS[var_name]
+
+def get_build_var(var_name):
+ return dump_many_vars(var_name)
def get_env(key):
return env.get(key)