content: /*
   * 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);
    }
  }
  