blob: fe1c024ec4bba99cb5bafa0fea9245751c58d00a [file] [log] [blame]
/*
* 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.
*/
package art;
import static art.Redefinition.CommonClassDefinition;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Base64;
import java.lang.reflect.*;
public class Test944 {
static class Transform {
public void sayHi() {
System.out.println("hello");
}
}
static class Transform2 {
public void sayHi() {
System.out.println("hello2");
}
}
/**
* base64 encoded class/dex file for
* static class Transform {
* public void sayHi() {
* System.out.println("Goodbye");
* }
* }
*/
private static CommonClassDefinition TRANSFORM_DEFINITION = new CommonClassDefinition(
Transform.class,
Base64.getDecoder().decode(
"yv66vgAAADQAIAoABgAOCQAPABAIABEKABIAEwcAFQcAGAEABjxpbml0PgEAAygpVgEABENvZGUB" +
"AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAAxUZXN0OTQ0LmphdmEMAAcA" +
"CAcAGQwAGgAbAQAHR29vZGJ5ZQcAHAwAHQAeBwAfAQAVYXJ0L1Rlc3Q5NDQkVHJhbnNmb3JtAQAJ" +
"VHJhbnNmb3JtAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9T" +
"eXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFt" +
"AQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAC2FydC9UZXN0OTQ0ACAABQAGAAAA" +
"AAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAACgABAAsACAABAAkA" +
"AAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAMAAgADQACAAwAAAACAA0AFwAAAAoA" +
"AQAFABQAFgAI"),
Base64.getDecoder().decode(
"ZGV4CjAzNQCFgsuWAAAAAAAAAAAAAAAAAAAAAAAAAAC4AwAAcAAAAHhWNBIAAAAAAAAAAPQCAAAU" +
"AAAAcAAAAAkAAADAAAAAAgAAAOQAAAABAAAA/AAAAAQAAAAEAQAAAQAAACQBAAB0AgAARAEAAEQB" +
"AABMAQAAVQEAAG4BAAB9AQAAoQEAAMEBAADYAQAA7AEAAAACAAAUAgAAIgIAAC0CAAAwAgAANAIA" +
"AEECAABHAgAATAIAAFUCAABcAgAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAA" +
"DAAAAAgAAAAAAAAADQAAAAgAAABkAgAABwAEABAAAAAAAAAAAAAAAAAAAAASAAAABAABABEAAAAF" +
"AAAAAAAAAAAAAAAAAAAABQAAAAAAAAAKAAAA5AIAALgCAAAAAAAABjxpbml0PgAHR29vZGJ5ZQAX" +
"TGFydC9UZXN0OTQ0JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk0NDsAIkxkYWx2aWsvYW5ub3RhdGlv" +
"bi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEv" +
"aW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAS" +
"TGphdmEvbGFuZy9TeXN0ZW07AAxUZXN0OTQ0LmphdmEACVRyYW5zZm9ybQABVgACVkwAC2FjY2Vz" +
"c0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhpAAV2YWx1ZQAAAQAAAAYAAAAKAAcOAAwA" +
"Bw4BCA8AAAAAAQABAAEAAABsAgAABAAAAHAQAwAAAA4AAwABAAIAAABxAgAACQAAAGIAAAAbAQEA" +
"AABuIAIAEAAOAAAAAAABAQCAgAT8BAEBlAUAAAICARMYAQIDAg4ECA8XCwACAAAAyAIAAM4CAADY" +
"AgAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAUAAAAcAAAAAIAAAAJAAAAwAAAAAMA" +
"AAACAAAA5AAAAAQAAAABAAAA/AAAAAUAAAAEAAAABAEAAAYAAAABAAAAJAEAAAIgAAAUAAAARAEA" +
"AAEQAAABAAAAZAIAAAMgAAACAAAAbAIAAAEgAAACAAAAfAIAAAAgAAABAAAAuAIAAAQgAAACAAAA" +
"yAIAAAMQAAABAAAA2AIAAAYgAAABAAAA5AIAAAAQAAABAAAA9AIAAA=="));
/**
* base64 encoded class/dex file for
* static class Transform2 {
* public void sayHi() {
* System.out.println("Goodbye2");
* }
* }
*/
private static CommonClassDefinition TRANSFORM2_DEFINITION = new CommonClassDefinition(
Transform2.class,
Base64.getDecoder().decode(
"yv66vgAAADQAIAoABgAOCQAPABAIABEKABIAEwcAFQcAGAEABjxpbml0PgEAAygpVgEABENvZGUB" +
"AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAAxUZXN0OTQ0LmphdmEMAAcA" +
"CAcAGQwAGgAbAQAIR29vZGJ5ZTIHABwMAB0AHgcAHwEAFmFydC9UZXN0OTQ0JFRyYW5zZm9ybTIB" +
"AApUcmFuc2Zvcm0yAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFu" +
"Zy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3Ry" +
"ZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAC2FydC9UZXN0OTQ0ACAABQAG" +
"AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAABQABAAsACAAB" +
"AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAHAAgACAACAAwAAAACAA0AFwAA" +
"AAoAAQAFABQAFgAI"),
Base64.getDecoder().decode(
"ZGV4CjAzNQAUg8BCAAAAAAAAAAAAAAAAAAAAAAAAAAC8AwAAcAAAAHhWNBIAAAAAAAAAAPgCAAAU" +
"AAAAcAAAAAkAAADAAAAAAgAAAOQAAAABAAAA/AAAAAQAAAAEAQAAAQAAACQBAAB4AgAARAEAAEQB" +
"AABMAQAAVgEAAHABAAB/AQAAowEAAMMBAADaAQAA7gEAAAICAAAWAgAAJAIAADACAAAzAgAANwIA" +
"AEQCAABKAgAATwIAAFgCAABfAgAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAA" +
"DAAAAAgAAAAAAAAADQAAAAgAAABoAgAABwAEABAAAAAAAAAAAAAAAAAAAAASAAAABAABABEAAAAF" +
"AAAAAAAAAAAAAAAAAAAABQAAAAAAAAAKAAAA6AIAALwCAAAAAAAABjxpbml0PgAIR29vZGJ5ZTIA" +
"GExhcnQvVGVzdDk0NCRUcmFuc2Zvcm0yOwANTGFydC9UZXN0OTQ0OwAiTGRhbHZpay9hbm5vdGF0" +
"aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2" +
"YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7" +
"ABJMamF2YS9sYW5nL1N5c3RlbTsADFRlc3Q5NDQuamF2YQAKVHJhbnNmb3JtMgABVgACVkwAC2Fj" +
"Y2Vzc0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhpAAV2YWx1ZQAAAAEAAAAGAAAABQAH" +
"DgAHAAcOAQgPAAAAAAEAAQABAAAAcAIAAAQAAABwEAMAAAAOAAMAAQACAAAAdQIAAAkAAABiAAAA" +
"GwEBAAAAbiACABAADgAAAAAAAQEAgIAEgAUBAZgFAAACAgETGAECAwIOBAgPFwsAAgAAAMwCAADS" +
"AgAA3AIAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAFAAAAHAAAAACAAAACQAAAMAA" +
"AAADAAAAAgAAAOQAAAAEAAAAAQAAAPwAAAAFAAAABAAAAAQBAAAGAAAAAQAAACQBAAACIAAAFAAA" +
"AEQBAAABEAAAAQAAAGgCAAADIAAAAgAAAHACAAABIAAAAgAAAIACAAAAIAAAAQAAALwCAAAEIAAA" +
"AgAAAMwCAAADEAAAAQAAANwCAAAGIAAAAQAAAOgCAAAAEAAAAQAAAPgCAAA="));
public static void run() throws Exception {
Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
doTest();
System.out.println("Passed");
}
private static void checkIsInstance(Class<?> klass, Object o) throws Exception {
if (!klass.isInstance(o)) {
throw new Exception(klass + " is not the class of " + o);
}
}
private static boolean arrayContains(long[] arr, long value) {
if (arr == null) {
return false;
}
for (int i = 0; i < arr.length; i++) {
if (arr[i] == value) {
return true;
}
}
return false;
}
/**
* Checks that we can find the dex-file for the given class in its classloader.
*
* Throws if it fails.
*/
private static void checkDexFileInClassLoader(Class<?> klass) throws Exception {
// If all the android BCP classes were availible when compiling this test and access checks
// weren't a thing this function would be written as follows:
//
// long dexFilePtr = getDexFilePointer(klass);
// dalvik.system.BaseDexClassLoader loader =
// (dalvik.system.BaseDexClassLoader)klass.getClassLoader();
// dalvik.system.DexPathList pathListValue = loader.pathList;
// dalvik.system.DexPathList.Element[] elementArrayValue = pathListValue.dexElements;
// int array_length = elementArrayValue.length;
// for (int i = 0; i < array_length; i++) {
// dalvik.system.DexPathList.Element curElement = elementArrayValue[i];
// dalvik.system.DexFile curDexFile = curElement.dexFile;
// if (curDexFile == null) {
// continue;
// }
// long[] curCookie = (long[])curDexFile.mCookie;
// long[] curInternalCookie = (long[])curDexFile.mInternalCookie;
// if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) {
// return;
// }
// }
// throw new Exception(
// "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass);
// Get all the fields and classes we need by reflection.
Class<?> baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field elementArrayField = dexPathListClass.getDeclaredField("dexElements");
Class<?> dexPathListElementClass = Class.forName("dalvik.system.DexPathList$Element");
Field dexFileField = dexPathListElementClass.getDeclaredField("dexFile");
Class<?> dexFileClass = Class.forName("dalvik.system.DexFile");
Field dexFileCookieField = dexFileClass.getDeclaredField("mCookie");
Field dexFileInternalCookieField = dexFileClass.getDeclaredField("mInternalCookie");
// Make all the fields accessible
AccessibleObject.setAccessible(new AccessibleObject[] { pathListField,
elementArrayField,
dexFileField,
dexFileCookieField,
dexFileInternalCookieField }, true);
long dexFilePtr = getDexFilePointer(klass);
ClassLoader loader = klass.getClassLoader();
checkIsInstance(baseDexClassLoaderClass, loader);
// DexPathList pathListValue = ((BaseDexClassLoader) loader).pathList;
Object pathListValue = pathListField.get(loader);
checkIsInstance(dexPathListClass, pathListValue);
// DexPathList.Element[] elementArrayValue = pathListValue.dexElements;
Object elementArrayValue = elementArrayField.get(pathListValue);
if (!elementArrayValue.getClass().isArray() ||
elementArrayValue.getClass().getComponentType() != dexPathListElementClass) {
throw new Exception("elementArrayValue is not an " + dexPathListElementClass + " array!");
}
// int array_length = elementArrayValue.length;
int array_length = Array.getLength(elementArrayValue);
for (int i = 0; i < array_length; i++) {
// DexPathList.Element curElement = elementArrayValue[i];
Object curElement = Array.get(elementArrayValue, i);
checkIsInstance(dexPathListElementClass, curElement);
// DexFile curDexFile = curElement.dexFile;
Object curDexFile = dexFileField.get(curElement);
if (curDexFile == null) {
continue;
}
checkIsInstance(dexFileClass, curDexFile);
// long[] curCookie = (long[])curDexFile.mCookie;
long[] curCookie = (long[])dexFileCookieField.get(curDexFile);
// long[] curInternalCookie = (long[])curDexFile.mInternalCookie;
long[] curInternalCookie = (long[])dexFileInternalCookieField.get(curDexFile);
if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) {
return;
}
}
throw new Exception(
"Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass);
}
private static void doTest() throws Exception {
Transform t = new Transform();
Transform2 t2 = new Transform2();
long initial_t1_dex = getDexFilePointer(Transform.class);
long initial_t2_dex = getDexFilePointer(Transform2.class);
if (initial_t2_dex != initial_t1_dex) {
throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " +
"have different initial dex files!");
}
checkDexFileInClassLoader(Transform.class);
checkDexFileInClassLoader(Transform2.class);
// Make sure they are loaded
t.sayHi();
t2.sayHi();
// Redefine both of the classes.
Redefinition.doMultiClassRedefinition(TRANSFORM_DEFINITION, TRANSFORM2_DEFINITION);
// Make sure we actually transformed them!
t.sayHi();
t2.sayHi();
long final_t1_dex = getDexFilePointer(Transform.class);
long final_t2_dex = getDexFilePointer(Transform2.class);
if (final_t2_dex == final_t1_dex) {
throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " +
"have the same initial dex files!");
} else if (final_t1_dex == initial_t1_dex) {
throw new Exception("The class " + Transform.class + " did not get a new dex file!");
} else if (final_t2_dex == initial_t2_dex) {
throw new Exception("The class " + Transform2.class + " did not get a new dex file!");
}
// Check to make sure the new dex files are in the class loader.
checkDexFileInClassLoader(Transform.class);
checkDexFileInClassLoader(Transform2.class);
}
// Gets the 'long' (really a native pointer) that is stored in the ClassLoader representing the
// DexFile a class is loaded from. This is plucked out of the internal DexCache object associated
// with the class.
private static long getDexFilePointer(Class<?> target) throws Exception {
// If all the android BCP classes were available when compiling this test and access checks
// weren't a thing this function would be written as follows:
//
// java.lang.DexCache dexCacheObject = target.dexCache;
// if (dexCacheObject == null) {
// return 0;
// }
// return dexCacheObject.dexFile;
Field dexCacheField = Class.class.getDeclaredField("dexCache");
Class<?> dexCacheClass = Class.forName("java.lang.DexCache");
Field dexFileField = dexCacheClass.getDeclaredField("dexFile");
AccessibleObject.setAccessible(new AccessibleObject[] { dexCacheField, dexFileField }, true);
Object dexCacheObject = dexCacheField.get(target);
if (dexCacheObject == null) {
return 0;
}
checkIsInstance(dexCacheClass, dexCacheObject);
return dexFileField.getLong(dexCacheObject);
}
}