1cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light/*
2cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * Copyright (C) 2018 The Android Open Source Project
3cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light *
4cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * Licensed under the Apache License, Version 2.0 (the "License");
5cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * you may not use this file except in compliance with the License.
6cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * You may obtain a copy of the License at
7cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light *
8cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light *      http://www.apache.org/licenses/LICENSE-2.0
9cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light *
10cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * Unless required by applicable law or agreed to in writing, software
11cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * distributed under the License is distributed on an "AS IS" BASIS,
12cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * See the License for the specific language governing permissions and
14cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light * limitations under the License.
15cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light */
16cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
17cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightpackage art.constmethodhandle;
18cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
19cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport java.io.*;
20cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport java.util.*;
21cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport java.lang.invoke.CallSite;
22cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport java.lang.invoke.MethodHandle;
23cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport java.lang.invoke.MethodHandles;
24cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport java.lang.invoke.MethodType;
25cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport java.nio.file.*;
26cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport org.objectweb.asm.ClassReader;
27cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport org.objectweb.asm.ClassVisitor;
28cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport org.objectweb.asm.ClassWriter;
29cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport org.objectweb.asm.Handle;
30cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport org.objectweb.asm.MethodVisitor;
31cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport org.objectweb.asm.Opcodes;
32cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightimport org.objectweb.asm.Type;
33cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
34cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light// This test will modify in place the compiled java files to fill in the transformed version and
35cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light// fill in the TestInvoker.runTest function with a load-constant of a method-handle. It will use d8
36cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light// (passed in as an argument) to create the dex we will transform TestInvoke into.
37cc917d9f0495402f5301aa9df0071ad38ab78142Alex Lightpublic class TestGenerator {
38cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
39cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  public static void main(String[] args) throws IOException {
40cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    if (args.length != 2) {
41cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      throw new Error("Unable to convert class to dex without d8 binary!");
42cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    }
43cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    Path base = Paths.get(args[0]);
44cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    String d8Bin = args[1];
45cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
46cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    Path initTestInvoke = base.resolve(TestGenerator.class.getPackage().getName().replace('.', '/'))
47cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                              .resolve(TestInvoke.class.getSimpleName() + ".class");
48cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    byte[] initClass = new FileInputStream(initTestInvoke.toFile()).readAllBytes();
49cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
50cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    // Make the initial version of TestInvoker
51cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    generateInvoker(initClass, "sayHi", new FileOutputStream(initTestInvoke.toFile()));
52cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
53cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    // Make the final 'class' version of testInvoker
54cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    ByteArrayOutputStream finalClass = new ByteArrayOutputStream();
55cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    generateInvoker(initClass, "sayBye", finalClass);
56cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
57cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    Path initTest1948 = base.resolve("art").resolve(art.Test1948.class.getSimpleName() + ".class");
58cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    byte[] finalClassBytes = finalClass.toByteArray();
59cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    byte[] finalDexBytes = getFinalDexBytes(d8Bin, finalClassBytes);
60cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    generateTestCode(
61cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        new FileInputStream(initTest1948.toFile()).readAllBytes(),
62cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        finalClassBytes,
63cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        finalDexBytes,
64cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        new FileOutputStream(initTest1948.toFile()));
65cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  }
66cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
67cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  // Modify the Test1948 class bytecode so it has the transformed version of TestInvoker as a string
68cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  // constant.
69cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  private static void generateTestCode(
70cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      byte[] initClass, byte[] transClass, byte[] transDex, OutputStream out) throws IOException {
71cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    ClassReader cr = new ClassReader(initClass);
72cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
73cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    cr.accept(
74cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        new ClassVisitor(Opcodes.ASM6, cw) {
75cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light          @Override
76cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light          public void visitEnd() {
77cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light            generateStringAccessorMethod(
78cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                cw, "getDexBase64", Base64.getEncoder().encodeToString(transDex));
79cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light            generateStringAccessorMethod(
80cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                cw, "getClassBase64", Base64.getEncoder().encodeToString(transClass));
81cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light            super.visitEnd();
82cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light          }
83cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        }, 0);
84cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    out.write(cw.toByteArray());
85cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  }
86cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
87cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  // Insert a string accessor method so we can get the transformed versions of TestInvoker.
88cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  private static void generateStringAccessorMethod(ClassVisitor cv, String name, String ret) {
89cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    MethodVisitor mv = cv.visitMethod(
90cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
91cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        name, "()Ljava/lang/String;", null, null);
92cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitLdcInsn(ret);
93cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitInsn(Opcodes.ARETURN);
94cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitMaxs(-1, -1);
95cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  }
96cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
97cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  // Use d8bin to convert the classBytes into a dex file bytes. We need to do this here because we
98cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  // need the dex-file bytes to be used by the test class to redefine TestInvoker. We use d8 because
99cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  // it doesn't require setting up a directory structures or matching file names like dx does.
100cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  // TODO We should maybe just call d8 functions directly?
101cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  private static byte[] getFinalDexBytes(String d8Bin, byte[] classBytes) throws IOException {
102cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    Path tempDir = Files.createTempDirectory("FinalTestInvoker_Gen");
103cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    File tempInput = Files.createTempFile(tempDir, "temp_input_class", ".class").toFile();
104cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
105cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    OutputStream tempClassStream = new FileOutputStream(tempInput);
106cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    tempClassStream.write(classBytes);
107cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    tempClassStream.close();
108cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    tempClassStream = null;
109cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
110cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    Process d8Proc = new ProcessBuilder(d8Bin,
111cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                                        // Put classes.dex in the temp-dir we made.
112cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                                        "--output", tempDir.toAbsolutePath().toString(),
113cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                                        "--min-api", "28",  // Allow the new invoke ops.
114cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                                        "--no-desugaring",  // Don't try to be clever please.
115cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                                        tempInput.toPath().toAbsolutePath().toString())
116cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        .inheritIO()  // Just print to stdio.
117cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        .start();
118cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    int res;
119cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    try {
120cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      res = d8Proc.waitFor();
121cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    } catch (Exception e) {
122cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      System.out.println("Failed to dex: ".concat(e.toString()));
123cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      e.printStackTrace();
124cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      res = -123;
125cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    }
126cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    tempInput.delete();
127cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    try {
128cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      if (res == 0) {
129cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        byte[] out = new FileInputStream(tempDir.resolve("classes.dex").toFile()).readAllBytes();
130cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        tempDir.resolve("classes.dex").toFile().delete();
131cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        return out;
132cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      }
133cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    } finally {
134cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      tempDir.toFile().delete();
135cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    }
136cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    throw new Error("Failed to get dex file! " + res);
137cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  }
138cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
139cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  private static void generateInvoker(
140cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      byte[] inputClass,
141cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      String toCall,
142cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light      OutputStream output) throws IOException {
143cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    ClassReader cr = new ClassReader(inputClass);
144cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
145cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    cr.accept(
146cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        new ClassVisitor(Opcodes.ASM6, cw) {
147cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light          @Override
148cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light          public void visitEnd() {
149cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light            generateRunTest(cw, toCall);
150cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light            super.visitEnd();
151cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light          }
152cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        }, 0);
153cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    output.write(cw.toByteArray());
154cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  }
155cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light
156cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  // Creates the following method:
157cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  //   public runTest(Runnable preCall) {
158cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  //     preCall.run();
159cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  //     MethodHandle mh = <CONSTANT MH>;
160cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  //     mh.invokeExact();
161cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  //   }
162cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  private static void generateRunTest(ClassVisitor cv, String toCall) {
163cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC,
164cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light                                      "runTest", "(Ljava/lang/Runnable;)V", null, null);
165cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    MethodType mt = MethodType.methodType(Void.TYPE);
166cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    Handle mh = new Handle(
167cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        Opcodes.H_INVOKESTATIC,
168cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        Type.getInternalName(Responses.class),
169cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        toCall,
170cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        mt.toMethodDescriptorString(),
171cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        false);
172cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    String internalName = Type.getInternalName(Runnable.class);
173cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitVarInsn(Opcodes.ALOAD, 1);
174cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, internalName, "run", "()V", true);
175cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitLdcInsn(mh);
176cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitMethodInsn(
177cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        Opcodes.INVOKEVIRTUAL,
178cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        Type.getInternalName(MethodHandle.class),
179cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        "invokeExact",
180cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        "()V",
181cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light        false);
182cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitInsn(Opcodes.RETURN);
183cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light    mv.visitMaxs(-1, -1);
184cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light  }
185cc917d9f0495402f5301aa9df0071ad38ab78142Alex Light}
186