Home | History | Annotate | Download | only in invokecustom
      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 
     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 
     48   public TestGenerator(Path classNamePath) {
     49     this.classNamePath = classNamePath;
     50   }
     51 
     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. */
     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    */
     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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    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