• 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
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