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}