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 java.util.Arrays;
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 simulation.
028 */
029public class LocalVariables implements Cloneable {
030
031    /** The Type[] containing the local variable slots. */
032    private final Type[] locals;
033
034    /**
035     * Creates a new LocalVariables object.
036     *
037     * @param localVariableCount local variable count.
038     */
039    public LocalVariables(final int localVariableCount) {
040        locals = new Type[localVariableCount];
041        Arrays.fill(locals, Type.UNKNOWN);
042    }
043
044    /**
045     * Returns a deep copy of this object; i.e. 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) this.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}