    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}