• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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