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 testGetCommonSuperClass()460 void testGetCommonSuperClass() { 461 ClassWriter classWriter = new ClassWriter(0); 462 463 assertEquals( 464 "java/lang/Object", 465 classWriter.getCommonSuperClass("java/lang/Object", "java/lang/Integer")); 466 assertEquals( 467 "java/lang/Object", 468 classWriter.getCommonSuperClass("java/lang/Integer", "java/lang/Object")); 469 assertEquals( 470 "java/lang/Object", 471 classWriter.getCommonSuperClass("java/lang/Integer", "java/lang/Runnable")); 472 assertEquals( 473 "java/lang/Object", 474 classWriter.getCommonSuperClass("java/lang/Runnable", "java/lang/Integer")); 475 assertEquals( 476 "java/lang/Throwable", 477 classWriter.getCommonSuperClass( 478 "java/lang/IndexOutOfBoundsException", "java/lang/AssertionError")); 479 Exception exception = 480 assertThrows( 481 TypeNotPresentException.class, 482 () -> classWriter.getCommonSuperClass("-", "java/lang/Object")); 483 assertEquals("Type - not present", exception.getMessage()); 484 exception = 485 assertThrows( 486 TypeNotPresentException.class, 487 () -> classWriter.getCommonSuperClass("java/lang/Object", "-")); 488 assertEquals("Type - not present", exception.getMessage()); 489 } 490 491 /** Tests that a ClassReader -> ClassWriter transform leaves classes unchanged. */ 492 @ParameterizedTest 493 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite(final PrecompiledClass classParameter, final Api apiParameter)494 void testReadAndWrite(final PrecompiledClass classParameter, final Api apiParameter) { 495 byte[] classFile = classParameter.getBytes(); 496 ClassReader classReader = new ClassReader(classFile); 497 ClassWriter classWriter = new ClassWriter(0); 498 499 classReader.accept(classWriter, attributes(), 0); 500 501 assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); 502 } 503 504 /** 505 * Tests that a ClassReader -> ClassWriter transform with the SKIP_CODE option produces a valid 506 * class. 507 */ 508 @ParameterizedTest 509 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_skipCode(final PrecompiledClass classParameter, final Api apiParameter)510 void testReadAndWrite_skipCode(final PrecompiledClass classParameter, final Api apiParameter) { 511 byte[] classFile = classParameter.getBytes(); 512 ClassReader classReader = new ClassReader(classFile); 513 ClassWriter classWriter = new ClassWriter(0); 514 515 classReader.accept(classWriter, attributes(), ClassReader.SKIP_CODE); 516 517 assertFalse(classWriter.hasFlags(ClassWriter.COMPUTE_MAXS)); 518 assertFalse(classWriter.hasFlags(ClassWriter.COMPUTE_FRAMES)); 519 assertTrue( 520 new ClassFile(classWriter.toByteArray()) 521 .toString() 522 .contains(classParameter.getInternalName())); 523 } 524 525 /** 526 * Tests that a ClassReader -> ClassWriter transform with the copy pool option leaves classes 527 * unchanged. 528 */ 529 @ParameterizedTest 530 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_copyPool(final PrecompiledClass classParameter, final Api apiParameter)531 void testReadAndWrite_copyPool(final PrecompiledClass classParameter, final Api apiParameter) { 532 byte[] classFile = classParameter.getBytes(); 533 ClassReader classReader = new ClassReader(classFile); 534 ClassWriter classWriter = new ClassWriter(classReader, 0); 535 536 classReader.accept(classWriter, attributes(), 0); 537 538 assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); 539 } 540 541 /** 542 * Tests that a ClassReader -> ClassWriter transform with the EXPAND_FRAMES option leaves classes 543 * unchanged. 544 */ 545 @ParameterizedTest 546 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_expandFrames( final PrecompiledClass classParameter, final Api apiParameter)547 void testReadAndWrite_expandFrames( 548 final PrecompiledClass classParameter, final Api apiParameter) { 549 byte[] classFile = classParameter.getBytes(); 550 ClassReader classReader = new ClassReader(classFile); 551 ClassWriter classWriter = new ClassWriter(0); 552 553 classReader.accept(classWriter, attributes(), ClassReader.EXPAND_FRAMES); 554 555 assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); 556 } 557 558 /** 559 * Tests that a ClassReader -> ClassWriter transform with the COMPUTE_MAXS option leaves classes 560 * unchanged. This is not true in general (the valid max stack and max locals for a given method 561 * are not unique), but this should be the case with our precompiled classes (except 562 * jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). 563 */ 564 @ParameterizedTest 565 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeMaxs(final PrecompiledClass classParameter, final Api apiParameter)566 void testReadAndWrite_computeMaxs(final PrecompiledClass classParameter, final Api apiParameter) { 567 assumeTrue(classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS); 568 byte[] classFile = classParameter.getBytes(); 569 ClassReader classReader = new ClassReader(classFile); 570 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 571 572 classReader.accept(classWriter, attributes(), 0); 573 574 assertTrue(classWriter.hasFlags(ClassWriter.COMPUTE_MAXS)); 575 assertFalse(classWriter.hasFlags(ClassWriter.COMPUTE_FRAMES)); 576 assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); 577 } 578 579 /** 580 * Tests that classes going through a ClassReader -> ClassWriter transform with the COMPUTE_MAXS 581 * option can be loaded and pass bytecode verification. 582 */ 583 @ParameterizedTest 584 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeMaxs_newInstance( final PrecompiledClass classParameter, final Api apiParameter)585 void testReadAndWrite_computeMaxs_newInstance( 586 final PrecompiledClass classParameter, final Api apiParameter) throws Exception { 587 byte[] classFile = classParameter.getBytes(); 588 ClassReader classReader = new ClassReader(classFile); 589 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 590 classReader.accept(classWriter, attributes(), 0); 591 592 Executable newInstance = () -> new ClassFile(classWriter.toByteArray()).newInstance(); 593 594 if (classParameter.isNotCompatibleWithCurrentJdk()) { 595 assertThrows(UnsupportedClassVersionError.class, newInstance); 596 } else { 597 assertDoesNotThrow(newInstance); 598 } 599 } 600 601 /** 602 * Tests that classes going through a ClassReader -> ClassWriter transform with the COMPUTE_FRAMES 603 * option can be loaded and pass bytecode verification. 604 */ 605 @ParameterizedTest 606 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeFrames( final PrecompiledClass classParameter, final Api apiParameter)607 void testReadAndWrite_computeFrames( 608 final PrecompiledClass classParameter, final Api apiParameter) { 609 assumeFalse(hasJsrOrRetInstructions(classParameter)); 610 byte[] classFile = classParameter.getBytes(); 611 ClassReader classReader = new ClassReader(classFile); 612 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 613 classReader.accept(classWriter, attributes(), 0); 614 615 byte[] newClassFile = classWriter.toByteArray(); 616 617 assertFalse(classWriter.hasFlags(ClassWriter.COMPUTE_MAXS)); 618 assertTrue(classWriter.hasFlags(ClassWriter.COMPUTE_FRAMES)); 619 // The computed stack map frames should be equal to the original ones, if any (classes before 620 // JDK8 don't have ones). This is not true in general (the valid frames for a given method are 621 // not unique), but this should be the case with our precompiled classes (except 622 // jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). 623 if (classParameter.isMoreRecentThan(Api.ASM4) 624 && classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS) { 625 assertEquals(new ClassFile(classFile), new ClassFile(newClassFile)); 626 } 627 Executable newInstance = () -> new ClassFile(newClassFile).newInstance(); 628 if (classParameter.isNotCompatibleWithCurrentJdk()) { 629 assertThrows(UnsupportedClassVersionError.class, newInstance); 630 } else { 631 assertDoesNotThrow(newInstance); 632 } 633 } 634 635 /** 636 * Tests that classes going through a ClassReader -> ClassWriter transform with the COMPUTE_FRAMES 637 * option can be loaded and pass bytecode verification. 638 */ 639 @ParameterizedTest 640 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeFrames_jsrInstructions( final PrecompiledClass classParameter, final Api apiParameter)641 void testReadAndWrite_computeFrames_jsrInstructions( 642 final PrecompiledClass classParameter, final Api apiParameter) { 643 assumeTrue(hasJsrOrRetInstructions(classParameter)); 644 byte[] classFile = classParameter.getBytes(); 645 ClassReader classReader = new ClassReader(classFile); 646 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 647 648 Executable accept = () -> classReader.accept(classWriter, attributes(), 0); 649 650 Exception exception = assertThrows(IllegalArgumentException.class, accept); 651 assertEquals("JSR/RET are not supported with computeFrames option", exception.getMessage()); 652 } 653 654 /** 655 * Tests that classes going through a ClassReader -> ClassWriter transform with the SKIP_FRAMES 656 * and COMPUTE_FRAMES options can be loaded and pass bytecode verification. 657 */ 658 @ParameterizedTest 659 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_skipAndComputeFrames( final PrecompiledClass classParameter, final Api apiParameter)660 void testReadAndWrite_skipAndComputeFrames( 661 final PrecompiledClass classParameter, final Api apiParameter) { 662 assumeFalse(hasJsrOrRetInstructions(classParameter)); 663 byte[] classFile = classParameter.getBytes(); 664 ClassReader classReader = new ClassReader(classFile); 665 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 666 classReader.accept(classWriter, attributes(), ClassReader.SKIP_FRAMES); 667 668 byte[] newClassFile = classWriter.toByteArray(); 669 670 // The computed stack map frames should be equal to the original ones, if any (classes before 671 // JDK8 don't have ones). This is not true in general (the valid frames for a given method are 672 // not unique), but this should be the case with our precompiled classes (except 673 // jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). 674 if (classParameter.isMoreRecentThan(Api.ASM4) 675 && classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS) { 676 assertEquals(new ClassFile(classFile), new ClassFile(newClassFile)); 677 } 678 Executable newInstance = () -> new ClassFile(newClassFile).newInstance(); 679 if (classParameter.isNotCompatibleWithCurrentJdk()) { 680 assertThrows(UnsupportedClassVersionError.class, newInstance); 681 } else { 682 assertDoesNotThrow(newInstance); 683 } 684 } 685 686 /** 687 * Tests that classes with dead code going through a ClassWriter with the COMPUTE_FRAMES option 688 * can be loaded and pass bytecode verification. 689 */ 690 @ParameterizedTest 691 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_computeFramesAndDeadCode( final PrecompiledClass classParameter, final Api apiParameter)692 void testReadAndWrite_computeFramesAndDeadCode( 693 final PrecompiledClass classParameter, final Api apiParameter) { 694 assumeFalse( 695 hasJsrOrRetInstructions(classParameter) || classParameter.isMoreRecentThan(apiParameter)); 696 byte[] classFile = classParameter.getBytes(); 697 ClassReader classReader = new ClassReader(classFile); 698 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 699 ClassVisitor classVisitor = new DeadCodeInserter(apiParameter.value(), classWriter); 700 classReader.accept(classVisitor, attributes(), ClassReader.SKIP_FRAMES); 701 702 byte[] newClassFile = classWriter.toByteArray(); 703 704 Executable newInstance = () -> new ClassFile(newClassFile).newInstance(); 705 if (classParameter.isNotCompatibleWithCurrentJdk()) { 706 assertThrows(UnsupportedClassVersionError.class, newInstance); 707 } else { 708 assertDoesNotThrow(newInstance); 709 } 710 } 711 712 /** 713 * Tests that classes with large methods (more than 32k) going through a ClassWriter with no 714 * option can be loaded and pass bytecode verification. Also tests that frames are not recomputed 715 * from stratch during this process (by making sure that {@link ClassWriter#getCommonSuperClass} 716 * is not called). 717 */ 718 @ParameterizedTest 719 @MethodSource(ALL_CLASSES_AND_ALL_APIS) testReadAndWrite_largeMethod(final PrecompiledClass classParameter, final Api apiParameter)720 void testReadAndWrite_largeMethod(final PrecompiledClass classParameter, final Api apiParameter) { 721 byte[] classFile = classParameter.getBytes(); 722 assumeFalse( 723 classFile.length > Short.MAX_VALUE || classParameter.isMoreRecentThan(apiParameter)); 724 ClassReader classReader = new ClassReader(classFile); 725 ClassWriter classWriter = new ClassWriterWithoutGetCommonSuperClass(); 726 ForwardJumpNopInserter forwardJumpNopInserter = 727 new ForwardJumpNopInserter(apiParameter.value(), classWriter); 728 classReader.accept(forwardJumpNopInserter, attributes(), 0); 729 if (!forwardJumpNopInserter.transformed) { 730 classWriter = new ClassWriterWithoutGetCommonSuperClass(); 731 classReader.accept( 732 new WideForwardJumpInserter(apiParameter.value(), classWriter), attributes(), 0); 733 } 734 735 byte[] transformedClass = classWriter.toByteArray(); 736 737 Executable newInstance = () -> new ClassFile(transformedClass).newInstance(); 738 if (classParameter.isNotCompatibleWithCurrentJdk()) { 739 assertThrows(UnsupportedClassVersionError.class, newInstance); 740 } else { 741 assertDoesNotThrow(newInstance); 742 } 743 // The transformed class should have the same structure as the original one (#317792). 744 ClassWriter originalClassWithoutCode = new ClassWriter(0); 745 classReader.accept(originalClassWithoutCode, ClassReader.SKIP_CODE); 746 ClassWriter transformedClassWithoutCode = new ClassWriter(0); 747 new ClassReader(transformedClass).accept(transformedClassWithoutCode, ClassReader.SKIP_CODE); 748 assertEquals( 749 new ClassFile(originalClassWithoutCode.toByteArray()), 750 new ClassFile(transformedClassWithoutCode.toByteArray())); 751 } 752 hasJsrOrRetInstructions(final PrecompiledClass classParameter)753 private static boolean hasJsrOrRetInstructions(final PrecompiledClass classParameter) { 754 return classParameter == PrecompiledClass.JDK3_ALL_INSTRUCTIONS 755 || classParameter == PrecompiledClass.JDK3_LARGE_METHOD; 756 } 757 newEmptyClassWriter()758 private static ClassWriter newEmptyClassWriter() { 759 ClassWriter classWriter = new ClassWriter(0); 760 classWriter.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "C", null, "java/lang/Object", null); 761 return classWriter; 762 } 763 getConstantPoolDump(final ClassWriter classWriter)764 private static String getConstantPoolDump(final ClassWriter classWriter) { 765 return new ClassFile(classWriter.toByteArray()).getConstantPoolDump(); 766 } 767 getDump(final ClassWriter classWriter)768 private static String getDump(final ClassWriter classWriter) { 769 return new ClassFile(classWriter.toByteArray()).toString(); 770 } 771 attributes()772 private static Attribute[] attributes() { 773 return new Attribute[] {new Comment(), new CodeComment()}; 774 } 775 776 private static class DeadCodeInserter extends ClassVisitor { 777 778 private String className; 779 DeadCodeInserter(final int api, final ClassVisitor classVisitor)780 DeadCodeInserter(final int api, final ClassVisitor classVisitor) { 781 super(api, classVisitor); 782 } 783 784 @Override visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces)785 public void visit( 786 final int version, 787 final int access, 788 final String name, 789 final String signature, 790 final String superName, 791 final String[] interfaces) { 792 className = name; 793 // Set V1_7 version to prevent fallback to old verifier. 794 super.visit( 795 (version & 0xFFFF) < Opcodes.V1_7 ? Opcodes.V1_7 : version, 796 access, 797 name, 798 signature, 799 superName, 800 interfaces); 801 } 802 803 @Override 804 public MethodVisitor visitMethod( 805 final int access, 806 final String name, 807 final String descriptor, 808 final String signature, 809 final String[] exceptions) { 810 int seed = (className + "." + name + descriptor).hashCode(); 811 return new MethodDeadCodeInserter( 812 api, seed, super.visitMethod(access, name, descriptor, signature, exceptions)); 813 } 814 } 815 816 private static class MethodDeadCodeInserter extends MethodVisitor implements Opcodes { 817 818 private Random random; 819 private boolean inserted; 820 821 MethodDeadCodeInserter(final int api, final int seed, final MethodVisitor methodVisitor) { 822 super(api, methodVisitor); 823 random = new Random(seed); 824 } 825 826 @Override 827 public void visitInsn(final int opcode) { 828 super.visitInsn(opcode); 829 maybeInsertDeadCode(); 830 } 831 832 @Override 833 public void visitIntInsn(final int opcode, final int operand) { 834 super.visitIntInsn(opcode, operand); 835 maybeInsertDeadCode(); 836 } 837 838 @Override 839 public void visitVarInsn(final int opcode, final int varIndex) { 840 super.visitVarInsn(opcode, varIndex); 841 maybeInsertDeadCode(); 842 } 843 844 @Override 845 public void visitTypeInsn(final int opcode, final String type) { 846 super.visitTypeInsn(opcode, type); 847 maybeInsertDeadCode(); 848 } 849 850 @Override 851 public void visitFieldInsn( 852 final int opcode, final String owner, final String name, final String descriptor) { 853 super.visitFieldInsn(opcode, owner, name, descriptor); 854 maybeInsertDeadCode(); 855 } 856 857 @Override 858 public void visitMethodInsn( 859 final int opcode, 860 final String owner, 861 final String name, 862 final String descriptor, 863 final boolean isInterface) { 864 super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); 865 maybeInsertDeadCode(); 866 } 867 868 @Override 869 public void visitInvokeDynamicInsn( 870 final String name, 871 final String descriptor, 872 final Handle bootstrapMethodHandle, 873 final Object... bootstrapMethodArguments) { 874 super.visitInvokeDynamicInsn( 875 name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); 876 maybeInsertDeadCode(); 877 } 878 879 @Override 880 public void visitJumpInsn(final int opcode, final Label label) { 881 super.visitJumpInsn(opcode, label); 882 maybeInsertDeadCode(); 883 } 884 885 @Override 886 public void visitLdcInsn(final Object value) { 887 if (value instanceof Boolean 888 || value instanceof Byte 889 || value instanceof Short 890 || value instanceof Character 891 || value instanceof Integer 892 || value instanceof Long 893 || value instanceof Double 894 || value instanceof Float 895 || value instanceof String 896 || value instanceof Type 897 || value instanceof Handle 898 || value instanceof ConstantDynamic) { 899 super.visitLdcInsn(value); 900 maybeInsertDeadCode(); 901 } else { 902 // If this happens, add support for the new type in 903 // MethodWriter.visitLdcInsn(), if needed. 904 throw new IllegalArgumentException("Unsupported type of value: " + value); 905 } 906 } 907 908 @Override 909 public void visitIincInsn(final int varIndex, final int increment) { 910 super.visitIincInsn(varIndex, increment); 911 maybeInsertDeadCode(); 912 } 913 914 @Override 915 public void visitTableSwitchInsn( 916 final int min, final int max, final Label dflt, final Label... labels) { 917 super.visitTableSwitchInsn(min, max, dflt, labels); 918 maybeInsertDeadCode(); 919 } 920 921 @Override 922 public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { 923 super.visitLookupSwitchInsn(dflt, keys, labels); 924 maybeInsertDeadCode(); 925 } 926 927 @Override 928 public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { 929 super.visitMultiANewArrayInsn(descriptor, numDimensions); 930 maybeInsertDeadCode(); 931 } 932 933 @Override 934 public void visitMaxs(final int maxStack, final int maxLocals) { 935 if (!inserted) { 936 insertDeadCode(); 937 } 938 super.visitMaxs(maxStack, maxLocals); 939 } 940 941 private void maybeInsertDeadCode() { 942 // Inserts dead code once every 50 instructions in average. 943 if (!inserted && random.nextFloat() < 1.0 / 50.0) { 944 insertDeadCode(); 945 } 946 } 947 948 private void insertDeadCode() { 949 Label end = new Label(); 950 visitJumpInsn(Opcodes.GOTO, end); 951 visitLdcInsn("DEAD CODE"); 952 visitLabel(end); 953 inserted = true; 954 } 955 } 956 957 /** Inserts NOP instructions after the first forward jump found, to get a wide jump. */ 958 private static class ForwardJumpNopInserter extends ClassVisitor { 959 960 boolean transformed; 961 962 ForwardJumpNopInserter(final int api, final ClassVisitor classVisitor) { 963 super(api, classVisitor); 964 } 965 966 @Override 967 public MethodVisitor visitMethod( 968 final int access, 969 final String name, 970 final String descriptor, 971 final String signature, 972 final String[] exceptions) { 973 return new MethodVisitor( 974 api, super.visitMethod(access, name, descriptor, signature, exceptions)) { 975 private final HashSet<Label> labels = new HashSet<>(); 976 977 @Override 978 public void visitLabel(final Label label) { 979 super.visitLabel(label); 980 labels.add(label); 981 } 982 983 @Override 984 public void visitJumpInsn(final int opcode, final Label label) { 985 if (!transformed && labels.contains(label)) { 986 transformed = true; 987 for (int i = 0; i <= Short.MAX_VALUE; ++i) { 988 visitInsn(Opcodes.NOP); 989 } 990 } 991 super.visitJumpInsn(opcode, label); 992 } 993 }; 994 } 995 } 996 997 /** Inserts a wide forward jump in the first non-abstract method that is found. */ 998 private static class WideForwardJumpInserter extends ClassVisitor { 999 1000 private boolean needFrames; 1001 private boolean transformed; 1002 1003 WideForwardJumpInserter(final int api, final ClassVisitor classVisitor) { 1004 super(api, classVisitor); 1005 } 1006 1007 @Override 1008 public void visit( 1009 final int version, 1010 final int access, 1011 final String name, 1012 final String signature, 1013 final String superName, 1014 final String[] interfaces) { 1015 needFrames = (version & 0xFFFF) >= Opcodes.V1_7; 1016 super.visit(version, access, name, signature, superName, interfaces); 1017 } 1018 1019 @Override 1020 public MethodVisitor visitMethod( 1021 final int access, 1022 final String name, 1023 final String descriptor, 1024 final String signature, 1025 final String[] exceptions) { 1026 return new MethodVisitor( 1027 api, super.visitMethod(access, name, descriptor, signature, exceptions)) { 1028 1029 @Override 1030 public void visitCode() { 1031 super.visitCode(); 1032 if (!transformed) { 1033 Label startLabel = new Label(); 1034 visitJumpInsn(Opcodes.GOTO, startLabel); 1035 if (needFrames) { 1036 visitLabel(new Label()); 1037 visitFrame(Opcodes.F_SAME, 0, null, 0, null); 1038 } 1039 for (int i = 0; i <= Short.MAX_VALUE; ++i) { 1040 visitInsn(Opcodes.NOP); 1041 } 1042 visitLabel(startLabel); 1043 if (needFrames) { 1044 visitFrame(Opcodes.F_SAME, 0, null, 0, null); 1045 visitInsn(Opcodes.NOP); 1046 } 1047 transformed = true; 1048 } 1049 } 1050 }; 1051 } 1052 } 1053 1054 /** 1055 * A ClassWriter whose {@link ClassWriter#getCommonSuperClass} method always throws an exception. 1056 */ 1057 private static class ClassWriterWithoutGetCommonSuperClass extends ClassWriter { 1058 1059 public ClassWriterWithoutGetCommonSuperClass() { 1060 super(0); 1061 } 1062 1063 @Override 1064 protected String getCommonSuperClass(final String type1, final String type2) { 1065 throw new UnsupportedOperationException(); 1066 } 1067 } 1068 } 1069