summaryrefslogtreecommitdiff
path: root/test/1980-obsolete-object-cleared/src/Main.java
diff options
context:
space:
mode:
Diffstat (limited to 'test/1980-obsolete-object-cleared/src/Main.java')
-rw-r--r--test/1980-obsolete-object-cleared/src/Main.java304
1 files changed, 304 insertions, 0 deletions
diff --git a/test/1980-obsolete-object-cleared/src/Main.java b/test/1980-obsolete-object-cleared/src/Main.java
new file mode 100644
index 0000000000..514defc236
--- /dev/null
+++ b/test/1980-obsolete-object-cleared/src/Main.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2017 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 art.*;
+import java.lang.ref.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.function.Consumer;
+import sun.misc.Unsafe;
+
+public class Main {
+ public static class Transform {
+ static {
+ }
+
+ public static Object SECRET_ARRAY = new byte[] {1, 2, 3, 4};
+ public static long SECRET_NUMBER = 42;
+
+ public static void foo() {}
+ }
+
+ /* Base64 for
+ * public static class Trasform {
+ * static {}
+ * public static Object AAA_PADDING;
+ * public static Object SECRET_ARRAY;
+ * public static long SECRET_NUMBER;
+ * public static void foo() {}
+ * public static void bar() {}
+ * }
+ */
+ public static final byte[] REDEFINED_DEX_FILE =
+ Base64.getDecoder()
+ .decode(
+ "ZGV4CjAzNQDdmsOAlizFD4Ogb6+/mfSdVzhmL8e/mRcYBAAAcAAAAHhWNBIAAAAAAAAAAGADAAAU"
+ + "AAAAcAAAAAcAAADAAAAAAQAAANwAAAADAAAA6AAAAAUAAAAAAQAAAQAAACgBAADQAgAASAEAAKwB"
+ + "AAC2AQAAvgEAAMsBAADOAQAA4AEAAOgBAAAMAgAALAIAAEACAABLAgAAWQIAAGgCAABzAgAAdgIA"
+ + "AIMCAACIAgAAjQIAAJMCAACaAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAADQAAAA0AAAAGAAAA"
+ + "AAAAAAEABQACAAAAAQAFAAoAAAABAAAACwAAAAEAAAAAAAAAAQAAAAEAAAABAAAADwAAAAEAAAAQ"
+ + "AAAABQAAAAEAAAABAAAAAQAAAAUAAAAAAAAACQAAAFADAAAhAwAAAAAAAAAAAAAAAAAAmgEAAAEA"
+ + "AAAOAAAAAQABAAEAAACeAQAABAAAAHAQBAAAAA4AAAAAAAAAAACiAQAAAQAAAA4AAAAAAAAAAAAA"
+ + "AKYBAAABAAAADgAHAA4ABgAOAAsADgAKAA4AAAAIPGNsaW5pdD4ABjxpbml0PgALQUFBX1BBRERJ"
+ + "TkcAAUoAEExNYWluJFRyYW5zZm9ybTsABkxNYWluOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv"
+ + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09i"
+ + "amVjdDsACU1haW4uamF2YQAMU0VDUkVUX0FSUkFZAA1TRUNSRVRfTlVNQkVSAAlUcmFuc2Zvcm0A"
+ + "AVYAC2FjY2Vzc0ZsYWdzAANiYXIAA2ZvbwAEbmFtZQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9u"
+ + "LW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiYTgzNTJmMjU0ODg1MzYyY2NkOGQ5"
+ + "MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYuMjAtZGV2In0AAgMBEhgCAgQCDgQJ"
+ + "ERcMAwAEAAAJAQkBCQCIgATIAgGBgATcAgEJ9AIBCYgDAAAAAAACAAAAEgMAABgDAABEAwAAAAAA"
+ + "AAAAAAAAAAAADwAAAAAAAAABAAAAAAAAAAEAAAAUAAAAcAAAAAIAAAAHAAAAwAAAAAMAAAABAAAA"
+ + "3AAAAAQAAAADAAAA6AAAAAUAAAAFAAAAAAEAAAYAAAABAAAAKAEAAAEgAAAEAAAASAEAAAMgAAAE"
+ + "AAAAmgEAAAIgAAAUAAAArAEAAAQgAAACAAAAEgMAAAAgAAABAAAAIQMAAAMQAAACAAAAQAMAAAYg"
+ + "AAABAAAAUAMAAAAQAAABAAAAYAMAAA==");
+
+ private interface TConsumer<T> {
+ public void accept(T t) throws Exception;
+ }
+
+ private interface ResetIterator<T> extends Iterator<T> {
+ public void reset();
+ }
+
+ private static final class BaseResetIter implements ResetIterator<Object[]> {
+ private boolean have_next = true;
+
+ public Object[] next() {
+ if (have_next) {
+ have_next = false;
+ return new Object[0];
+ } else {
+ throw new NoSuchElementException("only one element");
+ }
+ }
+
+ public boolean hasNext() {
+ return have_next;
+ }
+
+ public void reset() {
+ have_next = true;
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+
+ // Get the Unsafe object.
+ Field f = Unsafe.class.getDeclaredField("THE_ONE");
+ f.setAccessible(true);
+ Unsafe u = (Unsafe) f.get(null);
+
+ // Get the offsets into the original Transform class of the fields
+ long off_secret_array = genericFieldOffset(Transform.class.getDeclaredField("SECRET_ARRAY"));
+ long off_secret_number = genericFieldOffset(Transform.class.getDeclaredField("SECRET_NUMBER"));
+
+ System.out.println("Reading normally.");
+ System.out.println("\tOriginal secret number is: " + Transform.SECRET_NUMBER);
+ System.out.println("\tOriginal secret array is: " + Arrays.toString((byte[])Transform.SECRET_ARRAY));
+ System.out.println("Using unsafe to access values directly from memory.");
+ System.out.println(
+ "\tOriginal secret number is: " + u.getLong(Transform.class, off_secret_number));
+ System.out.println(
+ "\tOriginal secret array is: "
+ + Arrays.toString((byte[]) u.getObject(Transform.class, off_secret_array)));
+
+ // Redefine in a way that changes the offsets.
+ Redefinition.doCommonStructuralClassRedefinition(Transform.class, REDEFINED_DEX_FILE);
+
+ // Make sure the value is the same.
+ System.out.println("Reading normally post redefinition.");
+ System.out.println("\tPost-redefinition secret number is: " + Transform.SECRET_NUMBER);
+ System.out.println("\tPost-redefinition secret array is: " + Arrays.toString((byte[])Transform.SECRET_ARRAY));
+
+ // Get the (old) obsolete class from the ClassExt
+ Field ext_field = Class.class.getDeclaredField("extData");
+ ext_field.setAccessible(true);
+ Object ext_data = ext_field.get(Transform.class);
+ Field oc_field = ext_data.getClass().getDeclaredField("obsoleteClass");
+ oc_field.setAccessible(true);
+ Class<?> obsolete_class = (Class<?>) oc_field.get(ext_data);
+
+ // Try reading the fields directly out of memory using unsafe.
+ System.out.println("Obsolete class is: " + obsolete_class);
+ System.out.println("Using unsafe to access obsolete values directly from memory.");
+ System.out.println(
+ "\tObsolete secret number is: " + u.getLong(obsolete_class, off_secret_number));
+ System.out.println(
+ "\tObsolete secret array is: "
+ + Arrays.toString((byte[]) u.getObject(obsolete_class, off_secret_array)));
+
+ // Try calling all the public, non-static methods on the obsolete class. Make sure we cannot get
+ // j.l.r.{Method,Field} objects or instances.
+ TConsumer<Class> cc =
+ (Class c) -> {
+ for (Method m : Class.class.getDeclaredMethods()) {
+ if (Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) {
+ Iterable<Object[]> iter = CollectParameterValues(m, obsolete_class);
+ System.out.println("Calling " + m + " with params: " + iter);
+ for (Object[] arr : iter) {
+ try {
+ System.out.println(
+ m
+ + " on "
+ + safePrint(c)
+ + " with "
+ + deepPrint(arr)
+ + " = "
+ + safePrint(m.invoke(c, arr)));
+ } catch (Throwable e) {
+ System.out.println(
+ m + " with " + deepPrint(arr) + " throws " + safePrint(e) + ": " + safePrint(e.getCause()));
+ }
+ }
+ }
+ }
+ };
+ System.out.println("\n\nUsing obsolete class object!\n\n");
+ cc.accept(obsolete_class);
+ System.out.println("\n\nUsing non-obsolete class object!\n\n");
+ cc.accept(Transform.class);
+ }
+
+ public static Iterable<Object[]> CollectParameterValues(Method m, Class<?> obsolete_class) throws Exception {
+ Class<?>[] types = m.getParameterTypes();
+ final Object[][] params = new Object[types.length][];
+ for (int i = 0; i < types.length; i++) {
+ if (types[i].equals(Class.class)) {
+ params[i] =
+ new Object[] {
+ null, Object.class, obsolete_class, Transform.class, Long.TYPE, Class.class
+ };
+ } else if (types[i].equals(Boolean.TYPE)) {
+ params[i] = new Object[] {Boolean.TRUE, Boolean.FALSE};
+ } else if (types[i].equals(String.class)) {
+ params[i] = new Object[] {"NOT_USED_STRING", "foo", "SECRET_ARRAY"};
+ } else if (types[i].equals(Object.class)) {
+ params[i] = new Object[] {null, "foo", "NOT_USED_STRING", Transform.class};
+ } else if (types[i].isArray()) {
+ params[i] = new Object[] {new Object[0], new Class[0], null};
+ } else {
+ throw new Exception("Unknown type " + types[i] + " at " + i + " in " + m);
+ }
+ }
+ // Build the reset-iter.
+ ResetIterator<Object[]> iter = new BaseResetIter();
+ for (int i = params.length - 1; i >= 0; i--) {
+ iter = new ComboIter(Arrays.asList(params[i]), iter);
+ }
+ final Iterator<Object[]> fiter = iter;
+ // Wrap in an iterator with a useful toString method.
+ return new Iterable<Object[]>() {
+ public Iterator<Object[]> iterator() { return fiter; }
+ public String toString() { return deepPrint(params); }
+ };
+ }
+
+ public static String deepPrint(Object[] o) {
+ return Arrays.toString(
+ Arrays.stream(o)
+ .map(
+ (x) -> {
+ if (x == null) {
+ return "null";
+ } else if (x.getClass().isArray()) {
+ if (((Object[]) x).length == 0) {
+ return "new " + x.getClass().getComponentType().getName() + "[0]";
+ } else {
+ return deepPrint((Object[]) x);
+ }
+ } else {
+ return safePrint(x);
+ }
+ })
+ .toArray());
+ }
+
+ public static String safePrint(Object o) {
+ if (o instanceof ClassLoader) {
+ return o.getClass().getName();
+ } else if (o == null) {
+ return "null";
+ } else if (o instanceof Exception) {
+ String res = o.toString();
+ if (res.endsWith("-transformed)")) {
+ res = res.substring(0, res.lastIndexOf(" ")) + " <transformed-jar>)";
+ } else if (res.endsWith(".jar)")) {
+ res = res.substring(0, res.lastIndexOf(" ")) + " <original-jar>)";
+ }
+ return res;
+ } else if (o instanceof Transform) {
+ return "Transform Instance";
+ } else if (o instanceof Class && isObsoleteObject((Class) o)) {
+ return "(obsolete)" + o.toString();
+ } else if (o.getClass().isArray()) {
+ return Arrays.toString((Object[])o);
+ } else {
+ return o.toString();
+ }
+ }
+
+ private static class ComboIter implements ResetIterator<Object[]> {
+ private ResetIterator<Object[]> next;
+ private Object cur;
+ private boolean first;
+ private Iterator<Object> my_vals;
+ private Iterable<Object> my_vals_reset;
+
+ public Object[] next() {
+ if (!next.hasNext()) {
+ cur = my_vals.next();
+ first = false;
+ if (next != null) {
+ next.reset();
+ }
+ }
+ if (first) {
+ first = false;
+ cur = my_vals.next();
+ }
+ Object[] nv = next.next();
+ Object[] res = new Object[nv.length + 1];
+ res[0] = cur;
+ for (int i = 0; i < nv.length; i++) {
+ res[i + 1] = nv[i];
+ }
+ return res;
+ }
+
+ public boolean hasNext() {
+ return next.hasNext() || my_vals.hasNext();
+ }
+
+ public void reset() {
+ my_vals = my_vals_reset.iterator();
+ next.reset();
+ cur = null;
+ first = true;
+ }
+
+ public ComboIter(Iterable<Object> this_reset, ResetIterator<Object[]> next_reset) {
+ my_vals_reset = this_reset;
+ next = next_reset;
+ reset();
+ }
+ }
+
+ public static native long genericFieldOffset(Field f);
+
+ public static native boolean isObsoleteObject(Class c);
+}