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;
028import org.apache.bcel.util.Args;
029
030/**
031 * This class represents a stack map entry recording the types of local variables and the of stack items at a given
032 * byte code offset. See CLDC specification 5.3.1.2.
033 *
034 * See also https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.4
035 *
036 * <pre>
037 * union stack_map_frame {
038 *   same_frame;
039 *   same_locals_1_stack_item_frame;
040 *   same_locals_1_stack_item_frame_extended;
041 *   chop_frame;
042 *   same_frame_extended;
043 *   append_frame;
044 *   full_frame;
045 * }
046 * </pre>
047 *
048 * @see StackMap
049 * @see StackMapType
050 */
051public final class StackMapEntry implements Node, Cloneable {
052
053    static final StackMapEntry[] EMPTY_ARRAY = {};
054
055    private int frameType;
056    private int byteCodeOffset;
057    private StackMapType[] typesOfLocals;
058    private StackMapType[] typesOfStackItems;
059    private ConstantPool constantPool;
060
061    /**
062     * Constructs object from input stream.
063     *
064     * @param dataInput Input stream.
065     * @throws IOException Thrown if an I/O error occurs.
066     */
067    StackMapEntry(final DataInput dataInput, final ConstantPool constantPool) throws IOException {
068        this(dataInput.readByte() & 0xFF, -1, null, null, constantPool);
069
070        if (frameType >= Const.SAME_FRAME && frameType <= Const.SAME_FRAME_MAX) {
071            byteCodeOffset = frameType - Const.SAME_FRAME;
072        } else if (frameType >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME && frameType <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
073            byteCodeOffset = frameType - Const.SAME_LOCALS_1_STACK_ITEM_FRAME;
074            typesOfStackItems = new StackMapType[] { new StackMapType(dataInput, constantPool) };
075        } else if (frameType == Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
076            byteCodeOffset = dataInput.readUnsignedShort();
077            typesOfStackItems = new StackMapType[] { new StackMapType(dataInput, constantPool) };
078        } else if (frameType >= Const.CHOP_FRAME && frameType <= Const.CHOP_FRAME_MAX || frameType == Const.SAME_FRAME_EXTENDED) {
079            byteCodeOffset = dataInput.readUnsignedShort();
080        } else if (frameType >= Const.APPEND_FRAME && frameType <= Const.APPEND_FRAME_MAX) {
081            byteCodeOffset = dataInput.readUnsignedShort();
082            final int numberOfLocals = frameType - 251;
083            typesOfLocals = new StackMapType[numberOfLocals];
084            for (int i = 0; i < numberOfLocals; i++) {
085                typesOfLocals[i] = new StackMapType(dataInput, constantPool);
086            }
087        } else if (frameType == Const.FULL_FRAME) {
088            byteCodeOffset = dataInput.readUnsignedShort();
089            final int numberOfLocals = dataInput.readUnsignedShort();
090            typesOfLocals = new StackMapType[numberOfLocals];
091            for (int i = 0; i < numberOfLocals; i++) {
092                typesOfLocals[i] = new StackMapType(dataInput, constantPool);
093            }
094            final int numberOfStackItems = dataInput.readUnsignedShort();
095            typesOfStackItems = new StackMapType[numberOfStackItems];
096            for (int i = 0; i < numberOfStackItems; i++) {
097                typesOfStackItems[i] = new StackMapType(dataInput, constantPool);
098            }
099        } 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}