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