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 invokecustom;
18 
19 import java.io.FileInputStream;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.lang.invoke.CallSite;
23 import java.lang.invoke.MethodHandle;
24 import java.lang.invoke.MethodHandles;
25 import java.lang.invoke.MethodType;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.util.function.Consumer;
29 import org.objectweb.asm.ClassReader;
30 import org.objectweb.asm.ClassVisitor;
31 import org.objectweb.asm.ClassWriter;
32 import org.objectweb.asm.Handle;
33 import org.objectweb.asm.MethodVisitor;
34 import org.objectweb.asm.Opcodes;
35 import org.objectweb.asm.Type;
36 
37 public class TestGenerator {
38 
39   private final Path baseName;
40 
main(String[] args)41   public static void main(String[] args) throws IOException {
42     assert args.length == 1;
43     TestGenerator testGenerator = new TestGenerator(Paths.get(args[0]));
44     testGenerator.generateTests();
45   }
46 
TestGenerator(Path baseName)47   public TestGenerator(Path baseName) {
48     this.baseName = baseName;
49   }
50 
generateTests()51   private void generateTests() throws IOException {
52     generateTest(this::generateMethodTest1, "invokecustom/InvokeCustom1");
53     generateTest(this::generateMethodTest2, "invokecustom/InvokeCustom2");
54     generateTest(this::generateMethodTest3, "invokecustom/InvokeCustom3");
55     // skip generateMethodTest4 - dexdump doesn't support invoke-direct handles
56     generateTest(this::generateMethodTest5, "invokecustom/InvokeCustom5");
57     generateTest(this::generateMethodTest6, "invokecustom/InvokeCustom6");
58     generateTest(this::generateMethodTest7, "invokecustom/InvokeCustom7");
59     generateTest(this::generateMethodTest8, "invokecustom/InvokeCustom8");
60     // skip generateMethodTest9 - dexdump doesn't support invoke-interface handles
61     generateTest(this::generateMethodMain, "invokecustom/InvokeCustom");
62   }
63 
generateTest(Consumer<ClassWriter> generator, String className)64   private void generateTest(Consumer<ClassWriter> generator, String className) throws IOException {
65     ClassReader cr =
66         new ClassReader(
67             new FileInputStream(baseName.resolve("invokecustom/InvokeCustom.class").toFile()));
68     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
69     cr.accept(
70         new ClassVisitor(Opcodes.ASM5, cw) {
71           @Override
72           public void visit(
73               int version,
74               int access,
75               String name,
76               String signature,
77               String superName,
78               String[] interfaces) {
79             super.visit(version, access, className, signature, superName, interfaces);
80           }
81 
82           @Override
83           public void visitEnd() {
84             generator.accept(cw);
85             super.visitEnd();
86           }
87         },
88         0);
89     new FileOutputStream(baseName.resolve(className + ".class").toFile()).write(cw.toByteArray());
90   }
91 
92   /* generate main method that only call all test methods. */
generateMethodMain(ClassVisitor cv)93   private void generateMethodMain(ClassVisitor cv) {
94     MethodVisitor mv =
95         cv.visitMethod(
96             Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
97     String internalName = Type.getInternalName(InvokeCustom.class);
98     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "1", "test1", "()V", false);
99     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "2", "test2", "()V", false);
100     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "3", "test3", "()V", false);
101     // mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "4", "test4", "()V", false);
102     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "5", "test5", "()V", false);
103     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "6", "test6", "()V", false);
104     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "7", "test7", "()V", false);
105     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "8", "test8", "()V", false);
106     // mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "9", "test9", "()V", false);
107     mv.visitInsn(Opcodes.RETURN);
108     mv.visitMaxs(-1, -1);
109   }
110 
111   /**
112    * Generate test with an invokedynamic, a static bootstrap method without extra args and no arg to
113    * the target method.
114    */
generateMethodTest1(ClassVisitor cv)115   private void generateMethodTest1(ClassVisitor cv) {
116     MethodVisitor mv =
117         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test1", "()V", null, null);
118     MethodType mt =
119         MethodType.methodType(
120             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
121     Handle bootstrap =
122         new Handle(
123             Opcodes.H_INVOKESTATIC,
124             Type.getInternalName(InvokeCustom.class),
125             "bsmLookupStatic",
126             mt.toMethodDescriptorString(),
127             false);
128     mv.visitInvokeDynamicInsn("targetMethodTest1", "()V", bootstrap);
129     mv.visitInsn(Opcodes.RETURN);
130     mv.visitMaxs(-1, -1);
131   }
132 
133   /**
134    * Generate test with an invokedynamic, a static bootstrap method without extra args and args to
135    * the target method.
136    */
generateMethodTest2(ClassVisitor cv)137   private void generateMethodTest2(ClassVisitor cv) {
138     MethodVisitor mv =
139         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test2", "()V", null, null);
140     MethodType mt =
141         MethodType.methodType(
142             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
143     Handle bootstrap =
144         new Handle(
145             Opcodes.H_INVOKESTATIC,
146             Type.getInternalName(InvokeCustom.class),
147             "bsmLookupStatic",
148             mt.toMethodDescriptorString(),
149             false);
150     mv.visitLdcInsn(new Boolean(true));
151     mv.visitLdcInsn(new Byte((byte) 127));
152     mv.visitLdcInsn(new Character('c'));
153     mv.visitLdcInsn(new Short((short) 1024));
154     mv.visitLdcInsn(new Integer(123456));
155     mv.visitLdcInsn(new Float(1.2f));
156     mv.visitLdcInsn(new Long(123456789));
157     mv.visitLdcInsn(new Double(3.5123456789));
158     mv.visitLdcInsn("String");
159     mv.visitInvokeDynamicInsn("targetMethodTest2", "(ZBCSIFJDLjava/lang/String;)V", bootstrap);
160     mv.visitInsn(Opcodes.RETURN);
161     mv.visitMaxs(-1, -1);
162   }
163 
164   /**
165    * Generate test with an invokedynamic, a static bootstrap method with extra args and no arg to
166    * the target method.
167    */
generateMethodTest3(ClassVisitor cv)168   private void generateMethodTest3(ClassVisitor cv) {
169     MethodVisitor mv =
170         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test3", "()V", null, null);
171     MethodType mt =
172         MethodType.methodType(
173             CallSite.class,
174             MethodHandles.Lookup.class,
175             String.class,
176             MethodType.class,
177             int.class,
178             long.class,
179             float.class,
180             double.class);
181     Handle bootstrap =
182         new Handle(
183             Opcodes.H_INVOKESTATIC,
184             Type.getInternalName(InvokeCustom.class),
185             "bsmLookupStaticWithExtraArgs",
186             mt.toMethodDescriptorString(),
187             false);
188     mv.visitInvokeDynamicInsn(
189         "targetMethodTest3",
190         "()V",
191         bootstrap,
192         new Integer(1),
193         new Long(123456789),
194         new Float(123.456),
195         new Double(123456.789123));
196     mv.visitInsn(Opcodes.RETURN);
197     mv.visitMaxs(-1, -1);
198   }
199 
200   /**
201    * Generate test with an invokedynamic, a static bootstrap method with an extra arg that is a
202    * MethodHandle of kind invokespecial.
203    */
generateMethodTest4(ClassVisitor cv)204   private void generateMethodTest4(ClassVisitor cv) {
205     MethodVisitor mv =
206         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test4", "()V", null, null);
207     MethodType mt =
208         MethodType.methodType(
209             CallSite.class,
210             MethodHandles.Lookup.class,
211             String.class,
212             MethodType.class,
213             MethodHandle.class);
214     Handle bootstrap =
215         new Handle(
216             Opcodes.H_INVOKESTATIC,
217             Type.getInternalName(InvokeCustom.class),
218             "bsmCreateCallSite",
219             mt.toMethodDescriptorString(),
220             false);
221     mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(InvokeCustom.class));
222     mv.visitInsn(Opcodes.DUP);
223     mv.visitMethodInsn(
224         Opcodes.INVOKESPECIAL, Type.getInternalName(InvokeCustom.class), "<init>", "()V", false);
225     mv.visitInvokeDynamicInsn(
226         "targetMethodTest4",
227         "(Linvokecustom/InvokeCustom;)V",
228         bootstrap,
229         new Handle(
230             Opcodes.H_INVOKESPECIAL,
231             Type.getInternalName(Super.class),
232             "targetMethodTest4",
233             "()V",
234             false));
235     mv.visitInsn(Opcodes.RETURN);
236     mv.visitMaxs(-1, -1);
237   }
238 
239   /**
240    * Generate a test with an invokedynamic where the target generates a result that the call site
241    * prints out.
242    */
generateMethodTest5(ClassVisitor cv)243   private void generateMethodTest5(ClassVisitor cv) {
244     MethodVisitor mv =
245         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test5", "()V", null, null);
246     MethodType mt =
247         MethodType.methodType(
248             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
249     Handle bootstrap =
250         new Handle(
251             Opcodes.H_INVOKESTATIC,
252             Type.getInternalName(InvokeCustom.class),
253             "bsmLookupStatic",
254             mt.toMethodDescriptorString(),
255             false);
256     mv.visitIntInsn(Opcodes.SIPUSH, 1000);
257     mv.visitIntInsn(Opcodes.SIPUSH, -923);
258     mv.visitIntInsn(Opcodes.SIPUSH, 77);
259     mv.visitInvokeDynamicInsn("targetMethodTest5", "(III)I", bootstrap);
260     mv.visitVarInsn(Opcodes.ISTORE, 0);
261     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
262     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
263     mv.visitInsn(Opcodes.DUP);
264     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
265     mv.visitLdcInsn("targetMethodTest5 returned: ");
266     mv.visitMethodInsn(
267         Opcodes.INVOKEVIRTUAL,
268         "java/lang/StringBuilder",
269         "append",
270         "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
271     mv.visitVarInsn(Opcodes.ILOAD, 0);
272     mv.visitMethodInsn(
273         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
274     mv.visitMethodInsn(
275         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
276     mv.visitMethodInsn(
277         Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
278     mv.visitInsn(Opcodes.RETURN);
279     mv.visitMaxs(-1, -1);
280   }
281 
282   /**
283    * Generate a test with an invokedynamic where the call site invocation tests the summation of two
284    * long values and returns a long.
285    */
generateMethodTest6(ClassVisitor cv)286   private void generateMethodTest6(ClassVisitor cv) {
287     MethodVisitor mv =
288         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test6", "()V", null, null);
289     MethodType mt =
290         MethodType.methodType(
291             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
292     Handle bootstrap =
293         new Handle(
294             Opcodes.H_INVOKESTATIC,
295             Type.getInternalName(InvokeCustom.class),
296             "bsmLookupStatic",
297             mt.toMethodDescriptorString(),
298             false);
299     mv.visitLdcInsn(0x77777777777l);
300     mv.visitLdcInsn(-0x11111111111l);
301     mv.visitLdcInsn(0x66666666666l);
302     mv.visitInvokeDynamicInsn("targetMethodTest6", "(JJJ)J", bootstrap);
303     mv.visitVarInsn(Opcodes.LSTORE, 0);
304     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
305     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
306     mv.visitInsn(Opcodes.DUP);
307     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
308     mv.visitLdcInsn("targetMethodTest6 returned: ");
309     mv.visitMethodInsn(
310         Opcodes.INVOKEVIRTUAL,
311         "java/lang/StringBuilder",
312         "append",
313         "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
314     mv.visitVarInsn(Opcodes.LLOAD, 0);
315     mv.visitMethodInsn(
316         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;");
317     mv.visitMethodInsn(
318         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
319     mv.visitMethodInsn(
320         Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
321     mv.visitInsn(Opcodes.RETURN);
322     mv.visitMaxs(-1, -1);
323   }
324 
325   /**
326    * Generate a test with an invokedynamic where the call site invocation tests the product of two
327    * float values and returns a double.
328    */
generateMethodTest7(ClassVisitor cv)329   private void generateMethodTest7(ClassVisitor cv) {
330     MethodVisitor mv =
331         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test7", "()V", null, null);
332     MethodType mt =
333         MethodType.methodType(
334             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
335     Handle bootstrap =
336         new Handle(
337             Opcodes.H_INVOKESTATIC,
338             Type.getInternalName(InvokeCustom.class),
339             "bsmLookupStatic",
340             mt.toMethodDescriptorString(),
341             false);
342     double x = 0.5009765625;
343     double y = -x;
344     mv.visitLdcInsn((float) x);
345     mv.visitLdcInsn((float) y);
346     mv.visitLdcInsn(x * y);
347     mv.visitInvokeDynamicInsn("targetMethodTest7", "(FFD)D", bootstrap);
348     mv.visitVarInsn(Opcodes.DSTORE, 0);
349     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
350     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
351     mv.visitInsn(Opcodes.DUP);
352     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
353     mv.visitLdcInsn("targetMethodTest6 returned: ");
354     mv.visitMethodInsn(
355         Opcodes.INVOKEVIRTUAL,
356         "java/lang/StringBuilder",
357         "append",
358         "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
359     mv.visitVarInsn(Opcodes.DLOAD, 0);
360     mv.visitMethodInsn(
361         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(D)Ljava/lang/StringBuilder;");
362     mv.visitMethodInsn(
363         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
364     mv.visitMethodInsn(
365         Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
366     mv.visitInsn(Opcodes.RETURN);
367     mv.visitMaxs(-1, -1);
368   }
369 
370   /**
371    * Generate a test with multiple invokedynamic bytecodes operating on the same parameters. These
372    * invocations should each produce invoke-custom bytecodes with unique call site ids.
373    */
generateMethodTest8(ClassVisitor cv)374   private void generateMethodTest8(ClassVisitor cv) {
375     MethodVisitor mv =
376         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test8", "()V", null, null);
377     MethodType mt =
378         MethodType.methodType(
379             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
380     // These should be two distinct call sites and both invoke the
381     // bootstrap method. An erroneous implementation might treat them
382     // as the same call site because the handle arguments are the same.
383     Handle bootstrap1 =
384         new Handle(
385             Opcodes.H_INVOKESTATIC,
386             Type.getInternalName(InvokeCustom.class),
387             "bsmLookupStatic",
388             mt.toMethodDescriptorString(),
389             false);
390     mv.visitLdcInsn("First invokedynamic invocation");
391     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap1);
392 
393     Handle bootstrap2 =
394         new Handle(
395             Opcodes.H_INVOKESTATIC,
396             Type.getInternalName(InvokeCustom.class),
397             "bsmLookupStatic",
398             mt.toMethodDescriptorString(),
399             false);
400     mv.visitLdcInsn("Second invokedynamic invocation");
401     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap2);
402 
403     // Using same handle again creates a new call site so invokes the bootstrap method.
404     mv.visitLdcInsn("Dupe first invokedynamic invocation");
405     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap1);
406     mv.visitInsn(Opcodes.RETURN);
407     mv.visitMaxs(-1, -1);
408   }
409 
410   /** Generate a test with different kinds of constant method handles. */
generateMethodTest9(ClassVisitor cv)411   private void generateMethodTest9(ClassVisitor cv) {
412     MethodVisitor mv =
413         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test9", "()V", null, null);
414     MethodType mt =
415         MethodType.methodType(
416             CallSite.class,
417             MethodHandles.Lookup.class,
418             String.class,
419             MethodType.class,
420             MethodHandle.class,
421             MethodHandle.class,
422             MethodHandle.class,
423             MethodHandle.class,
424             MethodHandle.class,
425             MethodHandle.class,
426             MethodHandle.class);
427     String internalName = Type.getInternalName(InvokeCustom.class);
428     Handle bootstrap =
429         new Handle(
430             Opcodes.H_INVOKESTATIC,
431             internalName,
432             "bsmLookupTest9",
433             mt.toMethodDescriptorString(),
434             false);
435     Handle staticSetter =
436         new Handle(Opcodes.H_GETSTATIC, internalName, "staticFieldTest9", "I", false);
437     Handle staticGetter =
438         new Handle(Opcodes.H_PUTSTATIC, internalName, "staticFieldTest9", "I", false);
439     Handle setter = new Handle(Opcodes.H_GETFIELD, internalName, "fieldTest9", "F", false);
440     Handle getter = new Handle(Opcodes.H_PUTFIELD, internalName, "fieldTest9", "F", false);
441     Handle instanceInvoke =
442         new Handle(Opcodes.H_INVOKEVIRTUAL, internalName, "helperMethodTest9", "()V", false);
443     // H_INVOKESTATIC and H_INVOKESPECIAL are tested elsewhere.
444     Handle constructor =
445         new Handle(Opcodes.H_NEWINVOKESPECIAL, internalName, "<init>", "(I)V", false);
446     Handle interfaceInvoke =
447         new Handle(
448             Opcodes.H_INVOKEINTERFACE, Type.getInternalName(Runnable.class), "run", "()V", true);
449 
450     mv.visitInvokeDynamicInsn(
451         "targetMethodTest9",
452         "()V",
453         bootstrap,
454         staticSetter,
455         staticGetter,
456         setter,
457         getter,
458         instanceInvoke,
459         constructor,
460         interfaceInvoke);
461     mv.visitInsn(Opcodes.RETURN);
462     mv.visitMaxs(-1, -1);
463   }
464 }
465