Revert^8 "Make sure that const-class linkage is preserved."
Replaced two ReaderMutexLocks with WriterMutexLocks.
Removed some unnecessary debugging output.
Test: m test-art-host
Bug: 30627598
Original-Change-Id: Ie9b721464b4e9a5dcce8df8095548e983bba1fe8
This reverts commit 2c8c6b63da6ecb2ac701cc30f9b4fa4a8eea5cc8.
Change-Id: I3a1aeecf64e4b202cef61cceb248d48106a2f4a6
diff --git a/test/626-const-class-linking/clear_dex_cache_types.cc b/test/626-const-class-linking/clear_dex_cache_types.cc
new file mode 100644
index 0000000..b035896
--- /dev/null
+++ b/test/626-const-class-linking/clear_dex_cache_types.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+#include "object_lock.h"
+#include "scoped_thread_state_change-inl.h"
+
+namespace art {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_nativeClearResolvedTypes(JNIEnv*, jclass, jclass cls) {
+ ScopedObjectAccess soa(Thread::Current());
+ mirror::DexCache* dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
+ for (size_t i = 0, num_types = dex_cache->NumResolvedTypes(); i != num_types; ++i) {
+ dex_cache->SetResolvedType(dex::TypeIndex(i), ObjPtr<mirror::Class>(nullptr));
+ }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_nativeSkipVerification(JNIEnv*, jclass, jclass cls) {
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScope<1> hs(soa.Self());
+ Handle<mirror::Class> klass = hs.NewHandle(soa.Decode<mirror::Class>(cls));
+ mirror::Class::Status status = klass->GetStatus();
+ if (status == mirror::Class::kStatusResolved) {
+ ObjectLock<mirror::Class> lock(soa.Self(), klass);
+ klass->SetStatus(klass, mirror::Class::kStatusVerified, soa.Self());
+ } else {
+ LOG(ERROR) << klass->PrettyClass() << " has unexpected status: " << status;
+ }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_nativeDumpClasses(JNIEnv*, jclass, jobjectArray array) {
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScope<1> hs(soa.Self());
+ Handle<mirror::ObjectArray<mirror::Object>> classes =
+ hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(array));
+ CHECK(classes.Get() != nullptr);
+ for (size_t i = 0, length = classes->GetLength(); i != length; ++i) {
+ CHECK(classes->Get(i) != nullptr) << i;
+ CHECK(classes->Get(i)->IsClass())
+ << i << " " << classes->Get(i)->GetClass()->PrettyDescriptor();
+ mirror::Class* as_class = classes->Get(i)->AsClass();
+ mirror::ClassLoader* loader = as_class->GetClassLoader();
+ LOG(ERROR) << "Class #" << i << ": " << as_class->PrettyDescriptor()
+ << " @" << static_cast<const void*>(as_class)
+ << " status:" << as_class->GetStatus()
+ << " definingLoader:" << static_cast<const void*>(loader)
+ << " definingLoaderClass:"
+ << (loader != nullptr ? loader->GetClass()->PrettyDescriptor() : "N/A");
+ }
+}
+
+} // namespace art
diff --git a/test/626-const-class-linking/expected.txt b/test/626-const-class-linking/expected.txt
new file mode 100644
index 0000000..de1b815
--- /dev/null
+++ b/test/626-const-class-linking/expected.txt
@@ -0,0 +1,61 @@
+JNI_OnLoad called
+first: Helper1 class loader: DelegatingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: DelegatingLoader
+second: Test class loader: DefiningLoader
+testClearDexCache done
+first: Helper1 class loader: DelegatingLoader
+second: Test class loader: DefiningLoader
+first: Helper2 class loader: DelegatingLoader
+second: Test class loader: DefiningLoader
+testMultiDex done
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+total: 4
+ throwables: 0
+ classes: 4 (1 unique)
+testRacyLoader done
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper3 class loader: RacyLoader
+second: Test3 class loader: DefiningLoader
+first: Helper3 class loader: RacyLoader
+second: Test3 class loader: DefiningLoader
+total: 4
+ throwables: 0
+ classes: 4 (2 unique)
+testRacyLoader2 done
+java.lang.NoClassDefFoundError: Initiating class loader of type MisbehavingLoader returned class Helper2 instead of Test.
+testMisbehavingLoader done
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+total: 4
+ throwables: 0
+ classes: 4 (1 unique)
+testRacyMisbehavingLoader done
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+total: 4
+ throwables: 0
+ classes: 4 (1 unique)
+testRacyMisbehavingLoader2 done
diff --git a/test/626-const-class-linking/info.txt b/test/626-const-class-linking/info.txt
new file mode 100644
index 0000000..9c19a46
--- /dev/null
+++ b/test/626-const-class-linking/info.txt
@@ -0,0 +1,3 @@
+Test that once a const-class instruction is linked, it will keep referring
+to the same class even in the presence of custom class loaders even after
+clearing the dex cache type array.
diff --git a/test/626-const-class-linking/multidex.jpp b/test/626-const-class-linking/multidex.jpp
new file mode 100644
index 0000000..c7a6648
--- /dev/null
+++ b/test/626-const-class-linking/multidex.jpp
@@ -0,0 +1,27 @@
+ClassPair:
+ @@com.android.jack.annotations.ForceInMainDex
+ class ClassPair
+DefiningLoader:
+ @@com.android.jack.annotations.ForceInMainDex
+ class DefiningLoader
+DelegatingLoader:
+ @@com.android.jack.annotations.ForceInMainDex
+ class DelegatingLoader
+Helper1:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Helper1
+Main:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Main
+MisbehavingLoader:
+ @@com.android.jack.annotations.ForceInMainDex
+ class MisbehavingLoader
+RacyLoader:
+ @@com.android.jack.annotations.ForceInMainDex
+ class RacyLoader
+RacyMisbehavingHelper:
+ @@com.android.jack.annotations.ForceInMainDex
+ class RacyMisbehavingHelper
+RacyMisbehavingLoader:
+ @@com.android.jack.annotations.ForceInMainDex
+ class RacyMisbehavingLoader
diff --git a/test/626-const-class-linking/src-multidex/Helper2.java b/test/626-const-class-linking/src-multidex/Helper2.java
new file mode 100644
index 0000000..5bb31ee
--- /dev/null
+++ b/test/626-const-class-linking/src-multidex/Helper2.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Helper2 {
+ public static ClassPair get() {
+ Class<?> helper2_class = Helper2.class;
+ Class<?> test_class = Test.class;
+ return new ClassPair(helper2_class, test_class);
+ }
+}
diff --git a/test/626-const-class-linking/src-multidex/Helper3.java b/test/626-const-class-linking/src-multidex/Helper3.java
new file mode 100644
index 0000000..af996de
--- /dev/null
+++ b/test/626-const-class-linking/src-multidex/Helper3.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Helper3 {
+ public static ClassPair get() {
+ Class<?> helper3_class = Helper3.class;
+ Class<?> test3_class = Test3.class;
+ return new ClassPair(helper3_class, test3_class);
+ }
+}
diff --git a/test/626-const-class-linking/src-multidex/Test.java b/test/626-const-class-linking/src-multidex/Test.java
new file mode 100644
index 0000000..1b0cc2a
--- /dev/null
+++ b/test/626-const-class-linking/src-multidex/Test.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Test {
+}
diff --git a/test/626-const-class-linking/src-multidex/Test3.java b/test/626-const-class-linking/src-multidex/Test3.java
new file mode 100644
index 0000000..c4b134d
--- /dev/null
+++ b/test/626-const-class-linking/src-multidex/Test3.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Test3 {
+}
diff --git a/test/626-const-class-linking/src/ClassPair.java b/test/626-const-class-linking/src/ClassPair.java
new file mode 100644
index 0000000..b07036c
--- /dev/null
+++ b/test/626-const-class-linking/src/ClassPair.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class ClassPair {
+ public Class<?> first;
+ public Class<?> second;
+
+ public ClassPair(Class<?> first, Class<?> second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ public void print() {
+ String first_loader_name = first.getClassLoader().getClass().getName();
+ System.out.println("first: " + first.getName() + " class loader: " + first_loader_name);
+ String second_loader_name = second.getClassLoader().getClass().getName();
+ System.out.println("second: " + second.getName() + " class loader: " + second_loader_name);
+ }
+}
diff --git a/test/626-const-class-linking/src/DefiningLoader.java b/test/626-const-class-linking/src/DefiningLoader.java
new file mode 100644
index 0000000..b17ab77
--- /dev/null
+++ b/test/626-const-class-linking/src/DefiningLoader.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A class loader with atypical behavior: we try to load a private
+ * class implementation before asking the system or boot loader. This
+ * is used to create multiple classes with identical names in a single VM.
+ *
+ * If DexFile is available, we use that; if not, we assume we're not in
+ * Dalvik and instantiate the class with defineClass().
+ *
+ * The location of the DEX files and class data is dependent upon the
+ * test framework.
+ */
+public class DefiningLoader extends ClassLoader {
+ static {
+ // For JVM, register as parallel capable.
+ // Android treats all class loaders as parallel capable and makes this a no-op.
+ registerAsParallelCapable();
+ }
+
+ /* this is where the .class files live */
+ static final String CLASS_PATH1 = "classes/";
+ static final String CLASS_PATH2 = "classes2/";
+
+ /* this is the DEX/Jar file */
+ static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/626-const-class-linking.jar";
+
+ /* on Dalvik, this is a DexFile; otherwise, it's null */
+ private Class<?> mDexClass;
+
+ private Object mDexFile;
+
+ /**
+ * Construct DefiningLoader, grabbing a reference to the DexFile class
+ * if we're running under Dalvik.
+ */
+ public DefiningLoader(ClassLoader parent) {
+ super(parent);
+
+ try {
+ mDexClass = parent.loadClass("dalvik.system.DexFile");
+ } catch (ClassNotFoundException cnfe) {
+ // ignore -- not running Dalvik
+ }
+ }
+
+ /**
+ * Finds the class with the specified binary name.
+ *
+ * We search for a file in CLASS_PATH or pull an entry from DEX_FILE.
+ * If we don't find a match, we throw an exception.
+ */
+ protected Class<?> findClass(String name) throws ClassNotFoundException
+ {
+ if (mDexClass != null) {
+ return findClassDalvik(name);
+ } else {
+ return findClassNonDalvik(name);
+ }
+ }
+
+ /**
+ * Finds the class with the specified binary name, from a DEX file.
+ */
+ private Class<?> findClassDalvik(String name)
+ throws ClassNotFoundException {
+
+ if (mDexFile == null) {
+ synchronized (DefiningLoader.class) {
+ Constructor<?> ctor;
+ /*
+ * Construct a DexFile object through reflection.
+ */
+ try {
+ ctor = mDexClass.getConstructor(String.class);
+ } catch (NoSuchMethodException nsme) {
+ throw new ClassNotFoundException("getConstructor failed",
+ nsme);
+ }
+
+ try {
+ mDexFile = ctor.newInstance(DEX_FILE);
+ } catch (InstantiationException ie) {
+ throw new ClassNotFoundException("newInstance failed", ie);
+ } catch (IllegalAccessException iae) {
+ throw new ClassNotFoundException("newInstance failed", iae);
+ } catch (InvocationTargetException ite) {
+ throw new ClassNotFoundException("newInstance failed", ite);
+ }
+ }
+ }
+
+ /*
+ * Call DexFile.loadClass(String, ClassLoader).
+ */
+ Method meth;
+
+ try {
+ meth = mDexClass.getMethod("loadClass", String.class, ClassLoader.class);
+ } catch (NoSuchMethodException nsme) {
+ throw new ClassNotFoundException("getMethod failed", nsme);
+ }
+
+ try {
+ meth.invoke(mDexFile, name, this);
+ } catch (IllegalAccessException iae) {
+ throw new ClassNotFoundException("loadClass failed", iae);
+ } catch (InvocationTargetException ite) {
+ throw new ClassNotFoundException("loadClass failed",
+ ite.getCause());
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the class with the specified binary name, from .class files.
+ */
+ private Class<?> findClassNonDalvik(String name)
+ throws ClassNotFoundException {
+
+ String[] pathNames = { CLASS_PATH1 + name + ".class", CLASS_PATH2 + name + ".class" };
+
+ String pathName = null;
+ RandomAccessFile raf = null;
+
+ for (String pn : pathNames) {
+ pathName = pn;
+ try {
+ //System.out.println("--- Defining: looking for " + pathName);
+ raf = new RandomAccessFile(new File(pathName), "r");
+ break;
+ } catch (FileNotFoundException fnfe) {
+ }
+ }
+ if (raf == null) {
+ throw new ClassNotFoundException("Not found: " + pathNames[0] + ":" + pathNames[1]);
+ }
+
+ /* read the entire file in */
+ byte[] fileData;
+ try {
+ fileData = new byte[(int) raf.length()];
+ raf.readFully(fileData);
+ } catch (IOException ioe) {
+ throw new ClassNotFoundException("Read error: " + pathName);
+ } finally {
+ try {
+ raf.close();
+ } catch (IOException ioe) {
+ // drop
+ }
+ }
+
+ /* create the class */
+ //System.out.println("--- Defining: defining " + name);
+ try {
+ return defineClass(name, fileData, 0, fileData.length);
+ } catch (Throwable th) {
+ throw new ClassNotFoundException("defineClass failed", th);
+ }
+ }
+
+ /**
+ * Load a class.
+ *
+ * Normally a class loader wouldn't override this, but we want our
+ * version of the class to take precedence over an already-loaded
+ * version.
+ *
+ * We still want the system classes (e.g. java.lang.Object) from the
+ * bootstrap class loader.
+ */
+ synchronized protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ Class<?> res;
+
+ /*
+ * 1. Invoke findLoadedClass(String) to check if the class has
+ * already been loaded.
+ *
+ * This doesn't change.
+ */
+ res = findLoadedClass(name);
+ if (res != null) {
+ // System.out.println("FancyLoader.loadClass: " + name + " already loaded");
+ if (resolve)
+ resolveClass(res);
+ return res;
+ }
+
+ /*
+ * 3. Invoke the findClass(String) method to find the class.
+ */
+ try {
+ res = findClass(name);
+ if (resolve)
+ resolveClass(res);
+ }
+ catch (ClassNotFoundException e) {
+ // we couldn't find it, so eat the exception and keep going
+ }
+
+ /*
+ * 2. Invoke the loadClass method on the parent class loader. If
+ * the parent loader is null the class loader built-in to the
+ * virtual machine is used, instead.
+ *
+ * (Since we're not in java.lang, we can't actually invoke the
+ * parent's loadClass() method, but we passed our parent to the
+ * super-class which can take care of it for us.)
+ */
+ res = super.loadClass(name, resolve); // returns class or throws
+ return res;
+ }
+}
diff --git a/test/626-const-class-linking/src/DelegatingLoader.java b/test/626-const-class-linking/src/DelegatingLoader.java
new file mode 100644
index 0000000..49955d4
--- /dev/null
+++ b/test/626-const-class-linking/src/DelegatingLoader.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class DelegatingLoader extends DefiningLoader {
+ private DefiningLoader defining_loader;
+
+ public DelegatingLoader(ClassLoader parent, DefiningLoader defining_loader) {
+ super(parent);
+ this.defining_loader = defining_loader;
+ }
+
+ public void resetDefiningLoader(DefiningLoader defining_loader) {
+ this.defining_loader = defining_loader;
+ }
+
+ protected Class<?> findClass(String name) throws ClassNotFoundException
+ {
+ if (name.equals("Test")) {
+ throw new Error("Unexpected DelegatingLoader.findClass(\"Test\")");
+ }
+ return super.findClass(name);
+ }
+
+ protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ if (name.equals("Test")) {
+ return defining_loader.loadClass(name, resolve);
+ }
+ return super.loadClass(name, resolve);
+ }
+}
diff --git a/test/626-const-class-linking/src/Helper1.java b/test/626-const-class-linking/src/Helper1.java
new file mode 100644
index 0000000..ff9cd1a
--- /dev/null
+++ b/test/626-const-class-linking/src/Helper1.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Helper1 {
+ public static ClassPair get() {
+ Class<?> helper1_class = Helper1.class;
+ Class<?> test_class = Test.class;
+ return new ClassPair(helper1_class, test_class);
+ }
+}
diff --git a/test/626-const-class-linking/src/Main.java b/test/626-const-class-linking/src/Main.java
new file mode 100644
index 0000000..0029428
--- /dev/null
+++ b/test/626-const-class-linking/src/Main.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ try {
+ System.loadLibrary(args[0]);
+ } catch (UnsatisfiedLinkError ule) {
+ usingRI = true;
+ // Add expected JNI_OnLoad log line to match expected.txt.
+ System.out.println("JNI_OnLoad called");
+ }
+
+ testClearDexCache();
+ testMultiDex();
+ testRacyLoader();
+ testRacyLoader2();
+ testMisbehavingLoader();
+ testRacyMisbehavingLoader();
+ testRacyMisbehavingLoader2();
+ }
+
+ private static void testClearDexCache() throws Exception {
+ DelegatingLoader delegating_loader = createDelegatingLoader();
+ Class<?> helper = delegating_loader.loadClass("Helper1");
+
+ WeakReference<Class<?>> weak_test1 = wrapHelperGet(helper);
+ changeInner(delegating_loader);
+ clearResolvedTypes(helper);
+ Runtime.getRuntime().gc();
+ WeakReference<Class<?>> weak_test2 = wrapHelperGet(helper);
+ Runtime.getRuntime().gc();
+
+ Class<?> test1 = weak_test1.get();
+ if (test1 == null) {
+ System.out.println("test1 disappeared");
+ }
+ Class<?> test2 = weak_test2.get();
+ if (test2 == null) {
+ System.out.println("test2 disappeared");
+ }
+ if (test1 != test2) {
+ System.out.println("test1 != test2");
+ }
+
+ System.out.println("testClearDexCache done");
+ }
+
+ private static void testMultiDex() throws Exception {
+ DelegatingLoader delegating_loader = createDelegatingLoader();
+
+ Class<?> helper1 = delegating_loader.loadClass("Helper1");
+ WeakReference<Class<?>> weak_test1 = wrapHelperGet(helper1);
+
+ changeInner(delegating_loader);
+
+ Class<?> helper2 = delegating_loader.loadClass("Helper2");
+ WeakReference<Class<?>> weak_test2 = wrapHelperGet(helper2);
+
+ Runtime.getRuntime().gc();
+
+ Class<?> test1 = weak_test1.get();
+ if (test1 == null) {
+ System.out.println("test1 disappeared");
+ }
+ Class<?> test2 = weak_test2.get();
+ if (test2 == null) {
+ System.out.println("test2 disappeared");
+ }
+ if (test1 != test2) {
+ System.out.println("test1 != test2");
+ }
+
+ System.out.println("testMultiDex done");
+ }
+
+ private static void testMisbehavingLoader() throws Exception {
+ ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+ DefiningLoader defining_loader = new DefiningLoader(system_loader);
+ MisbehavingLoader misbehaving_loader =
+ new MisbehavingLoader(system_loader, defining_loader);
+ Class<?> helper = misbehaving_loader.loadClass("Helper1");
+
+ try {
+ WeakReference<Class<?>> weak_test = wrapHelperGet(helper);
+ } catch (InvocationTargetException ite) {
+ String message = ite.getCause().getMessage();
+ if (usingRI && "Test".equals(message)) {
+ // Replace RI message with dalvik message to match expected.txt.
+ message = "Initiating class loader of type " +
+ misbehaving_loader.getClass().getName() +
+ " returned class Helper2 instead of Test.";
+ }
+ System.out.println(ite.getCause().getClass().getName() + ": " + message);
+ }
+ System.out.println("testMisbehavingLoader done");
+ }
+
+ private static void testRacyLoader() throws Exception {
+ final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+
+ final Thread[] threads = new Thread[4];
+ final Object[] results = new Object[threads.length];
+
+ final RacyLoader racy_loader = new RacyLoader(system_loader, threads.length);
+ final Class<?> helper1 = racy_loader.loadClass("Helper1");
+ skipVerification(helper1); // Avoid class loading during verification.
+
+ for (int i = 0; i != threads.length; ++i) {
+ final int my_index = i;
+ Thread t = new Thread() {
+ public void run() {
+ try {
+ Method get = helper1.getDeclaredMethod("get");
+ results[my_index] = get.invoke(null);
+ } catch (InvocationTargetException ite) {
+ results[my_index] = ite.getCause();
+ } catch (Throwable t) {
+ results[my_index] = t;
+ }
+ }
+ };
+ t.start();
+ threads[i] = t;
+ }
+ for (Thread t : threads) {
+ t.join();
+ }
+ dumpResultStats(results, 1);
+ System.out.println("testRacyLoader done");
+ }
+
+ private static void testRacyLoader2() throws Exception {
+ final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+
+ final Thread[] threads = new Thread[4];
+ final Object[] results = new Object[threads.length];
+
+ final RacyLoader racy_loader = new RacyLoader(system_loader, threads.length);
+ final Class<?> helper1 = racy_loader.loadClass("Helper1");
+ skipVerification(helper1); // Avoid class loading during verification.
+ final Class<?> helper3 = racy_loader.loadClass("Helper3");
+ skipVerification(helper3); // Avoid class loading during verification.
+
+ for (int i = 0; i != threads.length; ++i) {
+ final int my_index = i;
+ Thread t = new Thread() {
+ public void run() {
+ try {
+ Class<?> helper = (my_index < threads.length / 2) ? helper1 : helper3;
+ Method get = helper.getDeclaredMethod("get");
+ results[my_index] = get.invoke(null);
+ } catch (InvocationTargetException ite) {
+ results[my_index] = ite.getCause();
+ } catch (Throwable t) {
+ results[my_index] = t;
+ }
+ }
+ };
+ t.start();
+ threads[i] = t;
+ }
+ for (Thread t : threads) {
+ t.join();
+ }
+ dumpResultStats(results, 2);
+ System.out.println("testRacyLoader2 done");
+ }
+
+ private static void testRacyMisbehavingLoader() throws Exception {
+ final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+
+ final Thread[] threads = new Thread[4];
+ final Object[] results = new Object[threads.length];
+
+ final RacyMisbehavingLoader racy_loader =
+ new RacyMisbehavingLoader(system_loader, threads.length, false);
+ final Class<?> helper1 = racy_loader.loadClass("RacyMisbehavingHelper");
+ skipVerification(helper1); // Avoid class loading during verification.
+
+ for (int i = 0; i != threads.length; ++i) {
+ final int my_index = i;
+ Thread t = new Thread() {
+ public void run() {
+ try {
+ Method get = helper1.getDeclaredMethod("get");
+ results[my_index] = get.invoke(null);
+ } catch (InvocationTargetException ite) {
+ results[my_index] = ite.getCause();
+ } catch (Throwable t) {
+ results[my_index] = t;
+ }
+ }
+ };
+ t.start();
+ threads[i] = t;
+ }
+ for (Thread t : threads) {
+ t.join();
+ }
+ dumpResultStats(results, 1);
+ System.out.println("testRacyMisbehavingLoader done");
+ }
+
+ private static void testRacyMisbehavingLoader2() throws Exception {
+ final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+
+ final Thread[] threads = new Thread[4];
+ final Object[] results = new Object[threads.length];
+
+ final RacyMisbehavingLoader racy_loader =
+ new RacyMisbehavingLoader(system_loader, threads.length, true);
+ final Class<?> helper1 = racy_loader.loadClass("RacyMisbehavingHelper");
+ skipVerification(helper1); // Avoid class loading during verification.
+
+ for (int i = 0; i != threads.length; ++i) {
+ final int my_index = i;
+ Thread t = new Thread() {
+ public void run() {
+ try {
+ Method get = helper1.getDeclaredMethod("get");
+ results[my_index] = get.invoke(null);
+ } catch (InvocationTargetException ite) {
+ results[my_index] = ite.getCause();
+ } catch (Throwable t) {
+ results[my_index] = t;
+ }
+ }
+ };
+ t.start();
+ threads[i] = t;
+ }
+ for (Thread t : threads) {
+ t.join();
+ }
+ dumpResultStats(results, 1);
+ System.out.println("testRacyMisbehavingLoader2 done");
+ }
+
+ private static void dumpResultStats(Object[] results, int expected_unique) throws Exception {
+ int throwables = 0;
+ int classes = 0;
+ int unique_classes = 0;
+ for (int i = 0; i != results.length; ++i) {
+ Object r = results[i];
+ if (r instanceof Throwable) {
+ ++throwables;
+ System.out.println(((Throwable) r).getMessage());
+ } else if (isClassPair(r)) {
+ printPair(r);
+ Object ref = getSecond(r);
+ ++classes;
+ ++unique_classes;
+ for (int j = 0; j != i; ++j) {
+ Object rj = results[j];
+ if (isClassPair(results[j]) && getSecond(results[j]) == ref) {
+ --unique_classes;
+ break;
+ }
+ }
+ }
+ }
+ System.out.println("total: " + results.length);
+ System.out.println(" throwables: " + throwables);
+ System.out.println(" classes: " + classes
+ + " (" + unique_classes + " unique)");
+ if (expected_unique != unique_classes) {
+ System.out.println("MISMATCH with expected_unique: " + expected_unique);
+ ArrayList<Class<?>> list = new ArrayList<Class<?>>();
+ for (int i = 0; i != results.length; ++i) {
+ Object r = results[i];
+ if (isClassPair(r)) {
+ list.add(getSecond(r));
+ }
+ }
+ nativeDumpClasses(list.toArray());
+ }
+ }
+
+ private static DelegatingLoader createDelegatingLoader() {
+ ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+ DefiningLoader defining_loader = new DefiningLoader(system_loader);
+ return new DelegatingLoader(system_loader, defining_loader);
+ }
+
+ private static void changeInner(DelegatingLoader delegating_loader) {
+ ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+ DefiningLoader defining_loader = new DefiningLoader(system_loader);
+ delegating_loader.resetDefiningLoader(defining_loader);
+ }
+
+ private static WeakReference<Class<?>> wrapHelperGet(Class<?> helper) throws Exception {
+ Method get = helper.getDeclaredMethod("get");
+ Object pair = get.invoke(null);
+ printPair(pair);
+ return new WeakReference<Class<?>>(getSecond(pair));
+ }
+
+ private static void printPair(Object pair) throws Exception {
+ Method print = pair.getClass().getDeclaredMethod("print");
+ print.invoke(pair);
+ }
+
+ private static Class<?> getSecond(Object pair) throws Exception {
+ Field second = pair.getClass().getDeclaredField("second");
+ return (Class<?>) second.get(pair);
+ }
+
+ private static boolean isClassPair(Object r) {
+ return r != null && r.getClass().getName().equals("ClassPair");
+ }
+
+ public static void clearResolvedTypes(Class<?> c) {
+ if (!usingRI) {
+ nativeClearResolvedTypes(c);
+ }
+ }
+
+ // Skip verification of a class on ART. Verification can cause classes to be loaded
+ // while holding a lock on the class being verified and holding that lock can interfere
+ // with the intent of the "racy" tests. In these tests we're waiting in the loadClass()
+ // for all the tested threads to synchronize and they cannot reach that point if they
+ // are waiting for the class lock on ClassLinker::InitializeClass(Helper1/Helper3).
+ public static void skipVerification(Class<?> c) {
+ if (!usingRI) {
+ nativeSkipVerification(c);
+ }
+ }
+
+ public static native void nativeClearResolvedTypes(Class<?> c);
+ public static native void nativeSkipVerification(Class<?> c);
+ public static native void nativeDumpClasses(Object[] array);
+
+ static boolean usingRI = false;
+}
diff --git a/test/626-const-class-linking/src/MisbehavingLoader.java b/test/626-const-class-linking/src/MisbehavingLoader.java
new file mode 100644
index 0000000..ca9783e
--- /dev/null
+++ b/test/626-const-class-linking/src/MisbehavingLoader.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Class loader that returns Helper2.class when asked to load "Test".
+public class MisbehavingLoader extends DefiningLoader {
+ private DefiningLoader defining_loader;
+
+ public MisbehavingLoader(ClassLoader parent, DefiningLoader defining_loader) {
+ super(parent);
+ this.defining_loader = defining_loader;
+ }
+
+ protected Class<?> findClass(String name) throws ClassNotFoundException
+ {
+ if (name.equals("Helper1") || name.equals("Helper2")) {
+ return super.findClass(name);
+ } else if (name.equals("Test")) {
+ throw new Error("Unexpected MisbehavingLoader.findClass(\"Test\")");
+ }
+ return super.findClass(name);
+ }
+
+ protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ if (name.equals("Helper1") || name.equals("Helper2")) {
+ return super.loadClass(name, resolve);
+ } else if (name.equals("Test")) {
+ // Ask for a different class.
+ return defining_loader.loadClass("Helper2", resolve);
+ }
+ return super.loadClass(name, resolve);
+ }
+}
diff --git a/test/626-const-class-linking/src/RacyLoader.java b/test/626-const-class-linking/src/RacyLoader.java
new file mode 100644
index 0000000..9c164a3
--- /dev/null
+++ b/test/626-const-class-linking/src/RacyLoader.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class RacyLoader extends DefiningLoader {
+ static {
+ // For JVM, register as parallel capable.
+ // Android treats all class loaders as parallel capable and makes this a no-op.
+ registerAsParallelCapable();
+ }
+
+ private Object lock = new Object();
+ private int index = 0;
+ private int count;
+
+ private DefiningLoader[] defining_loaders;
+
+ public RacyLoader(ClassLoader parent, int count) {
+ super(parent);
+ this.count = count;
+ defining_loaders = new DefiningLoader[2];
+ for (int i = 0; i != defining_loaders.length; ++i) {
+ defining_loaders[i] = new DefiningLoader(parent);
+ }
+ }
+
+ protected Class<?> findClass(String name) throws ClassNotFoundException
+ {
+ if (name.equals("Test") || name.equals("Test3")) {
+ throw new Error("Unexpected RacyLoader.findClass(\"" + name + "\")");
+ }
+ return super.findClass(name);
+ }
+
+ protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ if (name.equals("Test") || name.equals("Test3")) {
+ int my_index = syncWithOtherInstances(count);
+ Class<?> result = defining_loaders[my_index & 1].loadClass(name, resolve);
+ syncWithOtherInstances(2 * count);
+ return result;
+ }
+ return super.loadClass(name, resolve);
+ }
+
+ private int syncWithOtherInstances(int limit) {
+ int my_index;
+ synchronized (lock) {
+ my_index = index;
+ ++index;
+ if (index != limit) {
+ do {
+ try {
+ lock.wait();
+ } catch (InterruptedException ie) {
+ throw new Error(ie);
+ }
+ } while (index < limit);
+ } else {
+ lock.notifyAll();
+ }
+ }
+ return my_index;
+ }
+}
diff --git a/test/626-const-class-linking/src/RacyMisbehavingHelper.java b/test/626-const-class-linking/src/RacyMisbehavingHelper.java
new file mode 100644
index 0000000..4525278
--- /dev/null
+++ b/test/626-const-class-linking/src/RacyMisbehavingHelper.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Method;
+
+public class RacyMisbehavingHelper {
+ public static ClassPair get() {
+ Class<?> helper1_class = Helper1.class;
+ Class<?> test_class = Test.class;
+ try {
+ // After loading the correct class, allow loading the incorrect class.
+ ClassLoader loader = helper1_class.getClassLoader();
+ Method reportAfterLoading = loader.getClass().getDeclaredMethod("reportAfterLoading");
+ reportAfterLoading.invoke(loader);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return new ClassPair(helper1_class, test_class);
+ }
+}
diff --git a/test/626-const-class-linking/src/RacyMisbehavingLoader.java b/test/626-const-class-linking/src/RacyMisbehavingLoader.java
new file mode 100644
index 0000000..f5bcb4c
--- /dev/null
+++ b/test/626-const-class-linking/src/RacyMisbehavingLoader.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class RacyMisbehavingLoader extends DefiningLoader {
+ static {
+ // For JVM, register as parallel capable.
+ // Android treats all class loaders as parallel capable and makes this a no-op.
+ registerAsParallelCapable();
+ }
+
+ private Object lock = new Object();
+ private int index = 0;
+ private int count;
+ private boolean throw_error;
+
+ private DefiningLoader[] defining_loaders;
+
+ public RacyMisbehavingLoader(ClassLoader parent, int count, boolean throw_error) {
+ super(parent);
+ this.count = count;
+ this.throw_error = throw_error;
+ defining_loaders = new DefiningLoader[2];
+ for (int i = 0; i != defining_loaders.length; ++i) {
+ defining_loaders[i] = new DefiningLoader(parent);
+ }
+ }
+
+ public void reportAfterLoading() {
+ synchronized (lock) {
+ ++index;
+ if (index == 2 * count) {
+ lock.notifyAll();
+ }
+ }
+ }
+
+ protected Class<?> findClass(String name) throws ClassNotFoundException
+ {
+ if (name.equals("Test")) {
+ throw new Error("Unexpected RacyLoader.findClass(\"" + name + "\")");
+ }
+ return super.findClass(name);
+ }
+
+ protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ if (name.equals("Test")) {
+ int my_index = syncWithOtherInstances(count);
+ Class<?> result;
+ if ((my_index & 1) == 0) {
+ // Do not delay loading the correct class.
+ result = defining_loaders[my_index & 1].loadClass(name, resolve);
+ } else {
+ // Delay loading the wrong class.
+ syncWithOtherInstances(2 * count);
+ if (throw_error) {
+ throw new Error("RacyMisbehavingLoader throw_error=true");
+ }
+ result = defining_loaders[my_index & 1].loadClass("Test3", resolve);
+ }
+ return result;
+ }
+ return super.loadClass(name, resolve);
+ }
+
+ private int syncWithOtherInstances(int limit) {
+ int my_index;
+ synchronized (lock) {
+ my_index = index;
+ ++index;
+ if (index != limit) {
+ do {
+ try {
+ lock.wait();
+ } catch (InterruptedException ie) {
+ throw new Error(ie);
+ }
+ } while (index < limit);
+ } else {
+ lock.notifyAll();
+ }
+ }
+ return my_index;
+ }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 44c64c1..2625f56 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -314,6 +314,7 @@
"595-profile-saving/profile-saving.cc",
"596-app-images/app_images.cc",
"597-deopt-new-string/deopt.cc",
+ "626-const-class-linking/clear_dex_cache_types.cc",
],
shared_libs: [
"libbacktrace",
diff --git a/test/etc/default-build b/test/etc/default-build
index f2b5078..51ae175 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -273,8 +273,10 @@
fi
# Create a single jar with two dex files for multidex.
-if [ ${HAS_SRC_MULTIDEX} = "true" ] || [ ${HAS_SMALI_MULTIDEX} = "true" ]; then
- zip $TEST_NAME.jar classes.dex classes2.dex
-elif [ ${NEED_DEX} = "true" ]; then
- zip $TEST_NAME.jar classes.dex
+if [ ${NEED_DEX} = "true" ]; then
+ if [ ${HAS_SRC_MULTIDEX} = "true" ] || [ ${HAS_SMALI_MULTIDEX} = "true" ]; then
+ zip $TEST_NAME.jar classes.dex classes2.dex
+ else
+ zip $TEST_NAME.jar classes.dex
+ fi
fi
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index bb3a3ad..f0abb44 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -316,7 +316,8 @@
if [ "$USE_JVM" = "y" ]; then
export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64
# Xmx is necessary since we don't pass down the ART flags to JVM.
- cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes ${FLAGS} $MAIN $@ ${ARGS}"
+ # We pass the classes2 path whether it's used (src-multidex) or not.
+ cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 ${FLAGS} $MAIN $@ ${ARGS}"
if [ "$DEV_MODE" = "y" ]; then
echo $cmdline
fi