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