1 // ASM: a very small and fast Java bytecode manipulation framework 2 // Copyright (c) 2000-2011 INRIA, France Telecom 3 // All rights reserved. 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions 7 // are met: 8 // 1. Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // 2. Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // 3. Neither the name of the copyright holders nor the names of its 14 // contributors may be used to endorse or promote products derived from 15 // this software without specific prior written permission. 16 // 17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 // THE POSSIBILITY OF SUCH DAMAGE. 28 package org.objectweb.asm; 29 30 import static java.util.stream.Collectors.toSet; 31 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 32 import static org.junit.jupiter.api.Assertions.assertEquals; 33 import static org.junit.jupiter.api.Assertions.assertFalse; 34 import static org.junit.jupiter.api.Assertions.assertThrows; 35 import static org.junit.jupiter.api.Assertions.assertTrue; 36 import static org.junit.jupiter.api.Assertions.fail; 37 import static org.junit.jupiter.api.Assumptions.assumeFalse; 38 import static org.junit.jupiter.api.Assumptions.assumeTrue; 39 40 import java.io.IOException; 41 import java.lang.reflect.Field; 42 import java.lang.reflect.Method; 43 import java.lang.reflect.Modifier; 44 import java.nio.file.Files; 45 import java.nio.file.Paths; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.HashSet; 49 import java.util.Random; 50 import java.util.Set; 51 import org.junit.jupiter.api.Test; 52 import org.junit.jupiter.api.function.Executable; 53 import org.junit.jupiter.params.ParameterizedTest; 54 import org.junit.jupiter.params.provider.MethodSource; 55 import org.junit.jupiter.params.provider.ValueSource; 56 import org.objectweb.asm.test.AsmTest; 57 import org.objectweb.asm.test.ClassFile; 58 59 /** 60 * Unit tests for {@link ClassWriter}. 61 * 62 * @author Eric Bruneton 63 */ 64 class ClassWriterTest extends AsmTest { 65 66 /** 67 * Tests that the non-static fields of ClassWriter are the expected ones. This test is designed to 68 * fail each time new fields are added to ClassWriter, and serves as a reminder to update the 69 * field reset logic in {@link ClassWriter#replaceAsmInstructions()}, if needed, each time a new 70 * field is added. 71 */ 72 @Test testInstanceFields()73 void testInstanceFields() { 74 Set<String> actualFields = 75 Arrays.stream(ClassWriter.class.getDeclaredFields()) 76 .filter(field -> !Modifier.isStatic(field.getModifiers())) 77 .map(Field::getName) 78 .collect(toSet()); 79 80 Set<String> expectedFields = 81 new HashSet<String>( 82 Arrays.asList( 83 "flags", 84 "version", 85 "symbolTable", 86 "accessFlags", 87 "thisClass", 88 "superClass", 89 "interfaceCount", 90 "interfaces", 91 "firstField", 92 "lastField", 93 "firstMethod", 94 "lastMethod", 95 "numberOfInnerClasses", 96 "innerClasses", 97 "enclosingClassIndex", 98 "enclosingMethodIndex", 99 "signatureIndex", 100 "sourceFileIndex", 101 "debugExtension", 102 "lastRuntimeVisibleAnnotation", 103 "lastRuntimeInvisibleAnnotation", 104 "lastRuntimeVisibleTypeAnnotation", 105 "lastRuntimeInvisibleTypeAnnotation", 106 "moduleWriter", 107 "nestHostClassIndex", 108 "numberOfNestMemberClasses", 109 "nestMemberClasses", 110 "numberOfPermittedSubclasses", 111 "permittedSubclasses", 112 "firstRecordComponent", 113 "lastRecordComponent", 114 "firstAttribute", 115 "compute")); 116 // IMPORTANT: if this fails, update the string list AND update the logic that resets the 117 // ClassWriter fields in ClassWriter.toByteArray(), if needed (this logic is used to do a 118 // ClassReader->ClassWriter round trip to remove the ASM specific instructions due to large 119 // forward jumps). 120 assertEquals(expectedFields, actualFields); 121 } 122 123 /** 124 * Checks that all the ClassVisitor methods are overridden by ClassWriter and are final. 125 * ClassWriter does not take an api version as constructor argument. Therefore, backward 126 * compatibility of user subclasses overriding some visit methods of ClassWriter would not be 127 * possible to ensure. To prevent this, the ClassWriter visit methods must be final. 128 */ 129 @Test testVisitMethods_final()130 void testVisitMethods_final() { 131 ArrayList<Method> publicClassVisitorMethods = new ArrayList<>(); 132 for (Method classVisitorMethod : ClassVisitor.class.getDeclaredMethods()) { 133 int modifiers = classVisitorMethod.getModifiers(); 134 if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { 135 publicClassVisitorMethods.add(classVisitorMethod); 136 } 137 } 138 139 for (Method classVisitorMethod : publicClassVisitorMethods) { 140 try { 141 Method classWriterMethod = 142 ClassWriter.class.getMethod( 143 classVisitorMethod.getName(), classVisitorMethod.getParameterTypes()); 144 if (!classWriterMethod.getName().equals("getDelegate")) { 145 assertTrue( 146 Modifier.isFinal(classWriterMethod.getModifiers()), classWriterMethod + " is final"); 147 } 148 } catch (NoSuchMethodException e) { 149 fail("ClassWriter must override " + classVisitorMethod); 150 } 151 } 152 } 153 154 @Test testNewConst()155 void testNewConst() { 156 ClassWriter classWriter = newEmptyClassWriter(); 157 158 classWriter.newConst(Boolean.FALSE); 159 classWriter.newConst(Byte.valueOf((byte) 1)); 160 classWriter.newConst(Character.valueOf('2')); 161 classWriter.newConst(Short.valueOf((short) 3)); 162 163 String constantPoolDump = getConstantPoolDump(classWriter); 164 assertTrue(constantPoolDump.contains("constant_pool: 0")); 165 assertTrue(constantPoolDump.contains("constant_pool: 1")); 166 assertTrue(constantPoolDump.contains("constant_pool: 50")); 167 assertTrue(constantPoolDump.contains("constant_pool: 3")); 168 } 169 170 @Test testNewConst_illegalArgument()171 void testNewConst_illegalArgument() { 172 ClassWriter classWriter = newEmptyClassWriter(); 173 174 Executable newConst = () -> classWriter.newConst(new Object()); 175 176 Exception exception = assertThrows(IllegalArgumentException.class, newConst); 177 assertTrue(exception.getMessage().matches("value java\\.lang\\.Object@.*")); 178 } 179 180 @Test testNewUtf8()181 void testNewUtf8() { 182 ClassWriter classWriter = newEmptyClassWriter(); 183 184 classWriter.newUTF8("A"); 185 186 assertTrue(getConstantPoolDump(classWriter).contains("constant_pool: A")); 187 } 188 189 @Test testNewClass()190 void testNewClass() { 191 ClassWriter classWriter = newEmptyClassWriter(); 192 193 classWriter.newClass("A"); 194 195 assertTrue(getConstantPoolDump(classWriter).contains("constant_pool: ConstantClassInfo A")); 196 } 197 198 @Test testNewMethodType()199 void testNewMethodType() { 200 ClassWriter classWriter = newEmptyClassWriter(); 201 202 classWriter.newMethodType("()V"); 203 204 assertTrue( 205 getConstantPoolDump(classWriter).contains("constant_pool: ConstantMethodTypeInfo ()V")); 206 } 207 208 @Test testNewModule()209 void testNewModule() { 210 ClassWriter classWriter = newEmptyClassWriter(); 211 212 classWriter.newModule("A"); 213 214 assertTrue(getConstantPoolDump(classWriter).contains("constant_pool: ConstantModuleInfo A")); 215 } 216 217 @Test testNewPackage()218 void testNewPackage() { 219 ClassWriter classWriter = newEmptyClassWriter(); 220 221 classWriter.newPackage("A"); 222 223 assertTrue(getConstantPoolDump(classWriter).contains("constant_pool: ConstantPackageInfo A")); 224 } 225 226 @Test 227 @SuppressWarnings("deprecation") testDeprecatedNewHandle()228 void testDeprecatedNewHandle() { 229 ClassWriter classWriter = newEmptyClassWriter(); 230 231 classWriter.newHandle(Opcodes.H_GETFIELD, "A", "h", "I"); 232 233 assertTrue( 234 getConstantPoolDump(classWriter) 235 .contains("constant_pool: ConstantMethodHandleInfo 1.ConstantFieldRefInfo A.hI")); 236 } 237 238 @Test testNewHandle()239 void testNewHandle() { 240 ClassWriter classWriter = newEmptyClassWriter(); 241 242 classWriter.newHandle(Opcodes.H_GETFIELD, "A", "h", "I", false); 243 244 assertTrue( 245 getConstantPoolDump(classWriter) 246 .contains("constant_pool: ConstantMethodHandleInfo 1.ConstantFieldRefInfo A.hI")); 247 } 248 249 @Test testNewConstantDynamic()250 void testNewConstantDynamic() { 251 ClassWriter classWriter = newEmptyClassWriter(); 252 253 classWriter.newConstantDynamic( 254 "m", "Ljava/lang/String;", new Handle(Opcodes.H_INVOKESTATIC, "A", "m", "()V", false)); 255 256 String constantPoolDump = getConstantPoolDump(classWriter); 257 assertTrue( 258 constantPoolDump.contains("constant_pool: ConstantDynamicInfo 0.mLjava/lang/String;")); 259 assertTrue( 260 constantPoolDump.contains( 261 "constant_pool: ConstantMethodHandleInfo 6.ConstantMethodRefInfo A.m()V")); 262 assertTrue(constantPoolDump.contains("constant_pool: BootstrapMethods")); 263 } 264 265 @Test testNewInvokeDynamic()266 void testNewInvokeDynamic() { 267 ClassWriter classWriter = newEmptyClassWriter(); 268 269 classWriter.newInvokeDynamic("m", "()V", new Handle(Opcodes.H_GETFIELD, "A", "h", "I", false)); 270 271 String constantPoolDump = getConstantPoolDump(classWriter); 272 assertTrue(constantPoolDump.contains("ConstantInvokeDynamicInfo 0.m()V")); 273 assertTrue( 274 constantPoolDump.contains( 275 "constant_pool: ConstantMethodHandleInfo 1.ConstantFieldRefInfo A.hI")); 276 assertTrue(constantPoolDump.contains("constant_pool: BootstrapMethods")); 277 } 278 279 @Test testNewField()280 void testNewField() { 281 ClassWriter classWriter = newEmptyClassWriter(); 282 283 classWriter.newField("A", "f", "I"); 284 285 assertTrue( 286 getConstantPoolDump(classWriter).contains("constant_pool: ConstantFieldRefInfo A.fI")); 287 } 288 289 @Test testNewMethod()290 void testNewMethod() { 291 ClassWriter classWriter = newEmptyClassWriter(); 292 293 classWriter.newMethod("A", "m", "()V", false); 294 295 assertTrue( 296 getConstantPoolDump(classWriter).contains("constant_pool: ConstantMethodRefInfo A.m()V")); 297 } 298 299 @Test testNewNameType()300 void testNewNameType() { 301 ClassWriter classWriter = newEmptyClassWriter(); 302 303 classWriter.newNameType("m", "()V"); 304 305 assertTrue( 306 getConstantPoolDump(classWriter).contains("constant_pool: ConstantNameAndTypeInfo m()V")); 307 } 308 309 @ParameterizedTest 310 @ValueSource(ints = {65535, 65536}) testToByteArray_constantPoolSizeTooLarge(final int constantPoolCount)311 void testToByteArray_constantPoolSizeTooLarge(final int constantPoolCount) { 312 ClassWriter classWriter = newEmptyClassWriter(); 313 int initConstantPoolCount = 5; 314 for (int i = 0; i < constantPoolCount - initConstantPoolCount; ++i) { 315 classWriter.newConst(Integer.valueOf(i)); 316 } 317 318 Executable toByteArray = () -> classWriter.toByteArray(); 319 320 if (constantPoolCount > 65535) { 321 ClassTooLargeException exception = assertThrows(ClassTooLargeException.class, toByteArray); 322 assertEquals("C", exception.getClassName()); 323 assertEquals(constantPoolCount, exception.getConstantPoolCount()); 324 assertEquals("Class too large: C", exception.getMessage()); 325 } else { 326 assertDoesNotThrow(toByteArray); 327 } 328 } 329 330 @ParameterizedTest 331 @ValueSource(ints = {65535, 65536}) testToByteArray_methodCodeSizeTooLarge(final int methodCodeSize)332 void testToByteArray_methodCodeSizeTooLarge(final int methodCodeSize) { 333 ClassWriter classWriter = newEmptyClassWriter(); 334 String methodName = "m"; 335 String descriptor = "()V"; 336 MethodVisitor methodVisitor = 337 classWriter.visitMethod(Opcodes.ACC_STATIC, methodName, descriptor, null, null); 338 methodVisitor.visitCode(); 339 for (int i = 0; i < methodCodeSize - 1; ++i) { 340 methodVisitor.visitInsn(Opcodes.NOP); 341 } 342 methodVisitor.visitInsn(Opcodes.RETURN); 343 methodVisitor.visitMaxs(0, 0); 344 methodVisitor.visitEnd(); 345 346 Executable toByteArray = () -> classWriter.toByteArray(); 347 348 if (methodCodeSize > 65535) { 349 MethodTooLargeException exception = assertThrows(MethodTooLargeException.class, toByteArray); 350 assertEquals(methodName, exception.getMethodName()); 351 assertEquals("C", exception.getClassName()); 352 assertEquals(descriptor, exception.getDescriptor()); 353 assertEquals(methodCodeSize, exception.getCodeSize()); 354 assertEquals("Method too large: C.m ()V", exception.getMessage()); 355 } else { 356 assertDoesNotThrow(toByteArray); 357 } 358 } 359 360 @Test testToByteArray_largeSourceDebugExtension()361 void testToByteArray_largeSourceDebugExtension() { 362 ClassWriter classWriter = newEmptyClassWriter(); 363 classWriter.visitSource("Test.java", new String(new char[100000])); 364 365 classWriter.toByteArray(); 366 367 assertTrue(getDump(classWriter).contains("attribute_name_index: SourceDebugExtension")); 368 } 369 370 /** 371 * Tests that the COMPUTE_MAXS option works correctly on classes with very large or deeply nested 372 * subroutines (#307600, #311642). 373 * 374 * @throws IOException if the input class file can't be read. 375 */ 376 @ParameterizedTest 377 @ValueSource(strings = {"Issue307600.class", "Issue311642.class"}) testToByteArray_computeMaxs_largeSubroutines(final String classFileName)378 void testToByteArray_computeMaxs_largeSubroutines(final String classFileName) throws IOException { 379 ClassReader classReader = 380 new ClassReader(Files.newInputStream(Paths.get("src/test/resources/" + classFileName))); 381 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 382 classReader.accept(classWriter, attributes(), 0); 383 384 Executable toByteArray = () -> classWriter.toByteArray(); 385 386 assertDoesNotThrow(toByteArray); 387 } 388 389 @Test testToByteArray_computeFrames_mergeLongOrDouble()390 void testToByteArray_computeFrames_mergeLongOrDouble() { 391 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 392 classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "A", null, "java/lang/Object", null); 393 // Generate a default constructor, so that we can instantiate the class. 394 MethodVisitor methodVisitor = 395 classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); 396 methodVisitor.visitCode(); 397 methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); 398 methodVisitor.visitMethodInsn( 399 Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 400 methodVisitor.visitInsn(Opcodes.RETURN); 401 methodVisitor.visitMaxs(0, 0); 402 methodVisitor.visitEnd(); 403 // A method with a long local variable using slots 0 and 1, with an int stored in slot 1 in a 404 // branch. At the end of the method, the stack map frame should contain 'TOP' for slot 0, 405 // otherwise the class instantiation fails with a verification error. 406 methodVisitor = classWriter.visitMethod(Opcodes.ACC_STATIC, "m", "(J)V", null, null); 407 methodVisitor.visitCode(); 408 methodVisitor.visitInsn(Opcodes.ICONST_0); 409 Label label = new Label(); 410 methodVisitor.visitJumpInsn(Opcodes.IFNE, label); 411 methodVisitor.visitInsn(Opcodes.ICONST_0); 412 methodVisitor.visitVarInsn(Opcodes.ISTORE, 1); 413 methodVisitor.visitLabel(label); 414 methodVisitor.visitInsn(Opcodes.RETURN); 415 methodVisitor.visitMaxs(0, 0); 416 methodVisitor.visitEnd(); 417 classWriter.visitEnd(); 418 419 byte[] classFile = classWriter.toByteArray(); 420 421 assertDoesNotThrow(() -> new ClassFile(classFile).newInstance()); 422 } 423 424 @Test testToByteArray_computeFrames_highDimensionArrays()425 void testToByteArray_computeFrames_highDimensionArrays() { 426 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 427 classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "A", null, "java/lang/Object", null); 428 MethodVisitor methodVisitor = 429 classWriter.visitMethod( 430 Opcodes.ACC_STATIC, 431 "m", 432 "(I[[[[[[[[Ljava/lang/Integer;[[[[[[[[Ljava/lang/Long;)Ljava/lang/Object;", 433 null, 434 null); 435 methodVisitor.visitCode(); 436 methodVisitor.visitVarInsn(Opcodes.ILOAD, 0); 437 Label thenLabel = new Label(); 438 Label endIfLabel = new Label(); 439 methodVisitor.visitJumpInsn(Opcodes.IFNE, thenLabel); 440 methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); 441 methodVisitor.visitJumpInsn(Opcodes.GOTO, endIfLabel); 442 methodVisitor.visitLabel(thenLabel); 443 methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); 444 // At this point the stack can contain either an 8 dimensions Integer array or an 8 dimensions 445 // Long array. The merged type computed with the COMPUTE_FRAMES option should therefore be an 446 // 8 dimensions Number array. 447 methodVisitor.visitLabel(endIfLabel); 448 methodVisitor.visitInsn(Opcodes.ARETURN); 449 methodVisitor.visitMaxs(0, 0); 450 methodVisitor.visitEnd(); 451 classWriter.visitEnd(); 452 453 byte[] classFile = classWriter.toByteArray(); 454 455 // Check that the merged frame type is correctly computed. 456 assertTrue(new ClassFile(classFile).toString().contains("[[[[[[[[Ljava/lang/Number;")); 457 } 458 459 @Test testToByteArray_manyFramesWithForwardLabelReferences()460 void testToByteArray_manyFramesWithForwardLabelReferences() { 461 ClassWriter classWriter = new ClassWriter(0); 462 classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "A", null, "java/lang/Object", null); 463 MethodVisitor constructor = 464 classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); 465 constructor.visitCode(); 466 constructor.visitVarInsn(Opcodes.ALOAD, 0); 467 constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 468 constructor.visitInsn(Opcodes.RETURN); 469 constructor.visitMaxs(1, 1); 470 constructor.visitEnd(); 471 MethodVisitor methodVisitor = 472 classWriter.visitMethod(Opcodes.ACC_STATIC, "m", "()V", null, null); 473 methodVisitor.visitCode(); 474 Label label0 = new Label(); 475 methodVisitor.visitJumpInsn(Opcodes.GOTO, label0); 476 Label label1 = new Label(); 477 methodVisitor.visitLabel(label1); 478 Label[] newLabels = new Label[24]; 479 for (int i = 0; i < newLabels.length; ++i) { 480 newLabels[i] = new Label(); 481 } 482 methodVisitor.visitFrame(Opcodes.F_NEW, newLabels.length, newLabels, 0, null); 483 for (int i = 0; i < newLabels.length; ++i) { 484 methodVisitor.visitVarInsn(Opcodes.ALOAD, i); 485 methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "A", "<init>", "()V", false); 486 } 487 Label label2 = new Label(); 488 methodVisitor.visitJumpInsn(Opcodes.GOTO, label2); 489 methodVisitor.visitLabel(label0); 490 Object[] topTypes = new Object[newLabels.length]; 491 for (int i = 0; i < topTypes.length; ++i) { 492 topTypes[i] = Opcodes.TOP; 493 } 494 methodVisitor.visitFrame(Opcodes.F_NEW, topTypes.length, topTypes, 0, null); 495 for (int i = 0; i < newLabels.length; ++i) { 496 methodVisitor.visitLabel(newLabels[i]); 497 methodVisitor.visitTypeInsn(Opcodes.NEW, "A"); 498 methodVisitor.visitVarInsn(Opcodes.ASTORE, i); 499 } 500 methodVisitor.visitJumpInsn(Opcodes.GOTO, label1); 501 methodVisitor.visitLabel(label2); 502 String[] newTypes = new String[newLabels.length]; 503 for (int i = 0; i < newTypes.length; ++i) { 504 newTypes[i] = "A"; 505 } 506 methodVisitor.visitFrame(Opcodes.F_NEW, newTypes.length, newTypes, 0, null); 507 methodVisitor.visitInsn(Opcodes.RETURN); 508 methodVisitor.visitMaxs(1, newLabels.length); 509 methodVisitor.visitEnd(); 510 classWriter.visitEnd(); 511 512 byte[] classFile = classWriter.toByteArray(); 513 514 assertDoesNotThrow(() -> new ClassFile(classFile).newInstance()); 515 } 516 517 @Test testGetCommonSuperClass()518 void testGetCommonSuperClass() { 519 ClassWriter classWriter = new ClassWriter(0); 520 521 assertEquals( 522 "java/lang/Object", 523 classWriter.getCommonSuperClass("java/lang/Object", "java/lang/Integer")); 524 assertEquals( 525 "java/lang/Object", 526 classWriter.getCommonSuperClass("java/lang/Integer", "java/lang/Object")); 527 assertEquals( 528 "java/lang/Object", 529 classWriter.getCommonSuperClass("java/lang/Integer", "java/lang/Runnable")); 530 assertEquals( 531 "java/lang/Object", 532 classWriter.getCommonSuperClass("java/lang/Runnable", "java/lang/Integer")); 533 assertEquals( 534 "java/lang/Throwable", 535 classWriter.getCommonSuperClass( 536 "java/lang/IndexOutOfBoundsException", "java/lang/AssertionError")); 537 Exception exception = 538 assertThrows( 539 TypeNotPresentException.class, 540 () -> classWriter.getCommonSuperClass("-", "java/lang/Object")); 541 assertEquals("Type - not present", exception.getMessage()); 542 exception = 543 assertThrows( 544 TypeNotPresentException.class, 545 () -> classWriter.getCommonSuperClass("java/lang/Object", "-")); 546 assertEquals("Type - not present", exception.getMessage()); 547 } 548 549 /** Tests that a ClassReader -> ClassWriter transform leaves classes unchanged. */ 550 @ParameterizedTest 551 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite(final PrecompiledClass classParameter, final Api apiParameter)552 void testReadAndWrite(final PrecompiledClass classParameter, final Api apiParameter) { 553 byte[] classFile = classParameter.getBytes(); 554 ClassReader classReader = new ClassReader(classFile); 555 ClassWriter classWriter = new ClassWriter(0); 556 557 classReader.accept(classWriter, attributes(), 0); 558 559 assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); 560 } 561 562 /** 563 * Tests that a ClassReader -> ClassWriter transform with the SKIP_CODE option produces a valid 564 * class. 565 */ 566 @ParameterizedTest 567 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_skipCode(final PrecompiledClass classParameter, final Api apiParameter)568 void testReadAndWrite_skipCode(final PrecompiledClass classParameter, final Api apiParameter) { 569 byte[] classFile = classParameter.getBytes(); 570 ClassReader classReader = new ClassReader(classFile); 571 ClassWriter classWriter = new ClassWriter(0); 572 573 classReader.accept(classWriter, attributes(), ClassReader.SKIP_CODE); 574 575 assertFalse(classWriter.hasFlags(ClassWriter.COMPUTE_MAXS)); 576 assertFalse(classWriter.hasFlags(ClassWriter.COMPUTE_FRAMES)); 577 assertTrue( 578 new ClassFile(classWriter.toByteArray()) 579 .toString() 580 .contains(classParameter.getInternalName())); 581 } 582 583 /** 584 * Tests that a ClassReader -> ClassWriter transform with the copy pool option leaves classes 585 * unchanged. 586 */ 587 @ParameterizedTest 588 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_copyPool(final PrecompiledClass classParameter, final Api apiParameter)589 void testReadAndWrite_copyPool(final PrecompiledClass classParameter, final Api apiParameter) { 590 byte[] classFile = classParameter.getBytes(); 591 ClassReader classReader = new ClassReader(classFile); 592 ClassWriter classWriter = new ClassWriter(classReader, 0); 593 594 classReader.accept(classWriter, attributes(), 0); 595 596 assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); 597 } 598 599 /** 600 * Tests that a ClassReader -> ClassWriter transform with the EXPAND_FRAMES option leaves classes 601 * unchanged. 602 */ 603 @ParameterizedTest 604 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_expandFrames( final PrecompiledClass classParameter, final Api apiParameter)605 void testReadAndWrite_expandFrames( 606 final PrecompiledClass classParameter, final Api apiParameter) { 607 byte[] classFile = classParameter.getBytes(); 608 ClassReader classReader = new ClassReader(classFile); 609 ClassWriter classWriter = new ClassWriter(0); 610 611 classReader.accept(classWriter, attributes(), ClassReader.EXPAND_FRAMES); 612 613 assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); 614 } 615 616 /** 617 * Tests that a ClassReader -> ClassWriter transform with the COMPUTE_MAXS option leaves classes 618 * unchanged. This is not true in general (the valid max stack and max locals for a given method 619 * are not unique), but this should be the case with our precompiled classes (except 620 * jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). 621 */ 622 @ParameterizedTest 623 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeMaxs(final PrecompiledClass classParameter, final Api apiParameter)624 void testReadAndWrite_computeMaxs(final PrecompiledClass classParameter, final Api apiParameter) { 625 assumeTrue(classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS); 626 byte[] classFile = classParameter.getBytes(); 627 ClassReader classReader = new ClassReader(classFile); 628 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 629 630 classReader.accept(classWriter, attributes(), 0); 631 632 assertTrue(classWriter.hasFlags(ClassWriter.COMPUTE_MAXS)); 633 assertFalse(classWriter.hasFlags(ClassWriter.COMPUTE_FRAMES)); 634 assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); 635 } 636 637 /** 638 * Tests that classes going through a ClassReader -> ClassWriter transform with the COMPUTE_MAXS 639 * option can be loaded and pass bytecode verification. 640 */ 641 @ParameterizedTest 642 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeMaxs_newInstance( final PrecompiledClass classParameter, final Api apiParameter)643 void testReadAndWrite_computeMaxs_newInstance( 644 final PrecompiledClass classParameter, final Api apiParameter) throws Exception { 645 byte[] classFile = classParameter.getBytes(); 646 ClassReader classReader = new ClassReader(classFile); 647 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 648 classReader.accept(classWriter, attributes(), 0); 649 650 Executable newInstance = () -> new ClassFile(classWriter.toByteArray()).newInstance(); 651 652 if (classParameter.isNotCompatibleWithCurrentJdk()) { 653 assertThrows(UnsupportedClassVersionError.class, newInstance); 654 } else { 655 assertDoesNotThrow(newInstance); 656 } 657 } 658 659 /** 660 * Tests that classes going through a ClassReader -> ClassWriter transform with the COMPUTE_FRAMES 661 * option can be loaded and pass bytecode verification. 662 */ 663 @ParameterizedTest 664 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeFrames( final PrecompiledClass classParameter, final Api apiParameter)665 void testReadAndWrite_computeFrames( 666 final PrecompiledClass classParameter, final Api apiParameter) { 667 assumeFalse(hasJsrOrRetInstructions(classParameter)); 668 byte[] classFile = classParameter.getBytes(); 669 ClassReader classReader = new ClassReader(classFile); 670 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 671 classReader.accept(classWriter, attributes(), 0); 672 673 byte[] newClassFile = classWriter.toByteArray(); 674 675 assertFalse(classWriter.hasFlags(ClassWriter.COMPUTE_MAXS)); 676 assertTrue(classWriter.hasFlags(ClassWriter.COMPUTE_FRAMES)); 677 // The computed stack map frames should be equal to the original ones, if any (classes before 678 // JDK8 don't have ones). This is not true in general (the valid frames for a given method are 679 // not unique), but this should be the case with our precompiled classes (except 680 // jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). 681 if (classParameter.isMoreRecentThan(Api.ASM4) 682 && classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS) { 683 assertEquals(new ClassFile(classFile), new ClassFile(newClassFile)); 684 } 685 Executable newInstance = () -> new ClassFile(newClassFile).newInstance(); 686 if (classParameter.isNotCompatibleWithCurrentJdk()) { 687 assertThrows(UnsupportedClassVersionError.class, newInstance); 688 } else { 689 assertDoesNotThrow(newInstance); 690 } 691 } 692 693 /** 694 * Tests that classes going through a ClassReader -> ClassWriter transform with the COMPUTE_FRAMES 695 * option can be loaded and pass bytecode verification. 696 */ 697 @ParameterizedTest 698 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeFrames_jsrInstructions( final PrecompiledClass classParameter, final Api apiParameter)699 void testReadAndWrite_computeFrames_jsrInstructions( 700 final PrecompiledClass classParameter, final Api apiParameter) { 701 assumeTrue(hasJsrOrRetInstructions(classParameter)); 702 byte[] classFile = classParameter.getBytes(); 703 ClassReader classReader = new ClassReader(classFile); 704 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 705 706 Executable accept = () -> classReader.accept(classWriter, attributes(), 0); 707 708 Exception exception = assertThrows(IllegalArgumentException.class, accept); 709 assertEquals("JSR/RET are not supported with computeFrames option", exception.getMessage()); 710 } 711 712 /** 713 * Tests that classes going through a ClassReader -> ClassWriter transform with the SKIP_FRAMES 714 * and COMPUTE_FRAMES options can be loaded and pass bytecode verification. 715 */ 716 @ParameterizedTest 717 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_skipAndComputeFrames( final PrecompiledClass classParameter, final Api apiParameter)718 void testReadAndWrite_skipAndComputeFrames( 719 final PrecompiledClass classParameter, final Api apiParameter) { 720 assumeFalse(hasJsrOrRetInstructions(classParameter)); 721 byte[] classFile = classParameter.getBytes(); 722 ClassReader classReader = new ClassReader(classFile); 723 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 724 classReader.accept(classWriter, attributes(), ClassReader.SKIP_FRAMES); 725 726 byte[] newClassFile = classWriter.toByteArray(); 727 728 // The computed stack map frames should be equal to the original ones, if any (classes before 729 // JDK8 don't have ones). This is not true in general (the valid frames for a given method are 730 // not unique), but this should be the case with our precompiled classes (except 731 // jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). 732 if (classParameter.isMoreRecentThan(Api.ASM4) 733 && classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS) { 734 assertEquals(new ClassFile(classFile), new ClassFile(newClassFile)); 735 } 736 Executable newInstance = () -> new ClassFile(newClassFile).newInstance(); 737 if (classParameter.isNotCompatibleWithCurrentJdk()) { 738 assertThrows(UnsupportedClassVersionError.class, newInstance); 739 } else { 740 assertDoesNotThrow(newInstance); 741 } 742 } 743 744 /** 745 * Tests that classes with dead code going through a ClassWriter with the COMPUTE_FRAMES option 746 * can be loaded and pass bytecode verification. 747 */ 748 @ParameterizedTest 749 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeFramesAndDeadCode( final PrecompiledClass classParameter, final Api apiParameter)750 void testReadAndWrite_computeFramesAndDeadCode( 751 final PrecompiledClass classParameter, final Api apiParameter) { 752 assumeFalse( 753 hasJsrOrRetInstructions(classParameter) || classParameter.isMoreRecentThan(apiParameter)); 754 byte[] classFile = classParameter.getBytes(); 755 ClassReader classReader = new ClassReader(classFile); 756 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 757 ClassVisitor classVisitor = new DeadCodeInserter(apiParameter.value(), classWriter); 758 classReader.accept(classVisitor, attributes(), ClassReader.SKIP_FRAMES); 759 760 byte[] newClassFile = classWriter.toByteArray(); 761 762 Executable newInstance = () -> new ClassFile(newClassFile).newInstance(); 763 if (classParameter.isNotCompatibleWithCurrentJdk()) { 764 assertThrows(UnsupportedClassVersionError.class, newInstance); 765 } else { 766 assertDoesNotThrow(newInstance); 767 } 768 } 769 770 /** 771 * Tests that classes with large methods (more than 32k) going through a ClassWriter with no 772 * option can be loaded and pass bytecode verification. Also tests that frames are not recomputed 773 * from stratch during this process (by making sure that {@link ClassWriter#getCommonSuperClass} 774 * is not called). 775 */ 776 @ParameterizedTest 777 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_largeMethod(final PrecompiledClass classParameter, final Api apiParameter)778 void testReadAndWrite_largeMethod(final PrecompiledClass classParameter, final Api apiParameter) { 779 byte[] classFile = classParameter.getBytes(); 780 assumeFalse( 781 classFile.length > Short.MAX_VALUE || classParameter.isMoreRecentThan(apiParameter)); 782 ClassReader classReader = new ClassReader(classFile); 783 ClassWriter classWriter = new ClassWriterWithoutGetCommonSuperClass(); 784 ForwardJumpNopInserter forwardJumpNopInserter = 785 new ForwardJumpNopInserter(apiParameter.value(), classWriter); 786 classReader.accept(forwardJumpNopInserter, attributes(), 0); 787 if (!forwardJumpNopInserter.transformed) { 788 classWriter = new ClassWriterWithoutGetCommonSuperClass(); 789 classReader.accept( 790 new WideForwardJumpInserter(apiParameter.value(), classWriter), attributes(), 0); 791 } 792 793 byte[] transformedClass = classWriter.toByteArray(); 794 795 Executable newInstance = () -> new ClassFile(transformedClass).newInstance(); 796 if (classParameter.isNotCompatibleWithCurrentJdk()) { 797 assertThrows(UnsupportedClassVersionError.class, newInstance); 798 } else { 799 assertDoesNotThrow(newInstance); 800 } 801 // The transformed class should have the same structure as the original one (#317792). 802 ClassWriter originalClassWithoutCode = new ClassWriter(0); 803 classReader.accept(originalClassWithoutCode, ClassReader.SKIP_CODE); 804 ClassWriter transformedClassWithoutCode = new ClassWriter(0); 805 new ClassReader(transformedClass).accept(transformedClassWithoutCode, ClassReader.SKIP_CODE); 806 assertEquals( 807 new ClassFile(originalClassWithoutCode.toByteArray()), 808 new ClassFile(transformedClassWithoutCode.toByteArray())); 809 } 810 hasJsrOrRetInstructions(final PrecompiledClass classParameter)811 private static boolean hasJsrOrRetInstructions(final PrecompiledClass classParameter) { 812 return classParameter == PrecompiledClass.JDK3_ALL_INSTRUCTIONS 813 || classParameter == PrecompiledClass.JDK3_LARGE_METHOD; 814 } 815 newEmptyClassWriter()816 private static ClassWriter newEmptyClassWriter() { 817 ClassWriter classWriter = new ClassWriter(0); 818 classWriter.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "C", null, "java/lang/Object", null); 819 return classWriter; 820 } 821 getConstantPoolDump(final ClassWriter classWriter)822 private static String getConstantPoolDump(final ClassWriter classWriter) { 823 return new ClassFile(classWriter.toByteArray()).getConstantPoolDump(); 824 } 825 getDump(final ClassWriter classWriter)826 private static String getDump(final ClassWriter classWriter) { 827 return new ClassFile(classWriter.toByteArray()).toString(); 828 } 829 attributes()830 private static Attribute[] attributes() { 831 return new Attribute[] {new Comment(), new CodeComment()}; 832 } 833 834 private static class DeadCodeInserter extends ClassVisitor { 835 836 private String className; 837 DeadCodeInserter(final int api, final ClassVisitor classVisitor)838 DeadCodeInserter(final int api, final ClassVisitor classVisitor) { 839 super(api, classVisitor); 840 } 841 842 @Override visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces)843 public void visit( 844 final int version, 845 final int access, 846 final String name, 847 final String signature, 848 final String superName, 849 final String[] interfaces) { 850 className = name; 851 // Set V1_7 version to prevent fallback to old verifier. 852 super.visit( 853 (version & 0xFFFF) < Opcodes.V1_7 ? Opcodes.V1_7 : version, 854 access, 855 name, 856 signature, 857 superName, 858 interfaces); 859 } 860 861 @Override 862 public MethodVisitor visitMethod( 863 final int access, 864 final String name, 865 final String descriptor, 866 final String signature, 867 final String[] exceptions) { 868 int seed = (className + "." + name + descriptor).hashCode(); 869 return new MethodDeadCodeInserter( 870 api, seed, super.visitMethod(access, name, descriptor, signature, exceptions)); 871 } 872 } 873 874 private static class MethodDeadCodeInserter extends MethodVisitor implements Opcodes { 875 876 private Random random; 877 private boolean inserted; 878 879 MethodDeadCodeInserter(final int api, final int seed, final MethodVisitor methodVisitor) { 880 super(api, methodVisitor); 881 random = new Random(seed); 882 } 883 884 @Override 885 public void visitInsn(final int opcode) { 886 super.visitInsn(opcode); 887 maybeInsertDeadCode(); 888 } 889 890 @Override 891 public void visitIntInsn(final int opcode, final int operand) { 892 super.visitIntInsn(opcode, operand); 893 maybeInsertDeadCode(); 894 } 895 896 @Override 897 public void visitVarInsn(final int opcode, final int varIndex) { 898 super.visitVarInsn(opcode, varIndex); 899 maybeInsertDeadCode(); 900 } 901 902 @Override 903 public void visitTypeInsn(final int opcode, final String type) { 904 super.visitTypeInsn(opcode, type); 905 maybeInsertDeadCode(); 906 } 907 908 @Override 909 public void visitFieldInsn( 910 final int opcode, final String owner, final String name, final String descriptor) { 911 super.visitFieldInsn(opcode, owner, name, descriptor); 912 maybeInsertDeadCode(); 913 } 914 915 @Override 916 public void visitMethodInsn( 917 final int opcode, 918 final String owner, 919 final String name, 920 final String descriptor, 921 final boolean isInterface) { 922 super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); 923 maybeInsertDeadCode(); 924 } 925 926 @Override 927 public void visitInvokeDynamicInsn( 928 final String name, 929 final String descriptor, 930 final Handle bootstrapMethodHandle, 931 final Object... bootstrapMethodArguments) { 932 super.visitInvokeDynamicInsn( 933 name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); 934 maybeInsertDeadCode(); 935 } 936 937 @Override 938 public void visitJumpInsn(final int opcode, final Label label) { 939 super.visitJumpInsn(opcode, label); 940 maybeInsertDeadCode(); 941 } 942 943 @Override 944 public void visitLdcInsn(final Object value) { 945 if (value instanceof Boolean 946 || value instanceof Byte 947 || value instanceof Short 948 || value instanceof Character 949 || value instanceof Integer 950 || value instanceof Long 951 || value instanceof Double 952 || value instanceof Float 953 || value instanceof String 954 || value instanceof Type 955 || value instanceof Handle 956 || value instanceof ConstantDynamic) { 957 super.visitLdcInsn(value); 958 maybeInsertDeadCode(); 959 } else { 960 // If this happens, add support for the new type in 961 // MethodWriter.visitLdcInsn(), if needed. 962 throw new IllegalArgumentException("Unsupported type of value: " + value); 963 } 964 } 965 966 @Override 967 public void visitIincInsn(final int varIndex, final int increment) { 968 super.visitIincInsn(varIndex, increment); 969 maybeInsertDeadCode(); 970 } 971 972 @Override 973 public void visitTableSwitchInsn( 974 final int min, final int max, final Label dflt, final Label... labels) { 975 super.visitTableSwitchInsn(min, max, dflt, labels); 976 maybeInsertDeadCode(); 977 } 978 979 @Override 980 public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { 981 super.visitLookupSwitchInsn(dflt, keys, labels); 982 maybeInsertDeadCode(); 983 } 984 985 @Override 986 public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { 987 super.visitMultiANewArrayInsn(descriptor, numDimensions); 988 maybeInsertDeadCode(); 989 } 990 991 @Override 992 public void visitMaxs(final int maxStack, final int maxLocals) { 993 if (!inserted) { 994 insertDeadCode(); 995 } 996 super.visitMaxs(maxStack, maxLocals); 997 } 998 999 private void maybeInsertDeadCode() { 1000 // Inserts dead code once every 50 instructions in average. 1001 if (!inserted && random.nextFloat() < 1.0 / 50.0) { 1002 insertDeadCode(); 1003 } 1004 } 1005 1006 private void insertDeadCode() { 1007 Label end = new Label(); 1008 visitJumpInsn(Opcodes.GOTO, end); 1009 visitLdcInsn("DEAD CODE"); 1010 visitLabel(end); 1011 inserted = true; 1012 } 1013 } 1014 1015 /** Inserts NOP instructions after the first forward jump found, to get a wide jump. */ 1016 private static class ForwardJumpNopInserter extends ClassVisitor { 1017 1018 boolean transformed; 1019 1020 ForwardJumpNopInserter(final int api, final ClassVisitor classVisitor) { 1021 super(api, classVisitor); 1022 } 1023 1024 @Override 1025 public MethodVisitor visitMethod( 1026 final int access, 1027 final String name, 1028 final String descriptor, 1029 final String signature, 1030 final String[] exceptions) { 1031 return new MethodVisitor( 1032 api, super.visitMethod(access, name, descriptor, signature, exceptions)) { 1033 private final HashSet<Label> labels = new HashSet<>(); 1034 1035 @Override 1036 public void visitLabel(final Label label) { 1037 super.visitLabel(label); 1038 labels.add(label); 1039 } 1040 1041 @Override 1042 public void visitJumpInsn(final int opcode, final Label label) { 1043 if (!transformed && labels.contains(label)) { 1044 transformed = true; 1045 for (int i = 0; i <= Short.MAX_VALUE; ++i) { 1046 visitInsn(Opcodes.NOP); 1047 } 1048 } 1049 super.visitJumpInsn(opcode, label); 1050 } 1051 }; 1052 } 1053 } 1054 1055 /** Inserts a wide forward jump in the first non-abstract method that is found. */ 1056 private static class WideForwardJumpInserter extends ClassVisitor { 1057 1058 private boolean needFrames; 1059 private boolean transformed; 1060 1061 WideForwardJumpInserter(final int api, final ClassVisitor classVisitor) { 1062 super(api, classVisitor); 1063 } 1064 1065 @Override 1066 public void visit( 1067 final int version, 1068 final int access, 1069 final String name, 1070 final String signature, 1071 final String superName, 1072 final String[] interfaces) { 1073 needFrames = (version & 0xFFFF) >= Opcodes.V1_7; 1074 super.visit(version, access, name, signature, superName, interfaces); 1075 } 1076 1077 @Override 1078 public MethodVisitor visitMethod( 1079 final int access, 1080 final String name, 1081 final String descriptor, 1082 final String signature, 1083 final String[] exceptions) { 1084 return new MethodVisitor( 1085 api, super.visitMethod(access, name, descriptor, signature, exceptions)) { 1086 1087 @Override 1088 public void visitCode() { 1089 super.visitCode(); 1090 if (!transformed) { 1091 Label startLabel = new Label(); 1092 visitJumpInsn(Opcodes.GOTO, startLabel); 1093 if (needFrames) { 1094 visitLabel(new Label()); 1095 visitFrame(Opcodes.F_SAME, 0, null, 0, null); 1096 } 1097 for (int i = 0; i <= Short.MAX_VALUE; ++i) { 1098 visitInsn(Opcodes.NOP); 1099 } 1100 visitLabel(startLabel); 1101 if (needFrames) { 1102 visitFrame(Opcodes.F_SAME, 0, null, 0, null); 1103 visitInsn(Opcodes.NOP); 1104 } 1105 transformed = true; 1106 } 1107 } 1108 }; 1109 } 1110 } 1111 1112 /** 1113 * A ClassWriter whose {@link ClassWriter#getCommonSuperClass} method always throws an exception. 1114 */ 1115 private static class ClassWriterWithoutGetCommonSuperClass extends ClassWriter { 1116 1117 public ClassWriterWithoutGetCommonSuperClass() { 1118 super(0); 1119 } 1120 1121 @Override 1122 protected String getCommonSuperClass(final String type1, final String type2) { 1123 throw new UnsupportedOperationException(); 1124 } 1125 } 1126 } 1127