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