001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017
018package org.apache.bcel.classfile;
019
020import java.io.DataInput;
021import java.io.DataOutputStream;
022import java.io.IOException;
023import java.util.Arrays;
024
025import org.apache.bcel.Const;
026
027/**
028 * This class represents a stack map entry recording the types of local variables and the of stack items at a given
029 * byte code offset. See CLDC specification 5.3.1.2
030 *
031 * @see StackMap
032 * @see StackMapType
033 */
034public final class StackMapEntry implements Node, Cloneable {
035
036    /**
037     * Empty array.
038     */
039    private static final StackMapType[] EMPTY_STACK_MAP_TYPE_ARRAY = {};
040
041    private int frameType;
042    private int byteCodeOffset;
043    private StackMapType[] typesOfLocals;
044    private StackMapType[] typesOfStackItems;
045    private ConstantPool constantPool;
046
047    /**
048     * Construct object from input stream.
049     *
050     * @param input Input stream
051     * @throws IOException if an I/O error occurs.
052     */
053    StackMapEntry(final DataInput input, final ConstantPool constantPool) throws IOException {
054        this(input.readByte() & 0xFF, -1, null, null, constantPool);
055
056        if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
057            byteCodeOffset = frameType - Const.SAME_FRAME;
058        } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
059            byteCodeOffset = frameType - Const.SAME_LOCALS_1_STACK_ITEM_FRAME;
060            typesOfStackItems = new StackMapType[1];
061            typesOfStackItems[0] = new StackMapType(input, constantPool);
062        } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
063            byteCodeOffset = input.readShort();
064            typesOfStackItems = new StackMapType[1];
065            typesOfStackItems[0] = new StackMapType(input, constantPool);
066        } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX) {
067            byteCodeOffset = input.readShort();
068        } else if (frameType == Const.SAME_FRAME_EXTENDED) {
069            byteCodeOffset = input.readShort();
070        } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
071            byteCodeOffset = input.readShort();
072            final int numberOfLocals = frameType - 251;
073            typesOfLocals = new StackMapType[numberOfLocals];
074            for (int i = 0; i < numberOfLocals; i++) {
075                typesOfLocals[i] = new StackMapType(input, constantPool);
076            }
077        } else if (frameType == Const.FULL_FRAME) {
078            byteCodeOffset = input.readShort();
079            final int numberOfLocals = input.readShort();
080            typesOfLocals = new StackMapType[numberOfLocals];
081            for (int i = 0; i < numberOfLocals; i++) {
082                typesOfLocals[i] = new StackMapType(input, constantPool);
083            }
084            final int numberOfStackItems = input.readShort();
085            typesOfStackItems = new StackMapType[numberOfStackItems];
086            for (int i = 0; i < numberOfStackItems; i++) {
087                typesOfStackItems[i] = new StackMapType(input, constantPool);
088            }
089        } else {
090            /* Can't happen */
091            throw new ClassFormatException("Invalid frame type found while parsing stack map table: " + frameType);
092        }
093    }
094
095    /**
096     * DO NOT USE
097     *
098     * @param byteCodeOffset
099     * @param numberOfLocals NOT USED
100     * @param typesOfLocals array of {@link StackMapType}s of locals
101     * @param numberOfStackItems NOT USED
102     * @param typesOfStackItems array ot {@link StackMapType}s of stack items
103     * @param constantPool the constant pool
104     * @deprecated Since 6.0, use {@link #StackMapEntry(int, int, StackMapType[], StackMapType[], ConstantPool)} instead
105     */
106    @java.lang.Deprecated
107    public StackMapEntry(final int byteCodeOffset, final int numberOfLocals, final StackMapType[] typesOfLocals, final int numberOfStackItems,
108        final StackMapType[] typesOfStackItems, final ConstantPool constantPool) {
109        this.byteCodeOffset = byteCodeOffset;
110        this.typesOfLocals = typesOfLocals != null ? typesOfLocals : EMPTY_STACK_MAP_TYPE_ARRAY;
111        this.typesOfStackItems = typesOfStackItems != null ? typesOfStackItems : EMPTY_STACK_MAP_TYPE_ARRAY;
112        this.constantPool = constantPool;
113        if (numberOfLocals < 0) {
114            throw new IllegalArgumentException("numberOfLocals < 0");
115        }
116        if (numberOfStackItems < 0) {
117            throw new IllegalArgumentException("numberOfStackItems < 0");
118        }
119    }
120
121    /**
122     * Create an instance
123     *
124     * @param tag the frameType to use
125     * @param byteCodeOffset
126     * @param typesOfLocals array of {@link StackMapType}s of locals
127     * @param typesOfStackItems array ot {@link StackMapType}s of stack items
128     * @param constantPool the constant pool
129     */
130    public StackMapEntry(final int tag, final int byteCodeOffset, final StackMapType[] typesOfLocals, final StackMapType[] typesOfStackItems,
131        final ConstantPool constantPool) {
132        this.frameType = tag;
133        this.byteCodeOffset = byteCodeOffset;
134        this.typesOfLocals = typesOfLocals != null ? typesOfLocals : EMPTY_STACK_MAP_TYPE_ARRAY;
135        this.typesOfStackItems = typesOfStackItems != null ? typesOfStackItems : EMPTY_STACK_MAP_TYPE_ARRAY;
136        this.constantPool = constantPool;
137    }
138
139    /**
140     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
141     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
142     *
143     * @param v Visitor object
144     */
145    @Override
146    public void accept(final Visitor v) {
147        v.visitStackMapEntry(this);
148    }
149
150    /**
151     * @return deep copy of this object
152     */
153    public StackMapEntry copy() {
154        StackMapEntry e;
155        try {
156            e = (StackMapEntry) clone();
157        } catch (final CloneNotSupportedException ex) {
158            throw new Error("Clone Not Supported");
159        }
160
161        e.typesOfLocals = new StackMapType[typesOfLocals.length];
162        Arrays.setAll(e.typesOfLocals, i -> typesOfLocals[i].copy());
163        e.typesOfStackItems = new StackMapType[typesOfStackItems.length];
164        Arrays.setAll(e.typesOfStackItems, i -> typesOfStackItems[i].copy());
165        return e;
166    }
167
168    /**
169     * Dump stack map entry
170     *
171     * @param file Output file stream
172     * @throws IOException if an I/O error occurs.
173     */
174    public void dump(final DataOutputStream file) throws IOException {
175        file.write(frameType);
176        if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
177            typesOfStackItems[0].dump(file);
178        } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
179            file.writeShort(byteCodeOffset);
180            typesOfStackItems[0].dump(file);
181        } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX) {
182            file.writeShort(byteCodeOffset);
183        } else if (frameType == Const.SAME_FRAME_EXTENDED) {
184            file.writeShort(byteCodeOffset);
185        } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
186            file.writeShort(byteCodeOffset);
187            for (final StackMapType type : typesOfLocals) {
188                type.dump(file);
189            }
190        } else if (frameType == Const.FULL_FRAME) {
191            file.writeShort(byteCodeOffset);
192            file.writeShort(typesOfLocals.length);
193            for (final StackMapType type : typesOfLocals) {
194                type.dump(file);
195            }
196            file.writeShort(typesOfStackItems.length);
197            for (final StackMapType type : typesOfStackItems) {
198                type.dump(file);
199            }
200        } else if (!(frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX)) {
201            /* Can't happen */
202            throw new ClassFormatException("Invalid Stack map table tag: " + frameType);
203        }
204    }
205
206    public int getByteCodeOffset() {
207        return byteCodeOffset;
208    }
209
210    /**
211     * @return Constant pool used by this object.
212     */
213    public ConstantPool getConstantPool() {
214        return constantPool;
215    }
216
217    public int getFrameType() {
218        return frameType;
219    }
220
221    /**
222     * Calculate stack map entry size
223     *
224     */
225    int getMapEntrySize() {
226        if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
227            return 1;
228        }
229        if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
230            return 1 + (typesOfStackItems[0].hasIndex() ? 3 : 1);
231        }
232        if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
233            return 3 + (typesOfStackItems[0].hasIndex() ? 3 : 1);
234        }
235        if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX || frameType == Const.SAME_FRAME_EXTENDED) {
236            return 3;
237        }
238        if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
239            int len = 3;
240            for (final StackMapType typesOfLocal : typesOfLocals) {
241                len += typesOfLocal.hasIndex() ? 3 : 1;
242            }
243            return len;
244        }
245        if (frameType != Const.FULL_FRAME) {
246            throw new IllegalStateException("Invalid StackMap frameType: " + frameType);
247        }
248        int len = 7;
249        for (final StackMapType typesOfLocal : typesOfLocals) {
250            len += typesOfLocal.hasIndex() ? 3 : 1;
251        }
252        for (final StackMapType typesOfStackItem : typesOfStackItems) {
253            len += typesOfStackItem.hasIndex() ? 3 : 1;
254        }
255        return len;
256    }
257
258    public int getNumberOfLocals() {
259        return typesOfLocals.length;
260    }
261
262    public int getNumberOfStackItems() {
263        return typesOfStackItems.length;
264    }
265
266    public StackMapType[] getTypesOfLocals() {
267        return typesOfLocals;
268    }
269
270    public StackMapType[] getTypesOfStackItems() {
271        return typesOfStackItems;
272    }
273
274    private boolean invalidFrameType(final int f) {
275        // @formatter:off
276        return f != Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED
277            && !(f >= Const.CHOP_FRAME && f <= Const.CHOP_FRAME_MAX)
278            && f != Const.SAME_FRAME_EXTENDED
279            && !(f >= Const.APPEND_FRAME && f <= Const.APPEND_FRAME_MAX)
280            && f != Const.FULL_FRAME;
281        // @formatter:on
282    }
283
284    public void setByteCodeOffset(final int newOffset) {
285        if (newOffset < 0 || newOffset > 32767) {
286            throw new IllegalArgumentException("Invalid StackMap offset: " + newOffset);
287        }
288
289        if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
290            if (newOffset > Const.SAME_FRAME_MAX) {
291                frameType = Const.SAME_FRAME_EXTENDED;
292            } else {
293                frameType = newOffset;
294            }
295        } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
296            if (newOffset > Const.SAME_FRAME_MAX) {
297                frameType = Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
298            } else {
299                frameType = Const.SAME_LOCALS_1_STACK_ITEM_FRAME + newOffset;
300            }
301        } else if (invalidFrameType(frameType)) {
302            throw new IllegalStateException("Invalid StackMap frameType: " + frameType);
303        }
304        byteCodeOffset = newOffset;
305    }
306
307    /**
308     * @param constantPool Constant pool to be used for this object.
309     */
310    public void setConstantPool(final ConstantPool constantPool) {
311        this.constantPool = constantPool;
312    }
313
314    public void setFrameType(final int ft) {
315        if (ft >= Const.SAME_FRAME && ft <= Const.SAME_FRAME_MAX) {
316            byteCodeOffset = ft - Const.SAME_FRAME;
317        } else if (ft >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && ft <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
318            byteCodeOffset = ft - Const.SAME_LOCALS_1_STACK_ITEM_FRAME;
319        } else if (invalidFrameType(ft)) {
320            throw new IllegalArgumentException("Invalid StackMap frameType");
321        }
322        frameType = ft;
323    }
324
325    /**
326     *
327     * @deprecated since 6.0
328     */
329    @java.lang.Deprecated
330    public void setNumberOfLocals(final int n) { // TODO unused
331    }
332
333    /**
334     *
335     * @deprecated since 6.0
336     */
337    @java.lang.Deprecated
338    public void setNumberOfStackItems(final int n) { // TODO unused
339    }
340
341    public void setTypesOfLocals(final StackMapType[] types) {
342        typesOfLocals = types != null ? types : EMPTY_STACK_MAP_TYPE_ARRAY;
343    }
344
345    public void setTypesOfStackItems(final StackMapType[] types) {
346        typesOfStackItems = types != null ? types : EMPTY_STACK_MAP_TYPE_ARRAY;
347    }
348
349    /**
350     * @return String representation.
351     */
352    @Override
353    public String toString() {
354        final StringBuilder buf = new StringBuilder(64);
355        buf.append("(");
356        if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
357            buf.append("SAME");
358        } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
359            buf.append("SAME_LOCALS_1_STACK");
360        } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
361            buf.append("SAME_LOCALS_1_STACK_EXTENDED");
362        } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX) {
363            buf.append("CHOP ").append(String.valueOf(251 - frameType));
364        } else if (frameType == Const.SAME_FRAME_EXTENDED) {
365            buf.append("SAME_EXTENDED");
366        } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
367            buf.append("APPEND ").append(String.valueOf(frameType - 251));
368        } else if (frameType == Const.FULL_FRAME) {
369            buf.append("FULL");
370        } else {
371            buf.append("UNKNOWN (").append(frameType).append(")");
372        }
373        buf.append(", offset delta=").append(byteCodeOffset);
374        if (typesOfLocals.length > 0) {
375            buf.append(", locals={");
376            for (int i = 0; i < typesOfLocals.length; i++) {
377                buf.append(typesOfLocals[i]);
378                if (i < typesOfLocals.length - 1) {
379                    buf.append(", ");
380                }
381            }
382            buf.append("}");
383        }
384        if (typesOfStackItems.length > 0) {
385            buf.append(", stack items={");
386            for (int i = 0; i < typesOfStackItems.length; i++) {
387                buf.append(typesOfStackItems[i]);
388                if (i < typesOfStackItems.length - 1) {
389                    buf.append(", ");
390                }
391            }
392            buf.append("}");
393        }
394        buf.append(")");
395        return buf.toString();
396    }
397
398    /**
399     * Update the distance (as an offset delta) from this StackMap entry to the next. Note that this might cause the
400     * frame type to change. Note also that delta may be negative.
401     *
402     * @param delta offset delta
403     */
404    public void updateByteCodeOffset(final int delta) {
405        setByteCodeOffset(byteCodeOffset + delta);
406    }
407}