View Javadoc
1   package mockit.asm.methods;
2   
3   import static mockit.asm.jvmConstants.Opcodes.DLOAD;
4   import static mockit.asm.jvmConstants.Opcodes.DSTORE;
5   import static mockit.asm.jvmConstants.Opcodes.GOTO;
6   import static mockit.asm.jvmConstants.Opcodes.GOTO_W;
7   import static mockit.asm.jvmConstants.Opcodes.IINC;
8   import static mockit.asm.jvmConstants.Opcodes.ILOAD;
9   import static mockit.asm.jvmConstants.Opcodes.INVOKEDYNAMIC;
10  import static mockit.asm.jvmConstants.Opcodes.INVOKEINTERFACE;
11  import static mockit.asm.jvmConstants.Opcodes.ISTORE;
12  import static mockit.asm.jvmConstants.Opcodes.LDC;
13  import static mockit.asm.jvmConstants.Opcodes.LDC2_W;
14  import static mockit.asm.jvmConstants.Opcodes.LDC_W;
15  import static mockit.asm.jvmConstants.Opcodes.LLOAD;
16  import static mockit.asm.jvmConstants.Opcodes.LOOKUPSWITCH;
17  import static mockit.asm.jvmConstants.Opcodes.LSTORE;
18  import static mockit.asm.jvmConstants.Opcodes.MULTIANEWARRAY;
19  import static mockit.asm.jvmConstants.Opcodes.SIPUSH;
20  import static mockit.asm.jvmConstants.Opcodes.TABLESWITCH;
21  import static mockit.asm.jvmConstants.Opcodes.WIDE;
22  
23  import edu.umd.cs.findbugs.annotations.NonNull;
24  import edu.umd.cs.findbugs.annotations.Nullable;
25  
26  import mockit.asm.SignatureWriter;
27  import mockit.asm.annotations.AnnotationVisitor;
28  import mockit.asm.classes.ClassWriter;
29  import mockit.asm.constantPool.ClassMemberItem;
30  import mockit.asm.constantPool.DynamicItem;
31  import mockit.asm.constantPool.Item;
32  import mockit.asm.constantPool.LongValueItem;
33  import mockit.asm.constantPool.StringItem;
34  import mockit.asm.controlFlow.CFGAnalysis;
35  import mockit.asm.controlFlow.Frame;
36  import mockit.asm.controlFlow.Label;
37  import mockit.asm.controlFlow.StackMapTableWriter;
38  import mockit.asm.exceptionHandling.ExceptionHandling;
39  import mockit.asm.jvmConstants.Access;
40  import mockit.asm.jvmConstants.Opcodes;
41  import mockit.asm.types.JavaType;
42  import mockit.asm.util.ByteVector;
43  import mockit.asm.util.MethodHandle;
44  
45  import org.checkerframework.checker.index.qual.NonNegative;
46  
47  /**
48   * A {@link MethodVisitor} that generates methods in bytecode form. Each visit method of this class appends the bytecode
49   * corresponding to the visited instruction to a byte vector, in the order these methods are called.
50   */
51  @SuppressWarnings({ "OverlyCoupledClass", "ClassWithTooManyFields", "OverlyComplexClass" })
52  public final class MethodWriter extends MethodVisitor {
53      /**
54       * The class writer to which this method must be added.
55       */
56      @NonNull
57      public final ClassWriter cw;
58  
59      /**
60       * The index of the constant pool item that contains the name of this method.
61       */
62      private final int nameItemIndex;
63  
64      /**
65       * The index of the constant pool item that contains the descriptor of this method.
66       */
67      private final int descItemIndex;
68  
69      /**
70       * The descriptor of this method.
71       */
72      @NonNull
73      private final String descriptor;
74  
75      @Nullable
76      private final SignatureWriter signatureWriter;
77  
78      /**
79       * If not zero, indicates that the code of this method must be copied from <code>cw.code</code>. More precisely,
80       * this field gives the index of the first byte to be copied from <code>cw.code</code>.
81       */
82      @NonNegative
83      int classReaderOffset;
84  
85      /**
86       * If not zero, indicates that the code of this method must be copied from <code>cw.cr</code>. More precisely, this
87       * field gives the number of bytes to be copied from <code>cw.code</code>.
88       */
89      @NonNegative
90      int classReaderLength;
91  
92      @Nullable
93      private final ExceptionsWriter exceptionsWriter;
94  
95      /**
96       * The runtime visible parameter annotations of this method, if any.
97       */
98      @Nullable
99      private AnnotationVisitor[] parameterAnnotations;
100 
101     /**
102      * The bytecode of this method.
103      */
104     @NonNull
105     private final ByteVector code;
106 
107     @NonNull
108     private final ExceptionHandling exceptionHandling;
109     @NonNull
110     private final StackMapTableWriter stackMapTableWriter;
111     @NonNull
112     private final LocalVariableTableWriter localVariableTableWriter;
113     @NonNull
114     private final LineNumberTableWriter lineNumberTableWriter;
115     @NonNull
116     private final CFGAnalysis cfgAnalysis;
117 
118     private final boolean computeFrames;
119 
120     /**
121      * Initializes this MethodWriter.
122      *
123      * @param cw
124      *            the class writer in which the method must be added
125      * @param access
126      *            the method's access flags (see {@link Opcodes})
127      * @param name
128      *            the method's name
129      * @param desc
130      *            the method's descriptor (see {@link JavaType})
131      * @param signature
132      *            the method's signature
133      * @param exceptions
134      *            the internal names of the method's exceptions
135      * @param computeFrames
136      *            <code>true</code> if the stack map tables must be recomputed from scratch
137      */
138     public MethodWriter(@NonNull ClassWriter cw, int access, @NonNull String name, @NonNull String desc,
139             @Nullable String signature, @Nullable String[] exceptions, boolean computeFrames) {
140         super(cw.getConstantPoolGeneration(), "<init>".equals(name) ? access | Access.CONSTRUCTOR : access);
141         this.cw = cw;
142         nameItemIndex = cp.newUTF8(name);
143         descItemIndex = cp.newUTF8(desc);
144         descriptor = desc;
145         signatureWriter = signature == null ? null : new SignatureWriter(cp, signature);
146         exceptionsWriter = exceptions == null ? null : new ExceptionsWriter(cp, exceptions);
147         code = new ByteVector();
148         this.computeFrames = computeFrames;
149         exceptionHandling = new ExceptionHandling(cp);
150         stackMapTableWriter = new StackMapTableWriter(cp, cw.isJava6OrNewer(), access, desc);
151         localVariableTableWriter = new LocalVariableTableWriter(cp);
152         lineNumberTableWriter = new LineNumberTableWriter(cp);
153         cfgAnalysis = new CFGAnalysis(cp, cw.getInternalClassName(), code, computeFrames);
154 
155         createMarkerAttributes(cw.getClassVersion());
156     }
157 
158     @NonNull
159     @Override
160     public AnnotationVisitor visitParameterAnnotation(@NonNegative int parameter, @NonNull String desc) {
161         AnnotationVisitor aw = new AnnotationVisitor(cp, desc);
162 
163         if (parameterAnnotations == null) {
164             int numParameters = JavaType.getArgumentTypes(descriptor).length;
165             parameterAnnotations = new AnnotationVisitor[numParameters];
166         }
167 
168         aw.setNext(parameterAnnotations[parameter]);
169         parameterAnnotations[parameter] = aw;
170 
171         return aw;
172     }
173 
174     @Override
175     public void visitInsn(int opcode) {
176         // Adds the instruction to the bytecode of the method.
177         code.putByte(opcode);
178 
179         cfgAnalysis.updateCurrentBlockForZeroOperandInstruction(opcode);
180     }
181 
182     @Override
183     public void visitIntInsn(int opcode, int operand) {
184         cfgAnalysis.updateCurrentBlockForSingleIntOperandInstruction(opcode, operand);
185 
186         // Adds the instruction to the bytecode of the method.
187         if (opcode == SIPUSH) {
188             code.put12(SIPUSH, operand);
189         } else { // BIPUSH or NEWARRAY
190             code.put11(opcode, operand);
191         }
192     }
193 
194     @Override
195     public void visitVarInsn(int opcode, @NonNegative int varIndex) {
196         cfgAnalysis.updateCurrentBlockForLocalVariableInstruction(opcode, varIndex);
197 
198         updateMaxLocals(opcode, varIndex);
199 
200         // Adds the instruction to the bytecode of the method.
201         if (varIndex < 4) {
202             int opt;
203 
204             if (opcode < ISTORE) { // ILOAD_0
205                 opt = 26 + (opcode - ILOAD << 2) + varIndex;
206             } else { // ISTORE_0
207                 opt = 59 + (opcode - ISTORE << 2) + varIndex;
208             }
209 
210             code.putByte(opt);
211         } else if (varIndex >= 256) {
212             code.putByte(WIDE).put12(opcode, varIndex);
213         } else {
214             code.put11(opcode, varIndex);
215         }
216 
217         if (opcode >= ISTORE && computeFrames && exceptionHandling.hasHandlers()) {
218             visitLabel(new Label());
219         }
220     }
221 
222     private void updateMaxLocals(int opcode, @NonNegative int varIndex) {
223         int n = opcode == LLOAD || opcode == DLOAD || opcode == LSTORE || opcode == DSTORE ? varIndex + 2
224                 : varIndex + 1;
225         stackMapTableWriter.updateMaxLocals(n);
226     }
227 
228     @Override
229     public void visitTypeInsn(int opcode, @NonNull String typeDesc) {
230         StringItem typeItem = cp.newClassItem(typeDesc);
231         cfgAnalysis.updateCurrentBlockForTypeInstruction(opcode, typeItem);
232 
233         // Adds the instruction to the bytecode of the method.
234         code.put12(opcode, typeItem.index);
235     }
236 
237     @Override
238     public void visitFieldInsn(int opcode, @NonNull String owner, @NonNull String name, @NonNull String desc) {
239         ClassMemberItem fieldItem = cp.newFieldItem(owner, name, desc);
240         cfgAnalysis.updateCurrentBlockForFieldInstruction(opcode, fieldItem, desc);
241 
242         // Adds the instruction to the bytecode of the method.
243         code.put12(opcode, fieldItem.index);
244     }
245 
246     @Override
247     public void visitMethodInsn(int opcode, @NonNull String owner, @NonNull String name, @NonNull String desc,
248             boolean itf) {
249         ClassMemberItem invokeItem = cp.newMethodItem(owner, name, desc, itf);
250         cfgAnalysis.updateCurrentBlockForInvokeInstruction(invokeItem, opcode, desc);
251 
252         // Adds the instruction to the bytecode of the method.
253         code.put12(opcode, invokeItem.index);
254 
255         if (opcode == INVOKEINTERFACE) {
256             int argSize = invokeItem.getArgSizeComputingIfNeeded(desc);
257             code.put11(argSize >> 2, 0);
258         }
259     }
260 
261     @Override
262     public void visitInvokeDynamicInsn(@NonNull String name, @NonNull String desc, @NonNull MethodHandle bsm,
263             @NonNull Object... bsmArgs) {
264         DynamicItem invokeItem = cw.addInvokeDynamicReference(name, desc, bsm, bsmArgs);
265         cfgAnalysis.updateCurrentBlockForInvokeInstruction(invokeItem, INVOKEDYNAMIC, desc);
266 
267         // Adds the instruction to the bytecode of the method.
268         code.put12(INVOKEDYNAMIC, invokeItem.index);
269         code.putShort(0);
270     }
271 
272     @Override
273     public void visitJumpInsn(int opcode, @NonNull Label label) {
274         Label nextInsn = cfgAnalysis.updateCurrentBlockForJumpInstruction(opcode, label);
275 
276         // Adds the instruction to the bytecode of the method.
277         if (label.isResolved() && label.position - code.getLength() < Short.MIN_VALUE) {
278             // Case of a backward jump with an offset < -32768. In this case we automatically replace GOTO with GOTO_W
279             // and IFxxx <l> with
280             // IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and
281             // where <l'> designates the
282             // instruction just after the GOTO_W.
283             if (opcode != GOTO) {
284                 // If the IF instruction is transformed into IFNOT GOTO_W the next instruction becomes the target of the
285                 // IFNOT instruction.
286                 if (nextInsn != null) {
287                     nextInsn.markAsTarget();
288                 }
289 
290                 code.putByte(opcode <= 166 ? (opcode + 1 ^ 1) - 1 : opcode ^ 1);
291                 code.putShort(8); // jump offset
292             }
293 
294             code.putByte(GOTO_W);
295             label.put(code, code.getLength() - 1, true);
296         } else {
297             // Case of a backward jump with an offset >= -32768, or of a forward jump with, of course, an unknown
298             // offset.
299             // In these cases we store the offset in 2 bytes (which will be increased in resizeInstructions, if needed).
300             code.putByte(opcode);
301             label.put(code, code.getLength() - 1, false);
302         }
303 
304         cfgAnalysis.updateCurrentBlockForJumpTarget(opcode, nextInsn);
305     }
306 
307     @Override
308     public void visitLabel(@NonNull Label label) {
309         cfgAnalysis.updateCurrentBlockForLabelBeforeNextInstruction(label);
310     }
311 
312     @Override
313     public void visitLdcInsn(@NonNull Object cst) {
314         Item constItem = cp.newConstItem(cst);
315         cfgAnalysis.updateCurrentBlockForLDCInstruction(constItem);
316 
317         // Adds the instruction to the bytecode of the method.
318         int index = constItem.index;
319 
320         if (constItem instanceof LongValueItem) {
321             code.put12(LDC2_W, index);
322         } else if (index >= 256) {
323             code.put12(LDC_W, index);
324         } else {
325             code.put11(LDC, index);
326         }
327     }
328 
329     @Override
330     public void visitIincInsn(@NonNegative int varIndex, int increment) {
331         cfgAnalysis.updateCurrentBlockForIINCInstruction(varIndex);
332 
333         // Updates max locals.
334         int n = varIndex + 1;
335         stackMapTableWriter.updateMaxLocals(n);
336 
337         // Adds the instruction to the bytecode of the method.
338         if (varIndex > 255 || increment > 127 || increment < -128) {
339             code.putByte(WIDE).put12(IINC, varIndex).putShort(increment);
340         } else {
341             code.putByte(IINC).put11(varIndex, increment);
342         }
343     }
344 
345     @Override
346     public void visitTableSwitchInsn(int min, int max, @NonNull Label dflt, @NonNull Label... labels) {
347         // Adds the instruction to the bytecode of the method.
348         int source = code.getLength();
349         code.putByte(TABLESWITCH);
350         code.roundUpLength();
351         dflt.put(code, source, true);
352         code.putInt(min).putInt(max);
353 
354         for (Label label : labels) {
355             label.put(code, source, true);
356         }
357 
358         cfgAnalysis.updateCurrentBlockForSwitchInstruction(dflt, labels);
359     }
360 
361     @Override
362     public void visitLookupSwitchInsn(@NonNull Label dflt, @NonNull int[] keys, @NonNull Label[] labels) {
363         // Adds the instruction to the bytecode of the method.
364         int source = code.getLength();
365         code.putByte(LOOKUPSWITCH);
366         code.roundUpLength();
367         dflt.put(code, source, true);
368         code.putInt(labels.length);
369 
370         for (int i = 0; i < labels.length; i++) {
371             code.putInt(keys[i]);
372             labels[i].put(code, source, true);
373         }
374 
375         cfgAnalysis.updateCurrentBlockForSwitchInstruction(dflt, labels);
376     }
377 
378     @Override
379     public void visitMultiANewArrayInsn(@NonNull String desc, @NonNegative int dims) {
380         StringItem arrayTypeItem = cp.newClassItem(desc);
381         cfgAnalysis.updateCurrentBlockForMULTIANEWARRAYInstruction(arrayTypeItem, dims);
382 
383         // Adds the instruction to the bytecode of the method.
384         code.put12(MULTIANEWARRAY, arrayTypeItem.index).putByte(dims);
385     }
386 
387     @Override
388     public void visitTryCatchBlock(@NonNull Label start, @NonNull Label end, @NonNull Label handler,
389             @Nullable String type) {
390         exceptionHandling.addHandler(start, end, handler, type);
391     }
392 
393     @Override
394     public void visitLocalVariable(@NonNull String name, @NonNull String desc, @Nullable String signature,
395             @NonNull Label start, @NonNull Label end, @NonNegative int index) {
396         int localsCount = localVariableTableWriter.addLocalVariable(name, desc, signature, start, end, index);
397         stackMapTableWriter.updateMaxLocals(localsCount);
398     }
399 
400     @Override
401     public void visitLineNumber(@NonNegative int line, @NonNull Label start) {
402         lineNumberTableWriter.addLineNumber(line, start);
403     }
404 
405     @Override
406     public void visitMaxStack(@NonNegative int maxStack) {
407         int computedMaxStack;
408 
409         if (computeFrames) {
410             exceptionHandling.completeControlFlowGraphWithExceptionHandlerBlocksFromComputedFrames();
411 
412             Frame firstFrame = cfgAnalysis.getFirstFrame();
413             stackMapTableWriter.createAndVisitFirstFrame(firstFrame, cw.getInternalClassName(), descriptor,
414                     classOrMemberAccess);
415 
416             computedMaxStack = cfgAnalysis.computeMaxStackSizeFromComputedFrames();
417             visitAllFramesToBeStoredInStackMap();
418         } else {
419             // TODO: figure out if/when the next call is needed, since no tests fail if commented out
420             exceptionHandling.completeControlFlowGraphWithExceptionHandlerBlocks();
421 
422             computedMaxStack = cfgAnalysis.computeMaxStackSize();
423             computedMaxStack = Math.max(maxStack, computedMaxStack);
424         }
425 
426         stackMapTableWriter.setMaxStack(computedMaxStack);
427     }
428 
429     private void visitAllFramesToBeStoredInStackMap() {
430         Label label = cfgAnalysis.getLabelForFirstBasicBlock();
431 
432         while (label != null) {
433             Frame frame = label.getFrame();
434 
435             if (label.isStoringFrame()) {
436                 stackMapTableWriter.visitFrame(frame);
437             }
438 
439             label = label.getSuccessor();
440         }
441     }
442 
443     /**
444      * Returns the size of the bytecode of this method.
445      */
446     @NonNegative
447     public int getSize() {
448         if (classReaderOffset > 0) {
449             return 6 + classReaderLength;
450         }
451 
452         int size = 8 + getMarkerAttributesSize() + getAnnotationsSize() + getParameterAnnotationsSize();
453         int codeLength = code.getLength();
454 
455         if (codeLength > 0) {
456             if (codeLength > 65536) {
457                 throw new RuntimeException("Method code too large!");
458             }
459 
460             cp.newUTF8("Code");
461 
462             size += 18 + codeLength + exceptionHandling.getSize();
463             size += localVariableTableWriter.getSize();
464             size += lineNumberTableWriter.getSize();
465             size += stackMapTableWriter.getSize();
466         }
467 
468         if (exceptionsWriter != null) {
469             size += exceptionsWriter.getSize();
470         }
471 
472         if (signatureWriter != null) {
473             size += signatureWriter.getSize();
474         }
475 
476         return size;
477     }
478 
479     @NonNegative
480     private int getParameterAnnotationsSize() {
481         int size = 0;
482 
483         if (parameterAnnotations != null) {
484             cp.newUTF8("RuntimeVisibleParameterAnnotations");
485 
486             int n = parameterAnnotations.length;
487             size += 7 + 2 * n;
488 
489             for (int i = n - 1; i >= 0; i--) {
490                 AnnotationVisitor parameterAnnotation = parameterAnnotations[i];
491                 size += parameterAnnotation == null ? 0 : parameterAnnotation.getSize();
492             }
493         }
494 
495         return size;
496     }
497 
498     /**
499      * Puts the bytecode of this method in the given byte vector.
500      */
501     @Override
502     protected void put(@NonNull ByteVector out) {
503         putAccess(out, Access.CONSTRUCTOR);
504         out.putShort(nameItemIndex);
505         out.putShort(descItemIndex);
506 
507         if (classReaderOffset > 0) {
508             out.putByteArray(cw.code, classReaderOffset, classReaderLength);
509             return;
510         }
511 
512         putMethodAttributeCount(out);
513         putMethodCode(out);
514 
515         if (exceptionsWriter != null) {
516             exceptionsWriter.put(out);
517         }
518 
519         putMarkerAttributes(out);
520 
521         if (signatureWriter != null) {
522             signatureWriter.put(out);
523         }
524 
525         putAnnotationAttributes(out);
526     }
527 
528     private void putMethodAttributeCount(@NonNull ByteVector out) {
529         int attributeCount = getMarkerAttributeCount();
530 
531         if (code.getLength() > 0) {
532             attributeCount++;
533         }
534 
535         if (exceptionsWriter != null) {
536             attributeCount++;
537         }
538 
539         if (signatureWriter != null) {
540             attributeCount++;
541         }
542 
543         if (annotations != null) {
544             attributeCount++;
545         }
546 
547         if (parameterAnnotations != null) {
548             attributeCount++;
549         }
550 
551         out.putShort(attributeCount);
552     }
553 
554     private void putMethodCode(@NonNull ByteVector out) {
555         if (code.getLength() > 0) {
556             putCodeSize(out);
557             stackMapTableWriter.putMaxStackAndLocals(out);
558             out.putInt(code.getLength()).putByteVector(code);
559             exceptionHandling.put(out);
560 
561             int codeAttributeCount = localVariableTableWriter.getAttributeCount();
562 
563             if (lineNumberTableWriter.hasLineNumbers()) {
564                 codeAttributeCount++;
565             }
566 
567             if (stackMapTableWriter.hasStackMap()) {
568                 codeAttributeCount++;
569             }
570 
571             out.putShort(codeAttributeCount);
572             localVariableTableWriter.put(out);
573             lineNumberTableWriter.put(out);
574             stackMapTableWriter.put(out);
575         }
576     }
577 
578     private void putCodeSize(@NonNull ByteVector out) {
579         int size = 12 + code.getLength() + exceptionHandling.getSize() + localVariableTableWriter.getSize()
580                 + lineNumberTableWriter.getSize() + stackMapTableWriter.getSize();
581 
582         out.putShort(cp.newUTF8("Code")).putInt(size);
583     }
584 
585     private void putAnnotationAttributes(@NonNull ByteVector out) {
586         putAnnotations(out);
587 
588         if (parameterAnnotations != null) {
589             out.putShort(cp.newUTF8("RuntimeVisibleParameterAnnotations"));
590             AnnotationVisitor.put(out, parameterAnnotations);
591         }
592     }
593 
594     @Nullable
595     public Label getCurrentBlock() {
596         return cfgAnalysis.getLabelForCurrentBasicBlock();
597     }
598 }