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 * @see StackMap
047 * @see StackMapType
048 */
049public final class StackMapEntry implements Node, Cloneable {
050
051    static final StackMapEntry[] EMPTY_ARRAY = {};
052
053    private int frameType;
054    private int byteCodeOffset;
055    private StackMapType[] typesOfLocals;
056    private StackMapType[] typesOfStackItems;
057    private ConstantPool constantPool;
058
059    /**
060     * Constructs object from input stream.
061     *
062     * @param dataInput Input stream
063     * @throws IOException if an I/O error occurs.
064     */
065    StackMapEntry(final DataInput dataInput, final ConstantPool constantPool) throws IOException {
066        this(dataInput.readByte() & 0xFF, -1, null, null, constantPool);
067
068        if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
069            byteCodeOffset = frameType - Const.SAME_FRAME;
070        } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
071            byteCodeOffset = frameType - Const.SAME_LOCALS_1_STACK_ITEM_FRAME;
072            typesOfStackItems = new StackMapType[] { new StackMapType(dataInput, constantPool) };
073        } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
074            byteCodeOffset = dataInput.readUnsignedShort();
075            typesOfStackItems = new StackMapType[] { new StackMapType(dataInput, constantPool) };
076        } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX || frameType == Const.SAME_FRAME_EXTENDED) {
077            byteCodeOffset = dataInput.readUnsignedShort();
078        } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
079            byteCodeOffset = dataInput.readUnsignedShort();
080            final int numberOfLocals = frameType - 251;
081            typesOfLocals = new StackMapType[numberOfLocals];
082            for (int i = 0; i < numberOfLocals; i++) {
083                typesOfLocals[i] = new StackMapType(dataInput, constantPool);
084            }
085        } else if (frameType == Const.FULL_FRAME) {
086            byteCodeOffset = dataInput.readUnsignedShort();
087            final int numberOfLocals = dataInput.readUnsignedShort();
088            typesOfLocals = new StackMapType[numberOfLocals];
089            for (int i = 0; i < numberOfLocals; i++) {
090                typesOfLocals[i] = new StackMapType(dataInput, constantPool);
091            }
092            final int numberOfStackItems = dataInput.readUnsignedShort();
093            typesOfStackItems = new StackMapType[numberOfStackItems];
094            for (int i = 0; i < numberOfStackItems; i++) {
095                typesOfStackItems[i] = new StackMapType(dataInput, constantPool);
096            }
097        } else {
098            /* Can't happen */
099            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}