001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.apache.bcel.classfile;
021
022import java.io.DataInput;
023import java.io.DataOutputStream;
024import java.io.IOException;
025import java.util.Arrays;
026
027import org.apache.bcel.Const;
028
029/**
030 * This class represents a stack map entry recording the types of local variables and the of stack items at a given
031 * byte code offset. See CLDC specification 5.3.1.2.
032 *
033 * See also https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.4
034 *
035 * <pre>
036 * union stack_map_frame {
037 *   same_frame;
038 *   same_locals_1_stack_item_frame;
039 *   same_locals_1_stack_item_frame_extended;
040 *   chop_frame;
041 *   same_frame_extended;
042 *   append_frame;
043 *   full_frame;
044 * }
045 * </pre>
046 *
047 * @see StackMap
048 * @see StackMapType
049 */
050public final class StackMapEntry implements Node, Cloneable {
051
052    static final StackMapEntry[] EMPTY_ARRAY = {};
053
054    private int frameType;
055    private int byteCodeOffset;
056    private StackMapType[] typesOfLocals;
057    private StackMapType[] typesOfStackItems;
058    private ConstantPool constantPool;
059
060    /**
061     * Constructs object from input stream.
062     *
063     * @param dataInput Input stream.
064     * @throws IOException if an I/O error occurs.
065     */
066    StackMapEntry(final DataInput dataInput, final ConstantPool constantPool) throws IOException {
067        this(dataInput.readByte() & 0xFF, -1, null, null, constantPool);
068
069        if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
070            byteCodeOffset = frameType - Const.SAME_FRAME;
071        } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
072            byteCodeOffset = frameType - Const.SAME_LOCALS_1_STACK_ITEM_FRAME;
073            typesOfStackItems = new StackMapType[] { new StackMapType(dataInput, constantPool) };
074        } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
075            byteCodeOffset = dataInput.readUnsignedShort();
076            typesOfStackItems = new StackMapType[] { new StackMapType(dataInput, constantPool) };
077        } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX || frameType == Const.SAME_FRAME_EXTENDED) {
078            byteCodeOffset = dataInput.readUnsignedShort();
079        } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
080            byteCodeOffset = dataInput.readUnsignedShort();
081            final int numberOfLocals = frameType - 251;
082            typesOfLocals = new StackMapType[numberOfLocals];
083            for (int i = 0; i < numberOfLocals; i++) {
084                typesOfLocals[i] = new StackMapType(dataInput, constantPool);
085            }
086        } else if (frameType == Const.FULL_FRAME) {
087            byteCodeOffset = dataInput.readUnsignedShort();
088            final int numberOfLocals = dataInput.readUnsignedShort();
089            typesOfLocals = new StackMapType[numberOfLocals];
090            for (int i = 0; i < numberOfLocals; i++) {
091                typesOfLocals[i] = new StackMapType(dataInput, constantPool);
092            }
093            final int numberOfStackItems = dataInput.readUnsignedShort();
094            typesOfStackItems = new StackMapType[numberOfStackItems];
095            for (int i = 0; i < numberOfStackItems; i++) {
096                typesOfStackItems[i] = new StackMapType(dataInput, constantPool);
097            }
098        } else {
099            /* 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}