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) this.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}