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.OpenOption;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
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 classNamePath;
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.class.getPackage().getName(), InvokeCustom.class.getSimpleName() + ".class"));
45     testGenerator.generateTests();
46   }
47 
TestGenerator(Path classNamePath)48   public TestGenerator(Path classNamePath) {
49     this.classNamePath = classNamePath;
50   }
51 
generateTests()52   private void generateTests() throws IOException {
53     ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
54     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
55     cr.accept(
56         new ClassVisitor(Opcodes.ASM5, cw) {
57           @Override
58           public void visitEnd() {
59             generateMethodTest1(cw);
60             generateMethodTest2(cw);
61             generateMethodTest3(cw);
62             generateMethodTest4(cw);
63             generateMethodTest5(cw);
64             generateMethodTest6(cw);
65             generateMethodTest7(cw);
66             generateMethodTest8(cw);
67             generateMethodTest9(cw);
68             generateMethodMain(cw);
69             super.visitEnd();
70           }
71         }, 0);
72     new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray());
73   }
74 
75   /* generate main method that only call all test methods. */
generateMethodMain(ClassVisitor cv)76   private void generateMethodMain(ClassVisitor cv) {
77     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
78                                       "main", "([Ljava/lang/String;)V", null, null);
79     String internalName = Type.getInternalName(InvokeCustom.class);
80     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test1", "()V", false);
81     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test2", "()V", false);
82     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test3", "()V", false);
83     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test4", "()V", false);
84     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test5", "()V", false);
85     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test6", "()V", false);
86     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test7", "()V", false);
87     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test8", "()V", false);
88     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, "test9", "()V", false);
89     mv.visitInsn(Opcodes.RETURN);
90     mv.visitMaxs(-1, -1);
91   }
92 
93   /**
94    *  Generate test with an invokedynamic, a static bootstrap method without extra args and no arg
95    *  to the target method.
96    */
generateMethodTest1(ClassVisitor cv)97   private void generateMethodTest1(ClassVisitor cv) {
98     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test1", "()V",
99                                       null, null);
100     MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
101                                           MethodType.class);
102     Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
103                                   "bsmLookupStatic", mt.toMethodDescriptorString(), false);
104     mv.visitInvokeDynamicInsn("targetMethodTest1", "()V", bootstrap);
105     mv.visitInsn(Opcodes.RETURN);
106     mv.visitMaxs(-1, -1);
107   }
108 
109   /**
110    *  Generate test with an invokedynamic, a static bootstrap method without extra args and
111    *  args to the target method.
112    */
generateMethodTest2(ClassVisitor cv)113   private void generateMethodTest2(ClassVisitor cv) {
114     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test2", "()V",
115                                       null, null);
116     MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
117                                           MethodType.class);
118     Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
119                                   "bsmLookupStatic", mt.toMethodDescriptorString(), false);
120     mv.visitLdcInsn(new Boolean(true));
121     mv.visitLdcInsn(new Byte((byte) 127));
122     mv.visitLdcInsn(new Character('c'));
123     mv.visitLdcInsn(new Short((short) 1024));
124     mv.visitLdcInsn(new Integer(123456));
125     mv.visitLdcInsn(new Float(1.2f));
126     mv.visitLdcInsn(new Long(123456789));
127     mv.visitLdcInsn(new Double(3.5123456789));
128     mv.visitLdcInsn("String");
129     mv.visitInvokeDynamicInsn("targetMethodTest2", "(ZBCSIFJDLjava/lang/String;)V", bootstrap);
130     mv.visitInsn(Opcodes.RETURN);
131     mv.visitMaxs(-1, -1);
132   }
133 
134   /**
135    *  Generate test with an invokedynamic, a static bootstrap method with extra args and no arg
136    *  to the target method.
137    */
generateMethodTest3(ClassVisitor cv)138   private void generateMethodTest3(ClassVisitor cv) {
139     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test3", "()V",
140                                       null, null);
141     MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
142                                           MethodType.class, int.class,
143                                           long.class, float.class, double.class);
144     Handle bootstrap = new Handle( Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
145         "bsmLookupStaticWithExtraArgs", mt.toMethodDescriptorString(), false);
146     mv.visitInvokeDynamicInsn("targetMethodTest3", "()V", bootstrap, new Integer(1),
147         new Long(123456789), new Float(123.456), new Double(123456.789123));
148     mv.visitInsn(Opcodes.RETURN);
149     mv.visitMaxs(-1, -1);
150   }
151 
152   /**
153    *  Generate test with an invokedynamic, a static bootstrap method with an extra arg that is a
154    *  MethodHandle of kind invokespecial.
155    */
generateMethodTest4(ClassVisitor cv)156   private void generateMethodTest4(ClassVisitor cv) {
157     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test4", "()V",
158         null, null);
159     MethodType mt =
160         MethodType.methodType(
161             CallSite.class,
162             MethodHandles.Lookup.class,
163             String.class,
164             MethodType.class,
165             MethodHandle.class);
166     Handle bootstrap = new Handle( Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
167         "bsmCreateCallSite", mt.toMethodDescriptorString(), false);
168     mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(InvokeCustom.class));
169     mv.visitInsn(Opcodes.DUP);
170     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(InvokeCustom.class),
171                        "<init>", "()V", false);
172     mv.visitInvokeDynamicInsn("targetMethodTest4", "(Linvokecustom/InvokeCustom;)V", bootstrap,
173                               new Handle(Opcodes.H_INVOKESPECIAL, Type.getInternalName(Super.class),
174                                          "targetMethodTest4", "()V", false));
175     mv.visitInsn(Opcodes.RETURN);
176     mv.visitMaxs(-1, -1);
177   }
178 
179   /**
180    * Generate a test with an invokedynamic where the target generates
181    * a result that the call site prints out.
182    */
generateMethodTest5(ClassVisitor cv)183   private void generateMethodTest5(ClassVisitor cv) {
184     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test5", "()V",
185         null, null);
186     MethodType mt = MethodType.methodType(
187             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
188     Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
189                                   "bsmLookupStatic", mt.toMethodDescriptorString(), false);
190     mv.visitIntInsn(Opcodes.SIPUSH, 1000);
191     mv.visitIntInsn(Opcodes.SIPUSH, -923);
192     mv.visitIntInsn(Opcodes.SIPUSH, 77);
193     mv.visitInvokeDynamicInsn("targetMethodTest5", "(III)I", bootstrap);
194     mv.visitVarInsn(Opcodes.ISTORE, 0);
195     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
196     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
197     mv.visitInsn(Opcodes.DUP);
198     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
199     mv.visitLdcInsn("targetMethodTest5 returned: ");
200     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
201                        "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
202     mv.visitVarInsn(Opcodes.ILOAD, 0);
203     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
204                        "(I)Ljava/lang/StringBuilder;");
205     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
206                        "()Ljava/lang/String;");
207     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
208                        "(Ljava/lang/String;)V");
209     mv.visitInsn(Opcodes.RETURN);
210     mv.visitMaxs(-1, -1);
211   }
212 
213   /**
214    * Generate a test with an invokedynamic where the call site invocation tests the summation of
215    * two long values and returns a long.
216    */
generateMethodTest6(ClassVisitor cv)217   private void generateMethodTest6(ClassVisitor cv) {
218     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test6", "()V",
219                                       null, null);
220     MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
221                                           MethodType.class);
222     Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
223                                   "bsmLookupStatic", mt.toMethodDescriptorString(), false);
224     mv.visitLdcInsn(0x77777777777l);
225     mv.visitLdcInsn(-0x11111111111l);
226     mv.visitLdcInsn(0x66666666666l);
227     mv.visitInvokeDynamicInsn("targetMethodTest6", "(JJJ)J", bootstrap);
228     mv.visitVarInsn(Opcodes.LSTORE, 0);
229     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
230     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
231     mv.visitInsn(Opcodes.DUP);
232     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
233     mv.visitLdcInsn("targetMethodTest6 returned: ");
234     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
235                        "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
236     mv.visitVarInsn(Opcodes.LLOAD, 0);
237     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
238                        "(J)Ljava/lang/StringBuilder;");
239     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
240                        "()Ljava/lang/String;");
241     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
242                        "(Ljava/lang/String;)V");
243     mv.visitInsn(Opcodes.RETURN);
244     mv.visitMaxs(-1, -1);
245   }
246 
247   /**
248    * Generate a test with an invokedynamic where the call site invocation tests the product of
249    * two float values and returns a double.
250    */
generateMethodTest7(ClassVisitor cv)251   private void generateMethodTest7(ClassVisitor cv) {
252     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test7", "()V",
253                                       null, null);
254     MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
255                                           MethodType.class);
256     Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
257                                   "bsmLookupStatic", mt.toMethodDescriptorString(), false);
258     double x = 0.5009765625;
259     double y = -x;
260     mv.visitLdcInsn((float) x);
261     mv.visitLdcInsn((float) y);
262     mv.visitLdcInsn(x * y);
263     mv.visitInvokeDynamicInsn("targetMethodTest7", "(FFD)D", bootstrap);
264     mv.visitVarInsn(Opcodes.DSTORE, 0);
265     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
266     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
267     mv.visitInsn(Opcodes.DUP);
268     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
269     mv.visitLdcInsn("targetMethodTest6 returned: ");
270     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
271                        "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
272     mv.visitVarInsn(Opcodes.DLOAD, 0);
273     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
274                        "(D)Ljava/lang/StringBuilder;");
275     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
276                        "()Ljava/lang/String;");
277     mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
278                        "(Ljava/lang/String;)V");
279     mv.visitInsn(Opcodes.RETURN);
280     mv.visitMaxs(-1, -1);
281   }
282 
283   /**
284    * Generate a test with multiple invokedynamic bytecodes operating on the same parameters.
285    * These invocations should each produce invoke-custom bytecodes with unique call site ids.
286    */
generateMethodTest8(ClassVisitor cv)287   private void generateMethodTest8(ClassVisitor cv) {
288     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test8", "()V",
289                                       null, null);
290     MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
291                                           MethodType.class);
292     // These should be two distinct call sites and both invoke the
293     // bootstrap method. An erroneous implementation might treat them
294     // as the same call site because the handle arguments are the same.
295     Handle bootstrap1 = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
296                                    "bsmLookupStatic", mt.toMethodDescriptorString(), false);
297     mv.visitLdcInsn("First invokedynamic invocation");
298     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap1);
299 
300     Handle bootstrap2 = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
301                                    "bsmLookupStatic", mt.toMethodDescriptorString(), false);
302     mv.visitLdcInsn("Second invokedynamic invocation");
303     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap2);
304 
305     // Using same handle again creates a new call site so invokes the bootstrap method.
306     mv.visitLdcInsn("Dupe first invokedynamic invocation");
307     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap1);
308     mv.visitInsn(Opcodes.RETURN);
309     mv.visitMaxs(-1, -1);
310   }
311 
312   /**
313    * Generate a test with different kinds of constant method handles.
314    */
generateMethodTest9(ClassVisitor cv)315   private void generateMethodTest9(ClassVisitor cv) {
316     MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test9", "()V",
317                                       null, null);
318     MethodType mt =
319         MethodType.methodType(CallSite.class,
320                               MethodHandles.Lookup.class, String.class, MethodType.class,
321                               MethodHandle.class, MethodHandle.class,
322                               MethodHandle.class, MethodHandle.class,
323                               MethodHandle.class, MethodHandle.class,
324                               MethodHandle.class, MethodHandle.class);
325     String internalName = Type.getInternalName(InvokeCustom.class);
326     Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, internalName, "bsmLookupTest9",
327                                   mt.toMethodDescriptorString(), false);
328     Handle staticSetter =
329         new Handle(Opcodes.H_GETSTATIC, internalName, "staticFieldTest9", "I", false);
330     Handle staticGetter =
331         new Handle(Opcodes.H_PUTSTATIC, internalName, "staticFieldTest9", "I", false);
332     Handle setter =
333         new Handle(Opcodes.H_GETFIELD, internalName, "fieldTest9", "F", false);
334     Handle getter =
335         new Handle(Opcodes.H_PUTFIELD, internalName, "fieldTest9", "F", false);
336     Handle instanceInvoke =
337         new Handle(Opcodes.H_INVOKEVIRTUAL, internalName, "helperMethodTest9", "()V", false);
338     Handle constructor =
339         new Handle(Opcodes.H_NEWINVOKESPECIAL, internalName, "<init>", "(I)V", false);
340     Handle interfaceInvoke =
341         new Handle(Opcodes.H_INVOKEINTERFACE,
342                    Type.getInternalName(Runnable.class),
343                    "run", "()V", true);
344     // test4 covers invokespecial for a super method. This covers invokespecial of a private method.
345     Handle privateInvoke =
346         new Handle(Opcodes.H_INVOKESPECIAL, internalName, "privateMethodTest9", "()V", false);
347 
348     mv.visitInvokeDynamicInsn("targetMethodTest9", "()V", bootstrap,
349                               staticSetter, staticGetter,
350                               setter, getter,
351                               instanceInvoke, constructor,
352                               interfaceInvoke, privateInvoke);
353     mv.visitInsn(Opcodes.RETURN);
354     mv.visitMaxs(-1, -1);
355   }
356 }
357