1 /*
2  * Copyright (C) 2017 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 static art.Redefinition.CommonClassDefinition;
20 
21 import java.util.Arrays;
22 import java.util.ArrayList;
23 import java.util.Base64;
24 import java.lang.reflect.*;
25 public class Test944 {
26 
27   static class Transform {
sayHi()28     public void sayHi() {
29       System.out.println("hello");
30     }
31   }
32 
33   static class Transform2 {
sayHi()34     public void sayHi() {
35       System.out.println("hello2");
36     }
37   }
38 
39   /**
40    * base64 encoded class/dex file for
41    * static class Transform {
42    *   public void sayHi() {
43    *    System.out.println("Goodbye");
44    *   }
45    * }
46    */
47   private static CommonClassDefinition TRANSFORM_DEFINITION = new CommonClassDefinition(
48       Transform.class,
49       Base64.getDecoder().decode(
50         "yv66vgAAADQAIAoABgAOCQAPABAIABEKABIAEwcAFQcAGAEABjxpbml0PgEAAygpVgEABENvZGUB" +
51         "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAAxUZXN0OTQ0LmphdmEMAAcA" +
52         "CAcAGQwAGgAbAQAHR29vZGJ5ZQcAHAwAHQAeBwAfAQAVYXJ0L1Rlc3Q5NDQkVHJhbnNmb3JtAQAJ" +
53         "VHJhbnNmb3JtAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9T" +
54         "eXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFt" +
55         "AQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAC2FydC9UZXN0OTQ0ACAABQAGAAAA" +
56         "AAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAACgABAAsACAABAAkA" +
57         "AAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAMAAgADQACAAwAAAACAA0AFwAAAAoA" +
58         "AQAFABQAFgAI"),
59       Base64.getDecoder().decode(
60         "ZGV4CjAzNQCFgsuWAAAAAAAAAAAAAAAAAAAAAAAAAAC4AwAAcAAAAHhWNBIAAAAAAAAAAPQCAAAU" +
61         "AAAAcAAAAAkAAADAAAAAAgAAAOQAAAABAAAA/AAAAAQAAAAEAQAAAQAAACQBAAB0AgAARAEAAEQB" +
62         "AABMAQAAVQEAAG4BAAB9AQAAoQEAAMEBAADYAQAA7AEAAAACAAAUAgAAIgIAAC0CAAAwAgAANAIA" +
63         "AEECAABHAgAATAIAAFUCAABcAgAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAA" +
64         "DAAAAAgAAAAAAAAADQAAAAgAAABkAgAABwAEABAAAAAAAAAAAAAAAAAAAAASAAAABAABABEAAAAF" +
65         "AAAAAAAAAAAAAAAAAAAABQAAAAAAAAAKAAAA5AIAALgCAAAAAAAABjxpbml0PgAHR29vZGJ5ZQAX" +
66         "TGFydC9UZXN0OTQ0JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk0NDsAIkxkYWx2aWsvYW5ub3RhdGlv" +
67         "bi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEv" +
68         "aW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAS" +
69         "TGphdmEvbGFuZy9TeXN0ZW07AAxUZXN0OTQ0LmphdmEACVRyYW5zZm9ybQABVgACVkwAC2FjY2Vz" +
70         "c0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhpAAV2YWx1ZQAAAQAAAAYAAAAKAAcOAAwA" +
71         "Bw4BCA8AAAAAAQABAAEAAABsAgAABAAAAHAQAwAAAA4AAwABAAIAAABxAgAACQAAAGIAAAAbAQEA" +
72         "AABuIAIAEAAOAAAAAAABAQCAgAT8BAEBlAUAAAICARMYAQIDAg4ECA8XCwACAAAAyAIAAM4CAADY" +
73         "AgAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAUAAAAcAAAAAIAAAAJAAAAwAAAAAMA" +
74         "AAACAAAA5AAAAAQAAAABAAAA/AAAAAUAAAAEAAAABAEAAAYAAAABAAAAJAEAAAIgAAAUAAAARAEA" +
75         "AAEQAAABAAAAZAIAAAMgAAACAAAAbAIAAAEgAAACAAAAfAIAAAAgAAABAAAAuAIAAAQgAAACAAAA" +
76         "yAIAAAMQAAABAAAA2AIAAAYgAAABAAAA5AIAAAAQAAABAAAA9AIAAA=="));
77 
78   /**
79    * base64 encoded class/dex file for
80    * static class Transform2 {
81    *   public void sayHi() {
82    *    System.out.println("Goodbye2");
83    *   }
84    * }
85    */
86   private static CommonClassDefinition TRANSFORM2_DEFINITION = new CommonClassDefinition(
87       Transform2.class,
88       Base64.getDecoder().decode(
89         "yv66vgAAADQAIAoABgAOCQAPABAIABEKABIAEwcAFQcAGAEABjxpbml0PgEAAygpVgEABENvZGUB" +
90         "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAAxUZXN0OTQ0LmphdmEMAAcA" +
91         "CAcAGQwAGgAbAQAIR29vZGJ5ZTIHABwMAB0AHgcAHwEAFmFydC9UZXN0OTQ0JFRyYW5zZm9ybTIB" +
92         "AApUcmFuc2Zvcm0yAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFu" +
93         "Zy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3Ry" +
94         "ZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAC2FydC9UZXN0OTQ0ACAABQAG" +
95         "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAABQABAAsACAAB" +
96         "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAHAAgACAACAAwAAAACAA0AFwAA" +
97         "AAoAAQAFABQAFgAI"),
98       Base64.getDecoder().decode(
99         "ZGV4CjAzNQAUg8BCAAAAAAAAAAAAAAAAAAAAAAAAAAC8AwAAcAAAAHhWNBIAAAAAAAAAAPgCAAAU" +
100         "AAAAcAAAAAkAAADAAAAAAgAAAOQAAAABAAAA/AAAAAQAAAAEAQAAAQAAACQBAAB4AgAARAEAAEQB" +
101         "AABMAQAAVgEAAHABAAB/AQAAowEAAMMBAADaAQAA7gEAAAICAAAWAgAAJAIAADACAAAzAgAANwIA" +
102         "AEQCAABKAgAATwIAAFgCAABfAgAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAA" +
103         "DAAAAAgAAAAAAAAADQAAAAgAAABoAgAABwAEABAAAAAAAAAAAAAAAAAAAAASAAAABAABABEAAAAF" +
104         "AAAAAAAAAAAAAAAAAAAABQAAAAAAAAAKAAAA6AIAALwCAAAAAAAABjxpbml0PgAIR29vZGJ5ZTIA" +
105         "GExhcnQvVGVzdDk0NCRUcmFuc2Zvcm0yOwANTGFydC9UZXN0OTQ0OwAiTGRhbHZpay9hbm5vdGF0" +
106         "aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2" +
107         "YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7" +
108         "ABJMamF2YS9sYW5nL1N5c3RlbTsADFRlc3Q5NDQuamF2YQAKVHJhbnNmb3JtMgABVgACVkwAC2Fj" +
109         "Y2Vzc0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhpAAV2YWx1ZQAAAAEAAAAGAAAABQAH" +
110         "DgAHAAcOAQgPAAAAAAEAAQABAAAAcAIAAAQAAABwEAMAAAAOAAMAAQACAAAAdQIAAAkAAABiAAAA" +
111         "GwEBAAAAbiACABAADgAAAAAAAQEAgIAEgAUBAZgFAAACAgETGAECAwIOBAgPFwsAAgAAAMwCAADS" +
112         "AgAA3AIAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAFAAAAHAAAAACAAAACQAAAMAA" +
113         "AAADAAAAAgAAAOQAAAAEAAAAAQAAAPwAAAAFAAAABAAAAAQBAAAGAAAAAQAAACQBAAACIAAAFAAA" +
114         "AEQBAAABEAAAAQAAAGgCAAADIAAAAgAAAHACAAABIAAAAgAAAIACAAAAIAAAAQAAALwCAAAEIAAA" +
115         "AgAAAMwCAAADEAAAAQAAANwCAAAGIAAAAQAAAOgCAAAAEAAAAQAAAPgCAAA="));
116 
run()117   public static void run() throws Exception {
118     Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
119     doTest();
120     System.out.println("Passed");
121   }
122 
checkIsInstance(Class<?> klass, Object o)123   private static void checkIsInstance(Class<?> klass, Object o) throws Exception {
124     if (!klass.isInstance(o)) {
125       throw new Exception(klass + " is not the class of " + o);
126     }
127   }
128 
arrayContains(long[] arr, long value)129   private static boolean arrayContains(long[] arr, long value) {
130     if (arr == null) {
131       return false;
132     }
133     for (int i = 0; i < arr.length; i++) {
134       if (arr[i] == value) {
135         return true;
136       }
137     }
138     return false;
139   }
140 
141   /**
142    * Checks that we can find the dex-file for the given class in its classloader.
143    *
144    * Throws if it fails.
145    */
checkDexFileInClassLoader(Class<?> klass)146   private static void checkDexFileInClassLoader(Class<?> klass) throws Exception {
147     // If all the android BCP classes were availible when compiling this test and access checks
148     // weren't a thing this function would be written as follows:
149     //
150     // long dexFilePtr = getDexFilePointer(klass);
151     // dalvik.system.BaseDexClassLoader loader =
152     //     (dalvik.system.BaseDexClassLoader)klass.getClassLoader();
153     // dalvik.system.DexPathList pathListValue = loader.pathList;
154     // dalvik.system.DexPathList.Element[] elementArrayValue = pathListValue.dexElements;
155     // int array_length = elementArrayValue.length;
156     // for (int i = 0; i < array_length; i++) {
157     //   dalvik.system.DexPathList.Element curElement = elementArrayValue[i];
158     //   dalvik.system.DexFile curDexFile = curElement.dexFile;
159     //   if (curDexFile == null) {
160     //     continue;
161     //   }
162     //   long[] curCookie = (long[])curDexFile.mCookie;
163     //   long[] curInternalCookie = (long[])curDexFile.mInternalCookie;
164     //   if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) {
165     //     return;
166     //   }
167     // }
168     // throw new Exception(
169     //     "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass);
170 
171     // Get all the fields and classes we need by reflection.
172     Class<?> baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
173     Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
174 
175     Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
176     Field elementArrayField = dexPathListClass.getDeclaredField("dexElements");
177 
178     Class<?> dexPathListElementClass = Class.forName("dalvik.system.DexPathList$Element");
179     Field dexFileField = dexPathListElementClass.getDeclaredField("dexFile");
180 
181     Class<?> dexFileClass = Class.forName("dalvik.system.DexFile");
182     Field dexFileCookieField = dexFileClass.getDeclaredField("mCookie");
183     Field dexFileInternalCookieField = dexFileClass.getDeclaredField("mInternalCookie");
184 
185     // Make all the fields accessible
186     AccessibleObject.setAccessible(new AccessibleObject[] { pathListField,
187                                                             elementArrayField,
188                                                             dexFileField,
189                                                             dexFileCookieField,
190                                                             dexFileInternalCookieField }, true);
191 
192     long dexFilePtr = getDexFilePointer(klass);
193 
194     ClassLoader loader = klass.getClassLoader();
195     checkIsInstance(baseDexClassLoaderClass, loader);
196     // DexPathList pathListValue = ((BaseDexClassLoader) loader).pathList;
197     Object pathListValue = pathListField.get(loader);
198 
199     checkIsInstance(dexPathListClass, pathListValue);
200 
201     // DexPathList.Element[] elementArrayValue = pathListValue.dexElements;
202     Object elementArrayValue = elementArrayField.get(pathListValue);
203     if (!elementArrayValue.getClass().isArray() ||
204         elementArrayValue.getClass().getComponentType() != dexPathListElementClass) {
205       throw new Exception("elementArrayValue is not an " + dexPathListElementClass + " array!");
206     }
207     // int array_length = elementArrayValue.length;
208     int array_length = Array.getLength(elementArrayValue);
209     for (int i = 0; i < array_length; i++) {
210       // DexPathList.Element curElement = elementArrayValue[i];
211       Object curElement = Array.get(elementArrayValue, i);
212       checkIsInstance(dexPathListElementClass, curElement);
213 
214       // DexFile curDexFile = curElement.dexFile;
215       Object curDexFile = dexFileField.get(curElement);
216       if (curDexFile == null) {
217         continue;
218       }
219       checkIsInstance(dexFileClass, curDexFile);
220 
221       // long[] curCookie = (long[])curDexFile.mCookie;
222       long[] curCookie = (long[])dexFileCookieField.get(curDexFile);
223       // long[] curInternalCookie = (long[])curDexFile.mInternalCookie;
224       long[] curInternalCookie = (long[])dexFileInternalCookieField.get(curDexFile);
225 
226       if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) {
227         return;
228       }
229     }
230     throw new Exception(
231         "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass);
232   }
233 
doTest()234   private static void doTest() throws Exception {
235     Transform t = new Transform();
236     Transform2 t2 = new Transform2();
237 
238     long initial_t1_dex = getDexFilePointer(Transform.class);
239     long initial_t2_dex = getDexFilePointer(Transform2.class);
240     if (initial_t2_dex != initial_t1_dex) {
241       throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " +
242                           "have different initial dex files!");
243     }
244     checkDexFileInClassLoader(Transform.class);
245     checkDexFileInClassLoader(Transform2.class);
246 
247     // Make sure they are loaded
248     t.sayHi();
249     t2.sayHi();
250     // Redefine both of the classes.
251     Redefinition.doMultiClassRedefinition(TRANSFORM_DEFINITION, TRANSFORM2_DEFINITION);
252     // Make sure we actually transformed them!
253     t.sayHi();
254     t2.sayHi();
255 
256     long final_t1_dex = getDexFilePointer(Transform.class);
257     long final_t2_dex = getDexFilePointer(Transform2.class);
258     if (final_t2_dex == final_t1_dex) {
259       throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " +
260                           "have the same initial dex files!");
261     } else if (final_t1_dex == initial_t1_dex) {
262       throw new Exception("The class " + Transform.class + " did not get a new dex file!");
263     } else if (final_t2_dex == initial_t2_dex) {
264       throw new Exception("The class " + Transform2.class + " did not get a new dex file!");
265     }
266     // Check to make sure the new dex files are in the class loader.
267     checkDexFileInClassLoader(Transform.class);
268     checkDexFileInClassLoader(Transform2.class);
269   }
270 
271   // Gets the 'long' (really a native pointer) that is stored in the ClassLoader representing the
272   // DexFile a class is loaded from. This is plucked out of the internal DexCache object associated
273   // with the class.
getDexFilePointer(Class<?> target)274   private static long getDexFilePointer(Class<?> target) throws Exception {
275     // If all the android BCP classes were available when compiling this test and access checks
276     // weren't a thing this function would be written as follows:
277     //
278     // java.lang.DexCache dexCacheObject = target.dexCache;
279     // if (dexCacheObject == null) {
280     //   return 0;
281     // }
282     // return dexCacheObject.dexFile;
283     Field dexCacheField = Class.class.getDeclaredField("dexCache");
284 
285     Class<?> dexCacheClass = Class.forName("java.lang.DexCache");
286     Field dexFileField = dexCacheClass.getDeclaredField("dexFile");
287 
288     AccessibleObject.setAccessible(new AccessibleObject[] { dexCacheField, dexFileField }, true);
289 
290     Object dexCacheObject = dexCacheField.get(target);
291     if (dexCacheObject == null) {
292       return 0;
293     }
294     checkIsInstance(dexCacheClass, dexCacheObject);
295     return dexFileField.getLong(dexCacheObject);
296   }
297 }
298