View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.bcel.verifier.structurals;
18  
19  import org.apache.bcel.generic.ReferenceType;
20  import org.apache.bcel.generic.Type;
21  import org.apache.bcel.verifier.exc.AssertionViolatedException;
22  import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
23  import org.apache.commons.lang3.ArrayFill;
24  
25  /**
26   * This class implements an array of local variables used for symbolic JVM simulation.
27   */
28  public class LocalVariables implements Cloneable {
29  
30      /** The Type[] containing the local variable slots. */
31      private final Type[] locals;
32  
33      /**
34       * Creates a new LocalVariables object.
35       *
36       * @param localVariableCount local variable count.
37       */
38      public LocalVariables(final int localVariableCount) {
39          locals = ArrayFill.fill(new Type[localVariableCount], Type.UNKNOWN);
40      }
41  
42      /**
43       * Returns a deep copy of this object; i.e. the clone operates on a new local variable array. However, the Type objects
44       * in the array are shared.
45       */
46      @Override
47      public Object clone() {
48          final LocalVariables lvs = new LocalVariables(locals.length);
49          System.arraycopy(this.locals, 0, lvs.locals, 0, locals.length);
50          return lvs;
51      }
52  
53      /*
54       * Fulfills the general contract of Object.equals().
55       */
56      @Override
57      public boolean equals(final Object o) {
58          if (!(o instanceof LocalVariables)) {
59              return false;
60          }
61          final LocalVariables lv = (LocalVariables) o;
62          if (this.locals.length != lv.locals.length) {
63              return false;
64          }
65          for (int i = 0; i < this.locals.length; i++) {
66              if (!this.locals[i].equals(lv.locals[i])) {
67                  // System.out.println(this.locals[i]+" is not "+lv.locals[i]);
68                  return false;
69              }
70          }
71          return true;
72      }
73  
74      /**
75       * Returns the type of the local variable slot index.
76       *
77       * @param slotIndex Slot to look up.
78       * @return the type of the local variable slot index.
79       */
80      public Type get(final int slotIndex) {
81          return locals[slotIndex];
82      }
83  
84      /**
85       * Returns a (correctly typed) clone of this object. This is equivalent to ((LocalVariables) this.clone()).
86       *
87       * @return a (correctly typed) clone of this object.
88       */
89      public LocalVariables getClone() {
90          return (LocalVariables) this.clone();
91      }
92  
93      /**
94       * @return a hash code value for the object.
95       */
96      @Override
97      public int hashCode() {
98          return locals.length;
99      }
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 }