blob: 25fa0204a38436efed339c6612ffa543c5c3b92f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.bcel.verifier.structurals;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
import org.apache.bcel.verifier.exc.AssertionViolatedException;
import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
/**
* This class implements an array of local variables used for symbolic JVM
* simulation.
*/
public class LocalVariables implements Cloneable {
/** The Type[] containing the local variable slots. */
private final Type[] locals;
/**
* Creates a new LocalVariables object.
*
* @param localVariableCount local variable count.
*/
public LocalVariables(final int localVariableCount) {
locals = new Type[localVariableCount];
for (int i=0; i<localVariableCount; i++) {
locals[i] = Type.UNKNOWN;
}
}
/**
* Returns a deep copy of this object; i.e. the clone
* operates on a new local variable array.
* However, the Type objects in the array are shared.
*/
@Override
public Object clone() {
final LocalVariables lvs = new LocalVariables(locals.length);
for (int i=0; i<locals.length; i++) {
lvs.locals[i] = this.locals[i];
}
return lvs;
}
/**
* Returns the type of the local variable slot index.
*
* @param slotIndex Slot to look up.
* @return the type of the local variable slot index.
*/
public Type get(final int slotIndex) {
return locals[slotIndex];
}
/**
* Returns a (correctly typed) clone of this object.
* This is equivalent to ((LocalVariables) this.clone()).
*
* @return a (correctly typed) clone of this object.
*/
public LocalVariables getClone() {
return (LocalVariables) this.clone();
}
/**
* Returns the number of local variable slots.
*
* @return the number of local variable slots.
*/
public int maxLocals() {
return locals.length;
}
/**
* Sets a new Type for the given local variable slot.
*
* @param slotIndex Target slot index.
* @param type Type to save at the given slot index.
*/
public void set(final int slotIndex, final Type type) { // TODO could be package-protected?
if (type == Type.BYTE || type == Type.SHORT || type == Type.BOOLEAN || type == Type.CHAR) {
throw new AssertionViolatedException("LocalVariables do not know about '"+type+"'. Use Type.INT instead.");
}
locals[slotIndex] = type;
}
/** @return a hash code value for the object.
*/
@Override
public int hashCode() { return locals.length; }
/*
* Fulfills the general contract of Object.equals().
*/
@Override
public boolean equals(final Object o) {
if (!(o instanceof LocalVariables)) {
return false;
}
final LocalVariables lv = (LocalVariables) o;
if (this.locals.length != lv.locals.length) {
return false;
}
for (int i=0; i<this.locals.length; i++) {
if (!this.locals[i].equals(lv.locals[i])) {
//System.out.println(this.locals[i]+" is not "+lv.locals[i]);
return false;
}
}
return true;
}
/**
* Merges two local variables sets as described in the Java Virtual Machine Specification,
* Second Edition, section 4.9.2, page 146.
*
* @param localVariable other local variable.
*/
public void merge(final LocalVariables localVariable) {
if (this.locals.length != localVariable.locals.length) {
throw new AssertionViolatedException("Merging LocalVariables of different size?!? From different methods or what?!?");
}
for (int i=0; i<locals.length; i++) {
merge(localVariable, i);
}
}
/**
* Merges a single local variable.
*
* @see #merge(LocalVariables)
*/
private void merge(final LocalVariables lv, final int i) {
try {
// We won't accept an unitialized object if we know it was initialized;
// compare vmspec2, 4.9.4, last paragraph.
if ( (!(locals[i] instanceof UninitializedObjectType)) && (lv.locals[i] instanceof UninitializedObjectType) ) {
throw new StructuralCodeConstraintException(
"Backwards branch with an uninitialized object in the local variables detected.");
}
// Even harder, what about _different_ uninitialized object types?!
if ( (!(locals[i].equals(lv.locals[i]))) && (locals[i] instanceof UninitializedObjectType) &&
(lv.locals[i] instanceof UninitializedObjectType) ) {
throw new StructuralCodeConstraintException(
"Backwards branch with an uninitialized object in the local variables detected.");
}
// If we just didn't know that it was initialized, we have now learned.
if ((locals[i] instanceof UninitializedObjectType) && !(lv.locals[i] instanceof UninitializedObjectType)) {
locals[i] = ((UninitializedObjectType) locals[i]).getInitialized();
}
if ((locals[i] instanceof ReferenceType) && (lv.locals[i] instanceof ReferenceType)) {
if (! locals[i].equals(lv.locals[i])) { // needed in case of two UninitializedObjectType instances
final Type sup = ((ReferenceType) locals[i]).getFirstCommonSuperclass((ReferenceType) (lv.locals[i]));
if (sup == null) {
// We should have checked this in Pass2!
throw new AssertionViolatedException(
"Could not load all the super classes of '"+locals[i]+"' and '"+lv.locals[i]+"'.");
}
locals[i] = sup;
}
}
else{
if (! (locals[i].equals(lv.locals[i])) ) {
/*TODO
if ((locals[i] instanceof org.apache.bcel.generic.ReturnaddressType) &&
(lv.locals[i] instanceof org.apache.bcel.generic.ReturnaddressType)) {
//System.err.println("merging "+locals[i]+" and "+lv.locals[i]);
throw new AssertionViolatedException("Merging different ReturnAddresses: '"+locals[i]+"' and '"+lv.locals[i]+"'.");
}
*/
locals[i] = Type.UNKNOWN;
}
}
} catch (final ClassNotFoundException e) {
// FIXME: maybe not the best way to handle this
throw new AssertionViolatedException("Missing class: " + e, e);
}
}
/**
* Returns a String representation of this object.
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (int i=0; i<locals.length; i++) {
sb.append(Integer.toString(i));
sb.append(": ");
sb.append(locals[i]);
sb.append("\n");
}
return sb.toString();
}
/**
* Replaces all occurrences of {@code uninitializedObjectType} in this local variables set
* with an "initialized" ObjectType.
*
* @param uninitializedObjectType the object to match.
*/
public void initializeObject(final UninitializedObjectType uninitializedObjectType) {
for (int i=0; i<locals.length; i++) {
if (locals[i] == uninitializedObjectType) {
locals[i] = uninitializedObjectType.getInitialized();
}
}
}
}