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