/* * 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; } } }