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