    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 */
    019package org.apache.bcel.verifier.structurals;
    020
    021import org.apache.bcel.generic.ReferenceType;
    022import org.apache.bcel.generic.Type;
    023import org.apache.bcel.verifier.exc.AssertionViolatedException;
    024import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
    025import org.apache.commons.lang3.ArrayFill;
    026
    027/**
    028 * This class implements an array of local variables used for symbolic JVM simulation.
    029 */
    030public class LocalVariables implements Cloneable {
    031
    032    /** The Type[] containing the local variable slots. */
    033    private final Type[] locals;
    034
    035    /**
    036     * Creates a new LocalVariables object.
    037     *
    038     * @param localVariableCount local variable count.
    039     */
    040    public LocalVariables(final int localVariableCount) {
    041        locals = ArrayFill.fill(new Type[localVariableCount], Type.UNKNOWN);
    042    }
    043
    044    /**
    045     * Returns a deep copy of this object; for example the clone operates on a new local variable array. However, the Type objects
    046     * in the array are shared.
    047     */
    048    @Override
    049    public Object clone() {
    050        final LocalVariables lvs = new LocalVariables(locals.length);
    051        System.arraycopy(this.locals, 0, lvs.locals, 0, locals.length);
    052        return lvs;
    053    }
    054
    055    /*
    056     * Fulfills the general contract of Object.equals().
    057     */
    058    @Override
    059    public boolean equals(final Object o) {
    060        if (!(o instanceof LocalVariables)) {
    061            return false;
    062        }
    063        final LocalVariables lv = (LocalVariables) o;
    064        if (this.locals.length != lv.locals.length) {
    065            return false;
    066        }
    067        for (int i = 0; i < this.locals.length; i++) {
    068            if (!this.locals[i].equals(lv.locals[i])) {
    069                // System.out.println(this.locals[i]+" is not "+lv.locals[i]);
    070                return false;
    071            }
    072        }
    073        return true;
    074    }
    075
    076    /**
    077     * Returns the type of the local variable slot index.
    078     *
    079     * @param slotIndex Slot to look up.
    080     * @return the type of the local variable slot index.
    081     */
    082    public Type get(final int slotIndex) {
    083        return locals[slotIndex];
    084    }
    085
    086    /**
    087     * Returns a (correctly typed) clone of this object. This is equivalent to ((LocalVariables) this.clone()).
    088     *
    089     * @return a (correctly typed) clone of this object.
    090     */
    091    public LocalVariables getClone() {
    092        return (LocalVariables) clone();
    093    }
    094
    095    /**
    096     * @return a hash code value for the object.
    097     */
    098    @Override
    099    public int hashCode() {
    100        return locals.length;
    101    }
    102
    103    /**
    104     * Replaces all occurrences of {@code uninitializedObjectType} in this local variables set with an "initialized"
    105     * ObjectType.
    106     *
    107     * @param uninitializedObjectType the object to match.
    108     */
    109    public void initializeObject(final UninitializedObjectType uninitializedObjectType) {
    110        for (int i = 0; i < locals.length; i++) {
    111            if (locals[i] == uninitializedObjectType) {
    112                locals[i] = uninitializedObjectType.getInitialized();
    113            }
    114        }
    115    }
    116
    117    /**
    118     * Returns the number of local variable slots.
    119     *
    120     * @return the number of local variable slots.
    121     */
    122    public int maxLocals() {
    123        return locals.length;
    124    }
    125
    126    /**
    127     * Merges two local variables sets as described in the Java Virtual Machine Specification, Second Edition, section
    128     * 4.9.2, page 146.
    129     *
    130     * @param localVariable other local variable.
    131     */
    132    public void merge(final LocalVariables localVariable) {
    133
    134        if (this.locals.length != localVariable.locals.length) {
    135            throw new AssertionViolatedException("Merging LocalVariables of different size?!? From different methods or what?!?");
    136        }
    137
    138        for (int i = 0; i < locals.length; i++) {
    139            merge(localVariable, i);
    140        }
    141    }
    142
    143    /**
    144     * Merges a single local variable.
    145     *
    146     * @see #merge(LocalVariables)
    147     */
    148    private void merge(final LocalVariables lv, final int i) {
    149        try {
    150
    151            // We won't accept an unitialized object if we know it was initialized;
    152            // compare vmspec2, 4.9.4, last paragraph.
    153            if (!(locals[i] instanceof UninitializedObjectType) && lv.locals[i] instanceof UninitializedObjectType) {
    154                throw new StructuralCodeConstraintException("Backwards branch with an uninitialized object in the local variables detected.");
    155            }
    156            // Even harder, what about _different_ uninitialized object types?!
    157            if (!locals[i].equals(lv.locals[i]) && locals[i] instanceof UninitializedObjectType && lv.locals[i] instanceof UninitializedObjectType) {
    158                throw new StructuralCodeConstraintException("Backwards branch with an uninitialized object in the local variables detected.");
    159            }
    160            // If we just didn't know that it was initialized, we have now learned.
    161            if (locals[i] instanceof UninitializedObjectType && !(lv.locals[i] instanceof UninitializedObjectType)) {
    162                locals[i] = ((UninitializedObjectType) locals[i]).getInitialized();
    163            }
    164            if (locals[i] instanceof ReferenceType && lv.locals[i] instanceof ReferenceType) {
    165                if (!locals[i].equals(lv.locals[i])) { // needed in case of two UninitializedObjectType instances
    166                    final Type sup = ((ReferenceType) locals[i]).getFirstCommonSuperclass((ReferenceType) lv.locals[i]);
    167
    168                    if (sup == null) {
    169                        // We should have checked this in Pass2!
    170                        throw new AssertionViolatedException("Could not load all the super classes of '" + locals[i] + "' and '" + lv.locals[i] + "'.");
    171                    }
    172                    locals[i] = sup;
    173                }
    174            } else if (!locals[i].equals(lv.locals[i])) {
    175                /*
    176                 * TODO if ((locals[i] instanceof org.apache.bcel.generic.ReturnaddressType) && (lv.locals[i] instanceof
    177                 * org.apache.bcel.generic.ReturnaddressType)) { //System.err.println("merging "+locals[i]+" and "+lv.locals[i]); throw
    178                 * new AssertionViolatedException("Merging different ReturnAddresses: '"+locals[i]+"' and '"+lv.locals[i]+"'."); }
    179                 */
    180                locals[i] = Type.UNKNOWN;
    181            }
    182        } catch (final ClassNotFoundException e) {
    183            // FIXME: maybe not the best way to handle this
    184            throw new AssertionViolatedException("Missing class: " + e, e);
    185        }
    186    }
    187
    188    /**
    189     * Sets a new Type for the given local variable slot.
    190     *
    191     * @param slotIndex Target slot index.
    192     * @param type Type to save at the given slot index.
    193     */
    194    public void set(final int slotIndex, final Type type) { // TODO could be package-protected?
    195        if (type == Type.BYTE || type == Type.SHORT || type == Type.BOOLEAN || type == Type.CHAR) {
    196            throw new AssertionViolatedException("LocalVariables do not know about '" + type + "'. Use Type.INT instead.");
    197        }
    198        locals[slotIndex] = type;
    199    }
    200
    201    /**
    202     * Returns a String representation of this object.
    203     */
    204    @Override
    205    public String toString() {
    206        final StringBuilder sb = new StringBuilder();
    207        for (int i = 0; i < locals.length; i++) {
    208            sb.append(Integer.toString(i));
    209            sb.append(": ");
    210            sb.append(locals[i]);
    211            sb.append("\n");
    212        }
    213        return sb.toString();
    214    }
    215}