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