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