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