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