diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/912-classes/classes.cc | 250 | ||||
| -rw-r--r-- | test/912-classes/src/Main.java | 75 | ||||
| -rw-r--r-- | test/Android.run-test.mk | 1 | ||||
| -rw-r--r-- | test/testrunner/env.py | 49 |
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) |