1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package art;
18 
19 import java.lang.invoke.MethodHandle;
20 import java.lang.invoke.MethodHandles;
21 import java.lang.ref.*;
22 import java.lang.reflect.*;
23 import java.util.*;
24 
25 public class Test1975 {
run()26   public static void run() throws Exception {
27     Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
28     doTest();
29   }
30 
31   private static final boolean PRINT_NONDETERMINISTIC = false;
32 
33   public static WeakHashMap<Object, Long> id_nums = new WeakHashMap<>();
34   public static long next_id = 0;
35 
printGeneric(Object o)36   public static String printGeneric(Object o) {
37     Long id = id_nums.get(o);
38     if (id == null) {
39       id = Long.valueOf(next_id++);
40       id_nums.put(o, id);
41     }
42     if (o == null) {
43       return "(ID: " + id + ") <NULL>";
44     }
45     Class oc = o.getClass();
46     if (oc.isArray() && oc.getComponentType() == Byte.TYPE) {
47       return "(ID: "
48           + id
49           + ") "
50           + Arrays.toString(Arrays.copyOf((byte[]) o, 10)).replace(']', ',')
51           + " ...]";
52     } else {
53       return "(ID: " + id + ") " + o.toString();
54     }
55   }
56 
57   // Since we are adding fields we redefine this class with the Transform1975 class to add new
58   // field-reads.
59   public static final class ReadTransformFields implements Runnable {
run()60     public void run() {
61       System.out.println("Read CUR_CLASS field: " + printGeneric(Transform1975.CUR_CLASS));
62       System.out.println(
63           "Read REDEFINED_DEX_BYTES field: " + printGeneric(Transform1975.REDEFINED_DEX_BYTES));
64     }
65   }
66 
67   /* Base64 encoded dex file for:
68    * public static final class ReadTransformFields implements Runnable {
69    *   public void run() {
70    *     System.out.println("Read CUR_CLASS field: " + printGeneric(Transform1975.CUR_CLASS));
71    *     System.out.println("Read REDEFINED_DEX_BYTES field: " + printGeneric(Transform1975.REDEFINED_DEX_BYTES));
72    *     System.out.println("Read NEW_STRING field: " + printGeneric(Transform1975.NEW_STRING));
73    *   }
74    * }
75    */
76   private static final byte[] NEW_READ_BYTES =
77       Base64.getDecoder()
78           .decode(
79               "ZGV4CjAzNQCHIfWvfkMos9E+Snhux5rSGhnDAbiVJlyYBgAAcAAAAHhWNBIAAAAAAAAAANQFAAAk"
80                   + "AAAAcAAAAA4AAAAAAQAABQAAADgBAAAEAAAAdAEAAAgAAACUAQAAAQAAANQBAACkBAAA9AEAAO4C"
81                   + "AAD2AgAAAQMAAAQDAAAIAwAALAMAADwDAABRAwAAdQMAAJUDAACsAwAAvwMAANMDAADpAwAA/QMA"
82                   + "ABgEAAAsBAAAOAQAAE0EAABlBAAAfgQAAKAEAAC1BAAAxAQAAMcEAADLBAAAzwQAANwEAADkBAAA"
83                   + "6gQAAO8EAAD9BAAABgUAAAsFAAAVBQAAHAUAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAAL"
84                   + "AAAADAAAAA0AAAAOAAAADwAAABcAAAAZAAAAAgAAAAkAAAAAAAAAAwAAAAkAAADgAgAAAwAAAAoA"
85                   + "AADoAgAAFwAAAAwAAAAAAAAAGAAAAAwAAADoAgAAAgAGAAEAAAACAAkAEAAAAAIADQARAAAACwAF"
86                   + "AB0AAAAAAAMAAAAAAAAAAwAgAAAAAQABAB4AAAAFAAQAHwAAAAcAAwAAAAAACgADAAAAAAAKAAIA"
87                   + "GwAAAAoAAAAhAAAAAAAAABEAAAAHAAAA2AIAABYAAADEBQAAowUAAAAAAAABAAEAAQAAAMYCAAAE"
88                   + "AAAAcBAEAAAADgAFAAEAAgAAAMoCAABVAAAAYgADAGIBAABxEAIAAQAMASICCgBwEAUAAgAaAxIA"
89                   + "biAGADIAbiAGABIAbhAHAAIADAFuIAMAEABiAAMAYgECAHEQAgABAAwBIgIKAHAQBQACABoDFABu"
90                   + "IAYAMgBuIAYAEgBuEAcAAgAMAW4gAwAQAGIAAwBiAQEAcRACAAEADAEiAgoAcBAFAAIAGgMTAG4g"
91                   + "BgAyAG4gBgASAG4QBwACAAwBbiADABAADgAEAA4ABgAOARwPARwPARwPAAABAAAACAAAAAEAAAAH"
92                   + "AAAAAQAAAAkABjxpbml0PgAJQ1VSX0NMQVNTAAFMAAJMTAAiTGFydC9UZXN0MTk3NSRSZWFkVHJh"
93                   + "bnNmb3JtRmllbGRzOwAOTGFydC9UZXN0MTk3NTsAE0xhcnQvVHJhbnNmb3JtMTk3NTsAIkxkYWx2"
94                   + "aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNs"
95                   + "YXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABFMamF2YS9sYW5nL0NsYXNzOwASTGphdmEvbGFu"
96                   + "Zy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2"
97                   + "YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsACk5FV19TVFJJTkcAE1JF"
98                   + "REVGSU5FRF9ERVhfQllURVMAFlJlYWQgQ1VSX0NMQVNTIGZpZWxkOiAAF1JlYWQgTkVXX1NUUklO"
99                   + "RyBmaWVsZDogACBSZWFkIFJFREVGSU5FRF9ERVhfQllURVMgZmllbGQ6IAATUmVhZFRyYW5zZm9y"
100                   + "bUZpZWxkcwANVGVzdDE5NzUuamF2YQABVgACVkwAAltCAAthY2Nlc3NGbGFncwAGYXBwZW5kAARu"
101                   + "YW1lAANvdXQADHByaW50R2VuZXJpYwAHcHJpbnRsbgADcnVuAAh0b1N0cmluZwAFdmFsdWUAdn5+"
102                   + "RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiYTgzNTJm"
103                   + "MjU0ODg1MzYyY2NkOGQ5MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYuMjAtZGV2"
104                   + "In0AAgMBIhgBAgQCGgQZHBcVAAABAQCBgAT0AwEBjAQAAAAAAAAAAgAAAJQFAACaBQAAuAUAAAAA"
105                   + "AAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAJAAAAHAAAAACAAAADgAAAAABAAADAAAABQAA"
106                   + "ADgBAAAEAAAABAAAAHQBAAAFAAAACAAAAJQBAAAGAAAAAQAAANQBAAABIAAAAgAAAPQBAAADIAAA"
107                   + "AgAAAMYCAAABEAAAAwAAANgCAAACIAAAJAAAAO4CAAAEIAAAAgAAAJQFAAAAIAAAAQAAAKMFAAAD"
108                   + "EAAAAgAAALQFAAAGIAAAAQAAAMQFAAAAEAAAAQAAANQFAAA=");
109 
ReadFields()110   static void ReadFields() throws Exception {
111     Runnable r = new ReadTransformFields();
112     System.out.println("Reading with reflection.");
113     for (Field f : Transform1975.class.getFields()) {
114       System.out.println(f.toString() + " = " + printGeneric(f.get(null)));
115     }
116     System.out.println("Reading normally in same class.");
117     Transform1975.readFields();
118     System.out.println("Reading with native.");
119     readNativeFields(Transform1975.class, getNativeFields(Transform1975.class.getFields()));
120     System.out.println("Reading normally in other class.");
121     r.run();
122     System.out.println("Reading using method handles.");
123     readMethodHandles(getMethodHandles(Transform1975.class.getFields()));
124     System.out.println("Doing modification maybe");
125     Transform1975.doSomething();
126     System.out.println("Reading with reflection after possible modification.");
127     for (Field f : Transform1975.class.getFields()) {
128       System.out.println(f.toString() + " = " + printGeneric(f.get(null)));
129     }
130     System.out.println("Reading normally in same class after possible modification.");
131     Transform1975.readFields();
132     System.out.println("Reading with native after possible modification.");
133     readNativeFields(Transform1975.class, getNativeFields(Transform1975.class.getFields()));
134     System.out.println("Reading normally in other class after possible modification.");
135     r.run();
136     System.out.println("Reading using method handles.");
137     readMethodHandles(getMethodHandles(Transform1975.class.getFields()));
138   }
139 
140   public static final class MethodHandleWrapper {
141     private MethodHandle mh;
142     private Field f;
MethodHandleWrapper(MethodHandle mh, Field f)143     public MethodHandleWrapper(MethodHandle mh, Field f) {
144       this.f = f;
145       this.mh = mh;
146     }
getHandle()147     public MethodHandle getHandle() {
148       return mh;
149     }
getField()150     public Field getField() {
151       return f;
152     }
invoke()153     public Object invoke() throws Throwable {
154       return mh.invoke();
155     }
toString()156     public String toString() {
157       return mh.toString();
158     }
159   }
160 
getMethodHandles(Field[] fields)161   public static MethodHandleWrapper[] getMethodHandles(Field[] fields) throws Exception {
162     final MethodHandles.Lookup l = MethodHandles.lookup();
163     MethodHandleWrapper[] res = new MethodHandleWrapper[fields.length];
164     for (int i = 0; i < res.length; i++) {
165       res[i] = new MethodHandleWrapper(l.unreflectGetter(fields[i]), fields[i]);;
166     }
167     return res;
168   }
169 
readMethodHandles(MethodHandleWrapper[] handles)170   public static void readMethodHandles(MethodHandleWrapper[] handles) throws Exception {
171     for (MethodHandleWrapper h : handles) {
172       try {
173         System.out.println(printGeneric(h) + " (" + h.getField() + ") = " + printGeneric(h.invoke()));
174       } catch (Throwable t) {
175         if (t instanceof Exception) {
176           throw (Exception)t;
177         } else if (t instanceof Error) {
178           throw (Error)t;
179         } else {
180           throw new RuntimeException("Unexpected throwable thrown!", t);
181         }
182       }
183     }
184   }
doTest()185   public static void doTest() throws Exception {
186     // TODO It would be good to have a test of invoke-custom too but since that requires smali and
187     // internally we just store the resolved MethodHandle this should all be good enough.
188 
189     // Grab Field objects from before the transformation.
190     Field[] old_fields = Transform1975.class.getFields();
191     for (Field f : old_fields) {
192       System.out.println("Saving Field object " + printGeneric(f) + " for later");
193     }
194     // Grab jfieldIDs from before the transformation.
195     long[] old_native_fields = getNativeFields(Transform1975.class.getFields());
196     // Grab MethodHandles from before the transformation.
197     MethodHandleWrapper[] handles = getMethodHandles(Transform1975.class.getFields());
198     for (MethodHandleWrapper h : handles) {
199       System.out.println("Saving MethodHandle object " + printGeneric(h) + " for later");
200     }
201     // Grab a 'setter' MethodHandle from before the redefinition.
202     Field cur_class_field = Transform1975.class.getDeclaredField("CUR_CLASS");
203     MethodHandleWrapper write_wrapper = new MethodHandleWrapper(MethodHandles.lookup().unreflectSetter(cur_class_field), cur_class_field);
204     System.out.println("Saving writable MethodHandle " + printGeneric(write_wrapper) + " for later");
205 
206     // Read the fields in all possible ways.
207     System.out.println("Reading fields before redefinition");
208     ReadFields();
209     // Redefine the transform class. Also change the ReadTransformFields so we don't have to deal
210     // with annoying compilation stuff.
211     Redefinition.doCommonStructuralClassRedefinition(
212         Transform1975.class, Transform1975.REDEFINED_DEX_BYTES);
213     Redefinition.doCommonClassRedefinition(
214         ReadTransformFields.class, new byte[] {}, NEW_READ_BYTES);
215     // Read the fields in all possible ways.
216     System.out.println("Reading fields after redefinition");
217     ReadFields();
218     // Check that the old Field, jfieldID, and MethodHandle objects were updated.
219     System.out.println("reading reflectively with old reflection objects");
220     for (Field f : old_fields) {
221       System.out.println("OLD FIELD OBJECT: " + f.toString() + " = " + printGeneric(f.get(null)));
222     }
223     System.out.println("reading natively with old jfieldIDs");
224     readNativeFields(Transform1975.class, old_native_fields);
225     // Make sure the fields keep the same id.
226     System.out.println("reading natively with new jfieldIDs");
227     long[] new_fields = getNativeFields(Transform1975.class.getFields());
228     Arrays.sort(old_native_fields);
229     Arrays.sort(new_fields);
230     boolean different = new_fields.length == old_native_fields.length;
231     for (int i = 0; i < old_native_fields.length && !different; i++) {
232       different = different || new_fields[i] != old_native_fields[i];
233     }
234     if (different) {
235       System.out.println(
236           "Missing expected fields! "
237               + Arrays.toString(new_fields)
238               + " vs "
239               + Arrays.toString(old_native_fields));
240     }
241     // Make sure the old handles work.
242     System.out.println("Reading with old method handles");
243     readMethodHandles(handles);
244     System.out.println("Reading with new method handles");
245     readMethodHandles(getMethodHandles(Transform1975.class.getFields()));
246     System.out.println("Writing " + printGeneric(Test1975.class) + " to CUR_CLASS with old method handle");
247     try {
248       write_wrapper.getHandle().invokeExact(Test1975.class);
249     } catch (Throwable t) {
250       throw new RuntimeException("something threw", t);
251     }
252     System.out.println("Reading changed value");
253     System.out.println("CUR_CLASS is now " + printGeneric(Transform1975.CUR_CLASS));
254   }
255 
printNativeField(long id, Field f, Object value)256   private static void printNativeField(long id, Field f, Object value) {
257     System.out.println(
258         "Field" + (PRINT_NONDETERMINISTIC ? " " + id : "") + " " + f + " = " + printGeneric(value));
259   }
260 
getNativeFields(Field[] fields)261   public static native long[] getNativeFields(Field[] fields);
262 
readNativeFields(Class<?> field_class, long[] sfields)263   public static native void readNativeFields(Class<?> field_class, long[] sfields);
264 }
265