blob: 51c31c3a37e6177bf5fc49eb9093f15f0fd08835 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed 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 com.android.dx.cf.code;
import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeBearer;
import com.android.dx.util.Hex;
/**
* Utility methods to merge various frame information.
*/
public final class Merger {
/**
* This class is uninstantiable.
*/
private Merger() {
// This space intentionally left blank.
}
/**
* Merges two locals arrays. If the merged result is the same as the first
* argument, then return the first argument (not a copy).
*
* @param locals1 {@code non-null;} a locals array
* @param locals2 {@code non-null;} another locals array
* @return {@code non-null;} the result of merging the two locals arrays
*/
public static OneLocalsArray mergeLocals(OneLocalsArray locals1,
OneLocalsArray locals2) {
if (locals1 == locals2) {
// Easy out.
return locals1;
}
int sz = locals1.getMaxLocals();
OneLocalsArray result = null;
if (locals2.getMaxLocals() != sz) {
throw new SimException("mismatched maxLocals values");
}
for (int i = 0; i < sz; i++) {
TypeBearer tb1 = locals1.getOrNull(i);
TypeBearer tb2 = locals2.getOrNull(i);
TypeBearer resultType = mergeType(tb1, tb2);
if (resultType != tb1) {
/*
* We only need to do anything when the result differs
* from what is in the first array, since that's what the
* result gets initialized to.
*/
if (result == null) {
result = locals1.copy();
}
if (resultType == null) {
result.invalidate(i);
} else {
result.set(i, resultType);
}
}
}
if (result == null) {
return locals1;
}
result.setImmutable();
return result;
}
/**
* Merges two stacks. If the merged result is the same as the first
* argument, then return the first argument (not a copy).
*
* @param stack1 {@code non-null;} a stack
* @param stack2 {@code non-null;} another stack
* @return {@code non-null;} the result of merging the two stacks
*/
public static ExecutionStack mergeStack(ExecutionStack stack1,
ExecutionStack stack2) {
if (stack1 == stack2) {
// Easy out.
return stack1;
}
int sz = stack1.size();
ExecutionStack result = null;
if (stack2.size() != sz) {
throw new SimException("mismatched stack depths");
}
for (int i = 0; i < sz; i++) {
TypeBearer tb1 = stack1.peek(i);
TypeBearer tb2 = stack2.peek(i);
TypeBearer resultType = mergeType(tb1, tb2);
if (resultType != tb1) {
/*
* We only need to do anything when the result differs
* from what is in the first stack, since that's what the
* result gets initialized to.
*/
if (result == null) {
result = stack1.copy();
}
try {
if (resultType == null) {
throw new SimException("incompatible: " + tb1 + ", " +
tb2);
} else {
result.change(i, resultType);
}
} catch (SimException ex) {
ex.addContext("...while merging stack[" + Hex.u2(i) + "]");
throw ex;
}
}
}
if (result == null) {
return stack1;
}
result.setImmutable();
return result;
}
/**
* Merges two frame types.
*
* @param ft1 {@code non-null;} a frame type
* @param ft2 {@code non-null;} another frame type
* @return {@code non-null;} the result of merging the two types
*/
public static TypeBearer mergeType(TypeBearer ft1, TypeBearer ft2) {
if ((ft1 == null) || ft1.equals(ft2)) {
return ft1;
} else if (ft2 == null) {
return null;
} else {
Type type1 = ft1.getType();
Type type2 = ft2.getType();
if (type1 == type2) {
return type1;
} else if (type1.isReference() && type2.isReference()) {
if (type1 == Type.KNOWN_NULL) {
/*
* A known-null merges with any other reference type to
* be that reference type.
*/
return type2;
} else if (type2 == Type.KNOWN_NULL) {
/*
* The same as above, but this time it's type2 that's
* the known-null.
*/
return type1;
} else if (type1.isArray() && type2.isArray()) {
TypeBearer componentUnion =
mergeType(type1.getComponentType(),
type2.getComponentType());
if (componentUnion == null) {
/*
* At least one of the types is a primitive type,
* so the merged result is just Object.
*/
return Type.OBJECT;
}
return ((Type) componentUnion).getArrayType();
} else {
/*
* All other unequal reference types get merged to be
* Object in this phase. This is fine here, but it
* won't be the right thing to do in the verifier.
*/
return Type.OBJECT;
}
} else if (type1.isIntlike() && type2.isIntlike()) {
/*
* Merging two non-identical int-like types results in
* the type int.
*/
return Type.INT;
} else {
return null;
}
}
}
/**
* Returns whether the given supertype is possibly assignable from
* the given subtype. This takes into account primitiveness,
* int-likeness, known-nullness, and array dimensions, but does
* not assume anything about class hierarchy other than that the
* type {@code Object} is the supertype of all reference
* types and all arrays are assignable to
* {@code Serializable} and {@code Cloneable}.
*
* @param supertypeBearer {@code non-null;} the supertype
* @param subtypeBearer {@code non-null;} the subtype
*/
public static boolean isPossiblyAssignableFrom(TypeBearer supertypeBearer,
TypeBearer subtypeBearer) {
Type supertype = supertypeBearer.getType();
Type subtype = subtypeBearer.getType();
if (supertype.equals(subtype)) {
// Easy out.
return true;
}
int superBt = supertype.getBasicType();
int subBt = subtype.getBasicType();
// Treat return types as Object for the purposes of this method.
if (superBt == Type.BT_ADDR) {
supertype = Type.OBJECT;
superBt = Type.BT_OBJECT;
}
if (subBt == Type.BT_ADDR) {
subtype = Type.OBJECT;
subBt = Type.BT_OBJECT;
}
if ((superBt != Type.BT_OBJECT) || (subBt != Type.BT_OBJECT)) {
/*
* No two distinct primitive types are assignable in this sense,
* unless they are both int-like.
*/
return supertype.isIntlike() && subtype.isIntlike();
}
// At this point, we know both types are reference types.
if (supertype == Type.KNOWN_NULL) {
/*
* A known-null supertype is only assignable from another
* known-null (handled in the easy out at the top of the
* method).
*/
return false;
} else if (subtype == Type.KNOWN_NULL) {
/*
* A known-null subtype is in fact assignable to any
* reference type.
*/
return true;
} else if (supertype == Type.OBJECT) {
/*
* Object is assignable from any reference type.
*/
return true;
} else if (supertype.isArray()) {
// The supertype is an array type.
if (! subtype.isArray()) {
// The subtype isn't an array, and so can't be assignable.
return false;
}
/*
* Strip off as many matched component types from both
* types as possible, and check the assignability of the
* results.
*/
do {
supertype = supertype.getComponentType();
subtype = subtype.getComponentType();
} while (supertype.isArray() && subtype.isArray());
return isPossiblyAssignableFrom(supertype, subtype);
} else if (subtype.isArray()) {
/*
* Other than Object (handled above), array types are
* assignable only to Serializable and Cloneable.
*/
return (supertype == Type.SERIALIZABLE) ||
(supertype == Type.CLONEABLE);
} else {
/*
* All other unequal reference types are considered at
* least possibly assignable.
*/
return true;
}
}
}