View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.bcel.classfile;
21  
22  import java.io.DataInput;
23  import java.io.DataOutputStream;
24  import java.io.IOException;
25  import java.util.Arrays;
26  
27  import org.apache.bcel.Const;
28  
29  /**
30   * This class represents a stack map entry recording the types of local variables and the of stack items at a given
31   * byte code offset. See CLDC specification 5.3.1.2.
32   *
33   * See also https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.4
34   *
35   * <pre>
36   * union stack_map_frame {
37   *   same_frame;
38   *   same_locals_1_stack_item_frame;
39   *   same_locals_1_stack_item_frame_extended;
40   *   chop_frame;
41   *   same_frame_extended;
42   *   append_frame;
43   *   full_frame;
44   * }
45   * </pre>
46   * @see StackMap
47   * @see StackMapType
48   */
49  public final class StackMapEntry implements Node, Cloneable {
50  
51      static final StackMapEntry[] EMPTY_ARRAY = {};
52  
53      private int frameType;
54      private int byteCodeOffset;
55      private StackMapType[] typesOfLocals;
56      private StackMapType[] typesOfStackItems;
57      private ConstantPool constantPool;
58  
59      /**
60       * Constructs object from input stream.
61       *
62       * @param dataInput Input stream
63       * @throws IOException if an I/O error occurs.
64       */
65      StackMapEntry(final DataInput dataInput, final ConstantPool constantPool) throws IOException {
66          this(dataInput.readByte() & 0xFF, -1, null, null, constantPool);
67  
68          if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
69              byteCodeOffset = frameType - Const.SAME_FRAME;
70          } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
71              byteCodeOffset = frameType - Const.SAME_LOCALS_1_STACK_ITEM_FRAME;
72              typesOfStackItems = new StackMapType[] { new StackMapType(dataInput, constantPool) };
73          } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
74              byteCodeOffset = dataInput.readUnsignedShort();
75              typesOfStackItems = new StackMapType[] { new StackMapType(dataInput, constantPool) };
76          } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX || frameType == Const.SAME_FRAME_EXTENDED) {
77              byteCodeOffset = dataInput.readUnsignedShort();
78          } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
79              byteCodeOffset = dataInput.readUnsignedShort();
80              final int numberOfLocals = frameType - 251;
81              typesOfLocals = new StackMapType[numberOfLocals];
82              for (int i = 0; i < numberOfLocals; i++) {
83                  typesOfLocals[i] = new StackMapType(dataInput, constantPool);
84              }
85          } else if (frameType == Const.FULL_FRAME) {
86              byteCodeOffset = dataInput.readUnsignedShort();
87              final int numberOfLocals = dataInput.readUnsignedShort();
88              typesOfLocals = new StackMapType[numberOfLocals];
89              for (int i = 0; i < numberOfLocals; i++) {
90                  typesOfLocals[i] = new StackMapType(dataInput, constantPool);
91              }
92              final int numberOfStackItems = dataInput.readUnsignedShort();
93              typesOfStackItems = new StackMapType[numberOfStackItems];
94              for (int i = 0; i < numberOfStackItems; i++) {
95                  typesOfStackItems[i] = new StackMapType(dataInput, constantPool);
96              }
97          } else {
98              /* Can't happen */
99              throw new ClassFormatException("Invalid frame type found while parsing stack map table: " + frameType);
100         }
101     }
102 
103     /**
104      * DO NOT USE
105      *
106      * @param byteCodeOffset
107      * @param numberOfLocals NOT USED
108      * @param typesOfLocals array of {@link StackMapType}s of locals
109      * @param numberOfStackItems NOT USED
110      * @param typesOfStackItems array ot {@link StackMapType}s of stack items
111      * @param constantPool the constant pool
112      * @deprecated Since 6.0, use {@link #StackMapEntry(int, int, StackMapType[], StackMapType[], ConstantPool)} instead
113      */
114     @java.lang.Deprecated
115     public StackMapEntry(final int byteCodeOffset, final int numberOfLocals, final StackMapType[] typesOfLocals, final int numberOfStackItems,
116         final StackMapType[] typesOfStackItems, final ConstantPool constantPool) {
117         this.byteCodeOffset = byteCodeOffset;
118         this.typesOfLocals = typesOfLocals != null ? typesOfLocals : StackMapType.EMPTY_ARRAY;
119         this.typesOfStackItems = typesOfStackItems != null ? typesOfStackItems : StackMapType.EMPTY_ARRAY;
120         this.constantPool = constantPool;
121         if (numberOfLocals < 0) {
122             throw new IllegalArgumentException("numberOfLocals < 0");
123         }
124         if (numberOfStackItems < 0) {
125             throw new IllegalArgumentException("numberOfStackItems < 0");
126         }
127     }
128 
129     /**
130      * Create an instance
131      *
132      * @param tag the frameType to use
133      * @param byteCodeOffset
134      * @param typesOfLocals array of {@link StackMapType}s of locals
135      * @param typesOfStackItems array ot {@link StackMapType}s of stack items
136      * @param constantPool the constant pool
137      */
138     public StackMapEntry(final int tag, final int byteCodeOffset, final StackMapType[] typesOfLocals, final StackMapType[] typesOfStackItems,
139         final ConstantPool constantPool) {
140         this.frameType = tag;
141         this.byteCodeOffset = byteCodeOffset;
142         this.typesOfLocals = typesOfLocals != null ? typesOfLocals : StackMapType.EMPTY_ARRAY;
143         this.typesOfStackItems = typesOfStackItems != null ? typesOfStackItems : StackMapType.EMPTY_ARRAY;
144         this.constantPool = constantPool;
145     }
146 
147     /**
148      * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
149      * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
150      *
151      * @param v Visitor object
152      */
153     @Override
154     public void accept(final Visitor v) {
155         v.visitStackMapEntry(this);
156     }
157 
158     /**
159      * @return deep copy of this object
160      */
161     public StackMapEntry copy() {
162         final StackMapEntry e;
163         try {
164             e = (StackMapEntry) clone();
165         } catch (final CloneNotSupportedException ex) {
166             throw new UnsupportedOperationException("Clone Not Supported", ex);
167         }
168 
169         e.typesOfLocals = new StackMapType[typesOfLocals.length];
170         Arrays.setAll(e.typesOfLocals, i -> typesOfLocals[i].copy());
171         e.typesOfStackItems = new StackMapType[typesOfStackItems.length];
172         Arrays.setAll(e.typesOfStackItems, i -> typesOfStackItems[i].copy());
173         return e;
174     }
175 
176     /**
177      * Dump stack map entry
178      *
179      * @param file Output file stream
180      * @throws IOException if an I/O error occurs.
181      */
182     public void dump(final DataOutputStream file) throws IOException {
183         file.write(frameType);
184         if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
185             typesOfStackItems[0].dump(file);
186         } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
187             file.writeShort(byteCodeOffset);
188             typesOfStackItems[0].dump(file);
189         } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX || frameType == Const.SAME_FRAME_EXTENDED) {
190             file.writeShort(byteCodeOffset);
191         } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
192             file.writeShort(byteCodeOffset);
193             for (final StackMapType type : typesOfLocals) {
194                 type.dump(file);
195             }
196         } else if (frameType == Const.FULL_FRAME) {
197             file.writeShort(byteCodeOffset);
198             file.writeShort(typesOfLocals.length);
199             for (final StackMapType type : typesOfLocals) {
200                 type.dump(file);
201             }
202             file.writeShort(typesOfStackItems.length);
203             for (final StackMapType type : typesOfStackItems) {
204                 type.dump(file);
205             }
206         } else if (!(frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX)) {
207             /* Can't happen */
208             throw new ClassFormatException("Invalid Stack map table tag: " + frameType);
209         }
210     }
211 
212     public int getByteCodeOffset() {
213         return byteCodeOffset;
214     }
215 
216     /**
217      * @return Constant pool used by this object.
218      */
219     public ConstantPool getConstantPool() {
220         return constantPool;
221     }
222 
223     public int getFrameType() {
224         return frameType;
225     }
226 
227     /**
228      * Calculate stack map entry size
229      */
230     int getMapEntrySize() {
231         if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
232             return 1;
233         }
234         if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
235             return 1 + (typesOfStackItems[0].hasIndex() ? 3 : 1);
236         }
237         if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
238             return 3 + (typesOfStackItems[0].hasIndex() ? 3 : 1);
239         }
240         if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX || frameType == Const.SAME_FRAME_EXTENDED) {
241             return 3;
242         }
243         if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
244             int len = 3;
245             for (final StackMapType typesOfLocal : typesOfLocals) {
246                 len += typesOfLocal.hasIndex() ? 3 : 1;
247             }
248             return len;
249         }
250         if (frameType != Const.FULL_FRAME) {
251             throw new IllegalStateException("Invalid StackMap frameType: " + frameType);
252         }
253         int len = 7;
254         for (final StackMapType typesOfLocal : typesOfLocals) {
255             len += typesOfLocal.hasIndex() ? 3 : 1;
256         }
257         for (final StackMapType typesOfStackItem : typesOfStackItems) {
258             len += typesOfStackItem.hasIndex() ? 3 : 1;
259         }
260         return len;
261     }
262 
263     public int getNumberOfLocals() {
264         return typesOfLocals.length;
265     }
266 
267     public int getNumberOfStackItems() {
268         return typesOfStackItems.length;
269     }
270 
271     public StackMapType[] getTypesOfLocals() {
272         return typesOfLocals;
273     }
274 
275     public StackMapType[] getTypesOfStackItems() {
276         return typesOfStackItems;
277     }
278 
279     private boolean invalidFrameType(final int f) {
280         // @formatter:off
281         return f != Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED
282             && !(f >= Const.CHOP_FRAME && f <= Const.CHOP_FRAME_MAX)
283             && f != Const.SAME_FRAME_EXTENDED
284             && !(f >= Const.APPEND_FRAME && f <= Const.APPEND_FRAME_MAX)
285             && f != Const.FULL_FRAME;
286         // @formatter:on
287     }
288 
289     public void setByteCodeOffset(final int newOffset) {
290         if (newOffset < 0 || newOffset > 32767) {
291             throw new IllegalArgumentException("Invalid StackMap offset: " + newOffset);
292         }
293 
294         if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
295             if (newOffset > Const.SAME_FRAME_MAX) {
296                 frameType = Const.SAME_FRAME_EXTENDED;
297             } else {
298                 frameType = newOffset;
299             }
300         } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
301             if (newOffset > Const.SAME_FRAME_MAX) {
302                 frameType = Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
303             } else {
304                 frameType = Const.SAME_LOCALS_1_STACK_ITEM_FRAME + newOffset;
305             }
306         } else if (invalidFrameType(frameType)) {
307             throw new IllegalStateException("Invalid StackMap frameType: " + frameType);
308         }
309         byteCodeOffset = newOffset;
310     }
311 
312     /**
313      * @param constantPool Constant pool to be used for this object.
314      */
315     public void setConstantPool(final ConstantPool constantPool) {
316         this.constantPool = constantPool;
317     }
318 
319     public void setFrameType(final int ft) {
320         if (ft >= Const.SAME_FRAME && ft <= Const.SAME_FRAME_MAX) {
321             byteCodeOffset = ft - Const.SAME_FRAME;
322         } else if (ft >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && ft <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
323             byteCodeOffset = ft - Const.SAME_LOCALS_1_STACK_ITEM_FRAME;
324         } else if (invalidFrameType(ft)) {
325             throw new IllegalArgumentException("Invalid StackMap frameType");
326         }
327         frameType = ft;
328     }
329 
330     /**
331      *
332      * @deprecated since 6.0
333      */
334     @java.lang.Deprecated
335     public void setNumberOfLocals(final int n) { // TODO unused
336     }
337 
338     /**
339      *
340      * @deprecated since 6.0
341      */
342     @java.lang.Deprecated
343     public void setNumberOfStackItems(final int n) { // TODO unused
344     }
345 
346     public void setTypesOfLocals(final StackMapType[] types) {
347         typesOfLocals = types != null ? types : StackMapType.EMPTY_ARRAY;
348     }
349 
350     public void setTypesOfStackItems(final StackMapType[] types) {
351         typesOfStackItems = types != null ? types : StackMapType.EMPTY_ARRAY;
352     }
353 
354     /**
355      * @return String representation.
356      */
357     @Override
358     public String toString() {
359         final StringBuilder buf = new StringBuilder(64);
360         buf.append("(");
361         if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
362             buf.append("SAME");
363         } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
364             buf.append("SAME_LOCALS_1_STACK");
365         } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
366             buf.append("SAME_LOCALS_1_STACK_EXTENDED");
367         } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX) {
368             buf.append("CHOP ").append(String.valueOf(251 - frameType));
369         } else if (frameType == Const.SAME_FRAME_EXTENDED) {
370             buf.append("SAME_EXTENDED");
371         } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
372             buf.append("APPEND ").append(String.valueOf(frameType - 251));
373         } else if (frameType == Const.FULL_FRAME) {
374             buf.append("FULL");
375         } else {
376             buf.append("UNKNOWN (").append(frameType).append(")");
377         }
378         buf.append(", offset delta=").append(byteCodeOffset);
379         if (typesOfLocals.length > 0) {
380             buf.append(", locals={");
381             for (int i = 0; i < typesOfLocals.length; i++) {
382                 buf.append(typesOfLocals[i]);
383                 if (i < typesOfLocals.length - 1) {
384                     buf.append(", ");
385                 }
386             }
387             buf.append("}");
388         }
389         if (typesOfStackItems.length > 0) {
390             buf.append(", stack items={");
391             for (int i = 0; i < typesOfStackItems.length; i++) {
392                 buf.append(typesOfStackItems[i]);
393                 if (i < typesOfStackItems.length - 1) {
394                     buf.append(", ");
395                 }
396             }
397             buf.append("}");
398         }
399         buf.append(")");
400         return buf.toString();
401     }
402 
403     /**
404      * Update the distance (as an offset delta) from this StackMap entry to the next. Note that this might cause the
405      * frame type to change. Note also that delta may be negative.
406      *
407      * @param delta offset delta
408      */
409     public void updateByteCodeOffset(final int delta) {
410         setByteCodeOffset(byteCodeOffset + delta);
411     }
412 }