diff options
Diffstat (limited to 'test/1980-obsolete-object-cleared/src/Main.java')
-rw-r--r-- | test/1980-obsolete-object-cleared/src/Main.java | 304 |
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); +} |