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.controlFlow;
7   
8   import static mockit.asm.controlFlow.StackMapTableWriter.LocalsAndStackItemsDiff.APPEND_FRAME;
9   import static mockit.asm.controlFlow.StackMapTableWriter.LocalsAndStackItemsDiff.CHOP_FRAME;
10  import static mockit.asm.controlFlow.StackMapTableWriter.LocalsAndStackItemsDiff.FULL_FRAME;
11  import static mockit.asm.controlFlow.StackMapTableWriter.LocalsAndStackItemsDiff.SAME_FRAME;
12  import static mockit.asm.controlFlow.StackMapTableWriter.LocalsAndStackItemsDiff.SAME_FRAME_EXTENDED;
13  import static mockit.asm.controlFlow.StackMapTableWriter.LocalsAndStackItemsDiff.SAME_LOCALS_1_STACK_ITEM_FRAME;
14  import static mockit.asm.controlFlow.StackMapTableWriter.LocalsAndStackItemsDiff.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
15  
16  import edu.umd.cs.findbugs.annotations.NonNull;
17  
18  import mockit.asm.constantPool.AttributeWriter;
19  import mockit.asm.constantPool.ConstantPoolGeneration;
20  import mockit.asm.constantPool.UninitializedTypeTableItem;
21  import mockit.asm.jvmConstants.Access;
22  import mockit.asm.types.JavaType;
23  import mockit.asm.util.ByteVector;
24  
25  import org.checkerframework.checker.index.qual.NonNegative;
26  
27  /**
28   * Writes the "StackMapTable" method attribute (or "StackMap" for classfiles older than Java 6).
29   */
30  public final class StackMapTableWriter extends AttributeWriter {
31      /**
32       * Constants that identify how many locals and stack items a frame has, with respect to its previous frame.
33       */
34      interface LocalsAndStackItemsDiff {
35          /**
36           * Same locals as the previous frame, number of stack items is zero.
37           */
38          int SAME_FRAME = 0; // to 63 (0-3f)
39  
40          /**
41           * Same locals as the previous frame, number of stack items is 1.
42           */
43          int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f)
44  
45          /**
46           * Same locals as the previous frame, number of stack items is 1. Offset is bigger then 63.
47           */
48          int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7
49  
50          /**
51           * Current locals are the same as the locals in the previous frame, except that the k last locals are absent.
52           * The value of k is given by the formula 251-frame_type.
53           */
54          int CHOP_FRAME = 248; // to 250 (f8-fA)
55  
56          /**
57           * Same locals as the previous frame, number of stack items is zero. Offset is bigger then 63.
58           */
59          int SAME_FRAME_EXTENDED = 251; // fb
60  
61          /**
62           * Current locals are the same as the locals in the previous frame, except that k additional locals are defined.
63           * The value of k is given by the formula frame_type-251.
64           */
65          int APPEND_FRAME = 252; // to 254 // fc-fe
66  
67          /**
68           * Full frame.
69           */
70          int FULL_FRAME = 255; // ff
71      }
72  
73      private final boolean java6OrNewer;
74  
75      /**
76       * Maximum stack size of this method.
77       */
78      @NonNegative
79      private int maxStack;
80  
81      /**
82       * Maximum number of local variables for this method.
83       */
84      @NonNegative
85      private int maxLocals;
86  
87      /**
88       * Number of stack map frames in the StackMapTable attribute.
89       */
90      @NonNegative
91      private int frameCount;
92  
93      /**
94       * The StackMapTable attribute.
95       */
96      private ByteVector stackMap;
97  
98      /**
99       * The last frame that was written in the StackMapTable attribute.
100      *
101      * @see #frameDefinition
102      */
103     private int[] previousFrame;
104 
105     /**
106      * The current stack map frame.
107      * <p>
108      * The first element contains the offset of the instruction to which the frame corresponds (frameDefinition[0] =
109      * offset), the second element is the number of locals (frameDefinition[1] = nLocal), and the third one is the
110      * number of stack elements (frameDefinition[2] = nStack). The local variables start at index 3 (frameDefinition[3
111      * to 3+nLocal-1]) and are followed by the operand stack values (frameDefinition[3+nLocal...]).
112      * <p>
113      * All types are encoded as integers, with the same format as the one used in {@link Label}, but limited to BASE
114      * types.
115      */
116     private int[] frameDefinition;
117 
118     /**
119      * The current index in {@link #frameDefinition}, when writing new values into the array.
120      */
121     @NonNegative
122     private int frameIndex;
123 
124     public StackMapTableWriter(@NonNull ConstantPoolGeneration cp, boolean java6OrNewer, int methodAccess,
125             @NonNull String methodDesc) {
126         super(cp);
127         this.java6OrNewer = java6OrNewer;
128 
129         int size = JavaType.getArgumentsAndReturnSizes(methodDesc) >> 2;
130 
131         if ((methodAccess & Access.STATIC) != 0) {
132             size--;
133         }
134 
135         maxLocals = size;
136     }
137 
138     public void setMaxStack(@NonNegative int maxStack) {
139         this.maxStack = maxStack;
140     }
141 
142     public void updateMaxLocals(@NonNegative int n) {
143         if (n > maxLocals) {
144             maxLocals = n;
145         }
146     }
147 
148     public void putMaxStackAndLocals(@NonNull ByteVector out) {
149         out.putShort(maxStack).putShort(maxLocals);
150     }
151 
152     @NonNegative
153     private int getInstructionOffset() {
154         return frameDefinition[0];
155     }
156 
157     private void setInstructionOffset(@NonNegative int offset) {
158         frameDefinition[0] = offset;
159     }
160 
161     @NonNegative
162     private int getNumLocals() {
163         return frameDefinition[1];
164     }
165 
166     private void setNumLocals(@NonNegative int numLocals) {
167         frameDefinition[1] = numLocals;
168     }
169 
170     @NonNegative
171     private int getStackSize() {
172         return frameDefinition[2];
173     }
174 
175     private void setStackSize(@NonNegative int stackSize) {
176         frameDefinition[2] = stackSize;
177     }
178 
179     private void writeFrameDefinition(@NonNegative int value) {
180         frameDefinition[frameIndex++] = value;
181     }
182 
183     public boolean hasStackMap() {
184         return stackMap != null;
185     }
186 
187     /**
188      * Starts the visit of a stack map frame. Sets {@link #frameIndex} to the index of the next element to be written in
189      * this frame.
190      *
191      * @param offset
192      *            the offset of the instruction to which the frame corresponds.
193      * @param nLocals
194      *            the number of local variables in the frame.
195      * @param nStack
196      *            the number of stack elements in the frame.
197      */
198     private void startFrame(@NonNegative int offset, @NonNegative int nLocals, @NonNegative int nStack) {
199         int n = 3 + nLocals + nStack;
200 
201         if (frameDefinition == null || frameDefinition.length < n) {
202             frameDefinition = new int[n];
203         }
204 
205         setInstructionOffset(offset);
206         setNumLocals(nLocals);
207         setStackSize(nStack);
208         frameIndex = 3;
209     }
210 
211     /**
212      * Checks if the visit of the current {@link #frameDefinition frame} is finished, and if yes, write it in the
213      * StackMapTable attribute.
214      */
215     private void endFrame() {
216         if (previousFrame != null) { // do not write the first frame
217             if (stackMap == null) {
218                 setAttribute(java6OrNewer ? "StackMapTable" : "StackMap");
219                 stackMap = new ByteVector();
220             }
221 
222             writeFrame();
223             frameCount++;
224         }
225 
226         previousFrame = frameDefinition;
227         frameDefinition = null;
228     }
229 
230     /**
231      * Compress and writes the current {@link #frameDefinition frame} in the StackMapTable attribute.
232      */
233     private void writeFrame() {
234         int currentLocalsSize = getNumLocals();
235         int currentStackSize = getStackSize();
236 
237         if (java6OrNewer) {
238             writeFrameForJava6OrNewer(currentLocalsSize, currentStackSize);
239         } else {
240             writeFrameForOldVersionOfJava(currentLocalsSize, currentStackSize);
241         }
242     }
243 
244     private void writeFrameForOldVersionOfJava(@NonNegative int localsSize, @NonNegative int stackSize) {
245         int instructionOffset = getInstructionOffset();
246         writeFrame(instructionOffset, localsSize, stackSize);
247     }
248 
249     private void writeFullFrame(@NonNegative int instructionOffset, @NonNegative int localsSize,
250             @NonNegative int stackSize) {
251         stackMap.putByte(FULL_FRAME);
252         writeFrame(instructionOffset, localsSize, stackSize);
253     }
254 
255     private void writeFrame(@NonNegative int instructionOffset, @NonNegative int localsSize,
256             @NonNegative int stackSize) {
257         stackMap.putShort(instructionOffset);
258 
259         stackMap.putShort(localsSize);
260         int lastTypeIndex = 3 + localsSize;
261         writeFrameTypes(3, lastTypeIndex);
262 
263         stackMap.putShort(stackSize);
264         writeFrameTypes(lastTypeIndex, lastTypeIndex + stackSize);
265     }
266 
267     private void writeFrameForJava6OrNewer(@NonNegative int currentLocalsSize, @NonNegative int currentStackSize) {
268         @NonNegative
269         int previousLocalsSize = previousFrame[1];
270         int k = currentStackSize == 0 ? currentLocalsSize - previousLocalsSize : 0;
271         @NonNegative
272         int delta = getDelta();
273         int type = selectFrameType(currentLocalsSize, currentStackSize, previousLocalsSize, k, delta);
274 
275         if (type == CHOP_FRAME) {
276             previousLocalsSize = currentLocalsSize;
277         }
278 
279         type = selectFullFrameIfLocalsAreNotTheSame(previousLocalsSize, type);
280 
281         switch (type) {
282             case SAME_FRAME:
283                 stackMap.putByte(delta);
284                 break;
285             case SAME_LOCALS_1_STACK_ITEM_FRAME:
286                 writeFrameWithSameLocalsAndOneStackItem(currentLocalsSize, delta);
287                 break;
288             case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
289                 writeExtendedFrameWithSameLocalsAndOneStackItem(currentLocalsSize, delta);
290                 break;
291             case SAME_FRAME_EXTENDED:
292                 writeFrameWithSameLocalsAndZeroStackItems(0, delta);
293                 break;
294             case CHOP_FRAME:
295                 writeFrameWithSameLocalsAndZeroStackItems(k, delta);
296                 break;
297             case APPEND_FRAME:
298                 writeAppendedFrame(currentLocalsSize, previousLocalsSize, k, delta);
299                 break;
300             case FULL_FRAME:
301                 writeFullFrame(delta, currentLocalsSize, currentStackSize);
302                 break;
303             default:
304                 throw new IllegalArgumentException("Unknown frame type: " + type);
305         }
306     }
307 
308     @NonNegative
309     private int getDelta() {
310         int offset = getInstructionOffset();
311         return frameCount == 0 ? offset : offset - previousFrame[0] - 1;
312     }
313 
314     @NonNegative
315     private static int selectFrameType(@NonNegative int currentLocalsSize, @NonNegative int currentStackSize,
316             @NonNegative int previousLocalsSize, int k, @NonNegative int delta) {
317         int type = FULL_FRAME;
318 
319         if (currentStackSize == 0) {
320             if (k == 0) {
321                 type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED;
322             } else if (k > 0) {
323                 if (k <= 3) {
324                     type = APPEND_FRAME;
325                 }
326             } else if (k >= -3) {
327                 type = CHOP_FRAME;
328             }
329         } else if (currentLocalsSize == previousLocalsSize && currentStackSize == 1) {
330             type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
331         }
332 
333         return type;
334     }
335 
336     @NonNegative
337     private int selectFullFrameIfLocalsAreNotTheSame(@NonNegative int previousLocalsSize, @NonNegative int type) {
338         if (type != FULL_FRAME) {
339             // Verify if locals are the same.
340             int l = 3;
341 
342             for (int j = 0; j < previousLocalsSize; j++) {
343                 if (frameDefinition[l] != previousFrame[l]) {
344                     return FULL_FRAME;
345                 }
346 
347                 l++;
348             }
349         }
350 
351         return type;
352     }
353 
354     private void writeFrameWithSameLocalsAndOneStackItem(@NonNegative int localsSize, @NonNegative int delta) {
355         stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
356         writeFrameTypes(3 + localsSize, 4 + localsSize);
357     }
358 
359     private void writeFrameWithSameLocalsAndZeroStackItems(int k, @NonNegative int delta) {
360         stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
361     }
362 
363     private void writeExtendedFrameWithSameLocalsAndOneStackItem(@NonNegative int localsSize, @NonNegative int delta) {
364         stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort(delta);
365         writeFrameTypes(3 + localsSize, 4 + localsSize);
366     }
367 
368     private void writeAppendedFrame(@NonNegative int currentLocalsSize, @NonNegative int previousLocalsSize, int k,
369             @NonNegative int delta) {
370         stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
371         writeFrameTypes(3 + previousLocalsSize, 3 + currentLocalsSize);
372     }
373 
374     /**
375      * Writes some types of the current {@link #frameDefinition frame} into the StackMapTable attribute. This method
376      * converts types from the format used in {@link Label} to the format used in StackMapTable attributes. In
377      * particular, it converts type table indexes to constant pool indexes.
378      *
379      * @param start
380      *            index of the first type in {@link #frameDefinition} to write
381      * @param end
382      *            index of last type in {@link #frameDefinition} to write (exclusive)
383      */
384     private void writeFrameTypes(@NonNegative int start, @NonNegative int end) {
385         for (int i = start; i < end; i++) {
386             int type = frameDefinition[i];
387             int dimensions = type & FrameTypeMask.DIM;
388 
389             if (dimensions == 0) {
390                 writeFrameOfRegularType(type);
391             } else {
392                 writeFrameOfArrayType(dimensions, type);
393             }
394         }
395     }
396 
397     private void writeFrameOfRegularType(@NonNegative int type) {
398         int typeTableIndex = type & FrameTypeMask.BASE_VALUE;
399 
400         switch (type & FrameTypeMask.BASE_KIND) {
401             case FrameTypeMask.OBJECT:
402                 String classDesc = cp.getInternalName(typeTableIndex);
403                 int classDescIndex = cp.newClass(classDesc);
404                 stackMap.putByte(7).putShort(classDescIndex);
405                 break;
406             case FrameTypeMask.UNINITIALIZED:
407                 UninitializedTypeTableItem uninitializedItemValue = cp.getUninitializedItemValue(typeTableIndex);
408                 int typeDesc = uninitializedItemValue.getOffset();
409                 stackMap.putByte(8).putShort(typeDesc);
410                 break;
411             default:
412                 stackMap.putByte(typeTableIndex);
413         }
414     }
415 
416     private void writeFrameOfArrayType(@NonNegative int arrayDimensions, @NonNegative int arrayElementType) {
417         StringBuilder sb = new StringBuilder();
418         writeDimensionsIntoArrayDescriptor(sb, arrayDimensions);
419 
420         if ((arrayElementType & FrameTypeMask.BASE_KIND) == FrameTypeMask.OBJECT) {
421             String arrayElementTypeDesc = cp.getInternalName(arrayElementType & FrameTypeMask.BASE_VALUE);
422             sb.append('L').append(arrayElementTypeDesc).append(';');
423         } else {
424             char typeCode = getTypeCodeForArrayElements(arrayElementType);
425             sb.append(typeCode);
426         }
427 
428         String arrayElementTypeDesc = sb.toString();
429         int typeDescIndex = cp.newClass(arrayElementTypeDesc);
430         stackMap.putByte(7).putShort(typeDescIndex);
431     }
432 
433     private static void writeDimensionsIntoArrayDescriptor(@NonNull StringBuilder sb,
434             @NonNegative int arrayDimensions) {
435         arrayDimensions >>= 28;
436 
437         while (arrayDimensions-- > 0) {
438             sb.append('[');
439         }
440     }
441 
442     private static char getTypeCodeForArrayElements(@NonNegative int arrayElementType) {
443         switch (arrayElementType & 0xF) {
444             case 1:
445                 return 'I';
446             case 2:
447                 return 'F';
448             case 3:
449                 return 'D';
450             case 9:
451                 return 'Z';
452             case 10:
453                 return 'B';
454             case 11:
455                 return 'C';
456             case 12:
457                 return 'S';
458             default:
459                 return 'J';
460         }
461     }
462 
463     /**
464      * Creates and visits the first (implicit) frame.
465      */
466     public void createAndVisitFirstFrame(@NonNull Frame frame, @NonNull String classDesc, @NonNull String methodDesc,
467             int methodAccess) {
468         JavaType[] args = JavaType.getArgumentTypes(methodDesc);
469         frame.initInputFrame(classDesc, methodAccess, args, maxLocals);
470         visitFrame(frame);
471     }
472 
473     /**
474      * Visits a frame that has been computed from scratch.
475      */
476     public void visitFrame(@NonNull Frame frame) {
477         int[] locals = frame.inputLocals;
478         int nLocal = computeNumberOfLocals(locals);
479 
480         int[] stacks = frame.inputStack;
481         int nStack = computeStackSize(stacks);
482 
483         startFrame(frame.owner.position, nLocal, nStack);
484         putLocalsOrStackElements(locals, nLocal);
485         putLocalsOrStackElements(stacks, nStack);
486         endFrame();
487     }
488 
489     /**
490      * Computes the number of locals (ignores TOP types that are just after a LONG or a DOUBLE, and all trailing TOP
491      * types).
492      */
493     @NonNegative
494     private static int computeNumberOfLocals(@NonNull int[] locals) {
495         int nLocal = 0;
496         int nTop = 0;
497 
498         for (int i = 0; i < locals.length; i++) {
499             int t = locals[i];
500 
501             if (t == FrameTypeMask.TOP) {
502                 nTop++;
503             } else {
504                 nLocal += nTop + 1;
505                 nTop = 0;
506             }
507 
508             if (t == FrameTypeMask.LONG || t == FrameTypeMask.DOUBLE) {
509                 i++;
510             }
511         }
512 
513         return nLocal;
514     }
515 
516     /**
517      * Computes the stack size (ignores TOP types that are just after a LONG or a DOUBLE).
518      */
519     @NonNegative
520     private static int computeStackSize(@NonNull int[] stacks) {
521         int nStack = 0;
522 
523         for (int i = 0; i < stacks.length; i++) {
524             int t = stacks[i];
525             nStack++;
526 
527             if (t == FrameTypeMask.LONG || t == FrameTypeMask.DOUBLE) {
528                 i++;
529             }
530         }
531 
532         return nStack;
533     }
534 
535     private void putLocalsOrStackElements(@NonNull int[] itemIndices, @NonNegative int nItems) {
536         for (int i = 0; nItems > 0; i++, nItems--) {
537             int itemType = itemIndices[i];
538             writeFrameDefinition(itemType);
539 
540             if (itemType == FrameTypeMask.LONG || itemType == FrameTypeMask.DOUBLE) {
541                 i++;
542             }
543         }
544     }
545 
546     @NonNegative
547     @Override
548     public int getSize() {
549         return stackMap == null ? 0 : 8 + stackMap.getLength();
550     }
551 
552     @Override
553     public void put(@NonNull ByteVector out) {
554         if (stackMap != null) {
555             put(out, 2 + stackMap.getLength());
556             out.putShort(frameCount);
557             out.putByteVector(stackMap);
558         }
559     }
560 }