| /* |
| * Copyright 2008 Google Inc. |
| * |
| * 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.google.gwt.dev.jjs.ast; |
| |
| import com.google.gwt.dev.jjs.ast.js.JMultiExpression; |
| import com.google.gwt.dev.jjs.impl.HasNameSort; |
| import com.google.gwt.dev.util.collect.HashMap; |
| import com.google.gwt.dev.util.collect.IdentityHashMap; |
| import com.google.gwt.dev.util.collect.IdentityHashSet; |
| import com.google.gwt.dev.util.collect.IdentitySets; |
| import com.google.gwt.dev.util.collect.Lists; |
| import com.google.gwt.dev.util.collect.Maps; |
| |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Oracle that can answer questions regarding the types in a program. |
| */ |
| public class JTypeOracle implements Serializable { |
| |
| /** |
| * Checks a clinit method to find out a few things. |
| * |
| * <ol> |
| * <li>What other clinits it calls.</li> |
| * <li>If it runs any code other than clinit calls.</li> |
| * </ol> |
| * |
| * This is used to remove "dead clinit cycles" where self-referential cycles |
| * of empty clinits can keep each other alive. |
| */ |
| private static final class CheckClinitVisitor extends JVisitor { |
| |
| private final Set<JDeclaredType> clinitTargets = new IdentityHashSet<JDeclaredType>(); |
| |
| /** |
| * Tracks whether any live code is run in this clinit. This is only reliable |
| * because we explicitly visit all AST structures that might contain |
| * non-clinit-calling code. |
| * |
| * @see #mightBeDeadCode(JExpression) |
| * @see #mightBeDeadCode(JStatement) |
| */ |
| private boolean hasLiveCode = false; |
| |
| public JDeclaredType[] getClinitTargets() { |
| return clinitTargets.toArray(new JDeclaredType[clinitTargets.size()]); |
| } |
| |
| public boolean hasLiveCode() { |
| return hasLiveCode; |
| } |
| |
| @Override |
| public boolean visit(JBlock x, Context ctx) { |
| for (JStatement stmt : x.getStatements()) { |
| if (mightBeDeadCode(stmt)) { |
| accept(stmt); |
| } else { |
| hasLiveCode = true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(JDeclarationStatement x, Context ctx) { |
| JVariable target = x.getVariableRef().getTarget(); |
| if (target instanceof JField) { |
| JField field = (JField) target; |
| if (field.getLiteralInitializer() != null) { |
| // Top level initializations generate no code. |
| return false; |
| } |
| } |
| hasLiveCode = true; |
| return false; |
| } |
| |
| @Override |
| public boolean visit(JExpressionStatement x, Context ctx) { |
| JExpression expr = x.getExpr(); |
| if (mightBeDeadCode(expr)) { |
| accept(expr); |
| } else { |
| hasLiveCode = true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(JMethodCall x, Context ctx) { |
| JMethod target = x.getTarget(); |
| if (JProgram.isClinit(target)) { |
| clinitTargets.add(target.getEnclosingType()); |
| } else { |
| hasLiveCode = true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(JMultiExpression x, Context ctx) { |
| for (JExpression expr : x.getExpressions()) { |
| // Only a JMultiExpression or JMethodCall can contain clinit calls. |
| if (mightBeDeadCode(expr)) { |
| accept(expr); |
| } else { |
| hasLiveCode = true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(JNewInstance x, Context ctx) { |
| if (x.hasSideEffects()) { |
| hasLiveCode = true; |
| } |
| return false; |
| } |
| |
| private boolean mightBeDeadCode(JExpression expr) { |
| // Must have a visit method for every subtype that answers yes! |
| return expr instanceof JMultiExpression || expr instanceof JMethodCall |
| || expr instanceof JNewInstance; |
| } |
| |
| private boolean mightBeDeadCode(JStatement stmt) { |
| // Must have a visit method for every subtype that answers yes! |
| return stmt instanceof JBlock || stmt instanceof JExpressionStatement |
| || stmt instanceof JDeclarationStatement; |
| } |
| } |
| |
| /** |
| * Compare two methods based on name and original argument types |
| * {@link JMethod#getOriginalParamTypes()}. Note that nothing special is done |
| * here regarding methods with type parameters in their argument lists. The |
| * caller must be careful that this level of matching is sufficient. |
| */ |
| public static boolean methodsDoMatch(JMethod method1, JMethod method2) { |
| // static methods cannot match each other |
| if (method1.isStatic() || method2.isStatic()) { |
| return false; |
| } |
| |
| // names must be identical |
| if (!method1.getName().equals(method2.getName())) { |
| return false; |
| } |
| |
| // original return type must be identical |
| if (method1.getOriginalReturnType() != method2.getOriginalReturnType()) { |
| return false; |
| } |
| |
| // original parameter types must be identical |
| List<JType> params1 = method1.getOriginalParamTypes(); |
| List<JType> params2 = method2.getOriginalParamTypes(); |
| int params1size = params1.size(); |
| if (params1size != params2.size()) { |
| return false; |
| } |
| |
| for (int i = 0; i < params1size; ++i) { |
| if (params1.get(i) != params2.get(i)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private JDeclaredType baseArrayType; |
| |
| /** |
| * A map of all interfaces to the set of classes that could theoretically |
| * implement them. |
| */ |
| private final Map<JInterfaceType, Set<JClassType>> couldBeImplementedMap = |
| new IdentityHashMap<JInterfaceType, Set<JClassType>>(); |
| |
| /** |
| * A map of all classes to the set of interfaces that they could theoretically |
| * implement. |
| */ |
| private final Map<JClassType, Set<JInterfaceType>> couldImplementMap = |
| new IdentityHashMap<JClassType, Set<JInterfaceType>>(); |
| |
| /** |
| * The set of all interfaces that are initially implemented by both a Java and |
| * Overlay type. |
| */ |
| private final Set<JInterfaceType> dualImpls = new IdentityHashSet<JInterfaceType>(); |
| |
| /** |
| * A map of all classes to the set of interfaces they directly implement, |
| * possibly through inheritance. |
| */ |
| private final Map<JClassType, Set<JInterfaceType>> implementsMap = |
| new IdentityHashMap<JClassType, Set<JInterfaceType>>(); |
| |
| /** |
| * The types in the program that are instantiable. All types in this set |
| * should be run-time types as defined at |
| * {@link JProgram#getRunTimeType(JReferenceType)}. |
| */ |
| private Set<JReferenceType> instantiatedTypes = null; |
| |
| /** |
| * A map of all interfaces to the set of classes that directly implement them, |
| * possibly through inheritance. |
| */ |
| private final Map<JInterfaceType, Set<JClassType>> isImplementedMap = |
| new IdentityHashMap<JInterfaceType, Set<JClassType>>(); |
| |
| private JDeclaredType javaIoSerializable; |
| |
| private JDeclaredType javaLangCloneable; |
| |
| /** |
| * Caches the {@link Object} class. |
| */ |
| private JClassType javaLangObject = null; |
| |
| /** |
| * A map of all interfaces that are implemented by overlay types to the |
| * overlay type that initially implements it. |
| */ |
| private final Map<JInterfaceType, JClassType> jsoSingleImpls = |
| new IdentityHashMap<JInterfaceType, JClassType>(); |
| |
| /** |
| * The associated {@link JProgram}. |
| */ |
| private final JProgram program; |
| |
| /** |
| * A map of all classes to the set of classes that extend them, directly or |
| * indirectly. |
| */ |
| private final Map<JClassType, Set<JClassType>> subClassMap = |
| new IdentityHashMap<JClassType, Set<JClassType>>(); |
| |
| /** |
| * A map of all interfaces to the set of interfaces that extend them, directly |
| * or indirectly. |
| */ |
| private final Map<JInterfaceType, Set<JInterfaceType>> subInterfaceMap = |
| new IdentityHashMap<JInterfaceType, Set<JInterfaceType>>(); |
| |
| /** |
| * A map of all classes to the set of classes they extend, directly or |
| * indirectly. |
| */ |
| private final Map<JClassType, Set<JClassType>> superClassMap = |
| new IdentityHashMap<JClassType, Set<JClassType>>(); |
| |
| /** |
| * A map of all interfaces to the set of interfaces they extend, directly or |
| * indirectly. |
| */ |
| private final Map<JInterfaceType, Set<JInterfaceType>> superInterfaceMap = |
| new IdentityHashMap<JInterfaceType, Set<JInterfaceType>>(); |
| /** |
| * A map of all methods with virtual overrides, onto the collection of |
| * overridden methods. Each key method's collections is a map of the set of |
| * subclasses who inherit the key method mapped onto the set of interface |
| * methods the key method virtually implements. For a definition of a virtual |
| * override, see {@link #getAllVirtualOverrides(JMethod)}. |
| */ |
| private final Map<JMethod, Map<JClassType, Set<JMethod>>> virtualUpRefMap = |
| new IdentityHashMap<JMethod, Map<JClassType, Set<JMethod>>>(); |
| |
| /** |
| * An index of all polymorphic methods for each class. |
| */ |
| private final Map<JClassType, Map<String, JMethod>> polyClassMethodMap = |
| new IdentityHashMap<JClassType, Map<String, JMethod>>(); |
| |
| public JTypeOracle(JProgram program) { |
| this.program = program; |
| } |
| |
| /** |
| * True if the type is a JSO or interface implemented by JSO.. |
| * |
| * @param type |
| * @return |
| */ |
| public boolean canBeJavaScriptObject(JType type) { |
| if (type instanceof JNonNullType) { |
| type = ((JNonNullType) type).getUnderlyingType(); |
| } |
| return program.isJavaScriptObject(type) || program.typeOracle.isSingleJsoImpl(type); |
| } |
| |
| public boolean canTheoreticallyCast(JReferenceType type, JReferenceType qType) { |
| if (!type.canBeNull() && qType == program.getTypeNull()) { |
| // Cannot cast non-nullable to null |
| return false; |
| } |
| |
| // Compare the underlying types. |
| type = type.getUnderlyingType(); |
| qType = qType.getUnderlyingType(); |
| |
| JClassType jlo = program.getTypeJavaLangObject(); |
| if (type == qType || type == jlo) { |
| return true; |
| } |
| |
| /** |
| * Cross-cast allowed in theory, prevents TypeTightener from turning |
| * cross-casts into null-casts. |
| */ |
| if (canBeJavaScriptObject(type) && canBeJavaScriptObject(qType)) { |
| return true; |
| } |
| |
| if (canTriviallyCast(type, qType)) { |
| return true; |
| } |
| |
| if (type instanceof JArrayType) { |
| |
| JArrayType aType = (JArrayType) type; |
| if (qType instanceof JArrayType) { |
| JArrayType qaType = (JArrayType) qType; |
| JType leafType = aType.getLeafType(); |
| JType qLeafType = qaType.getLeafType(); |
| int dims = aType.getDims(); |
| int qDims = qaType.getDims(); |
| |
| // null[] or Object[] -> int[][] might work, other combinations won't |
| if (dims < qDims && leafType != program.getTypeJavaLangObject() |
| && !(leafType instanceof JNullType)) { |
| return false; |
| } |
| |
| if (dims == qDims) { |
| if (leafType instanceof JReferenceType && qLeafType instanceof JReferenceType) { |
| return canTheoreticallyCast((JReferenceType) leafType, (JReferenceType) qLeafType); |
| } |
| } |
| } |
| |
| /* |
| * Warning: If this code is ever updated to consider casts of array types |
| * to interface types, then be sure to consider that casting an array to |
| * Serializable and Cloneable succeeds. Currently all casts of an array to |
| * an interface return true, which is overly conservative but is safe. |
| */ |
| } else if (type instanceof JClassType) { |
| |
| JClassType cType = (JClassType) type; |
| if (qType instanceof JClassType) { |
| return isSubClass(cType, (JClassType) qType); |
| } else if (qType instanceof JInterfaceType) { |
| return get(couldImplementMap, cType).contains(qType); |
| } |
| } else if (type instanceof JInterfaceType) { |
| |
| JInterfaceType iType = (JInterfaceType) type; |
| if (qType instanceof JClassType) { |
| return get(couldBeImplementedMap, iType).contains(qType); |
| } |
| } else if (type instanceof JNullType) { |
| } |
| |
| return true; |
| } |
| |
| public boolean canTriviallyCast(JReferenceType type, JReferenceType qType) { |
| if (type.canBeNull() && !qType.canBeNull()) { |
| // Cannot reliably cast nullable to non-nullable |
| return false; |
| } |
| |
| // Compare the underlying types. |
| type = type.getUnderlyingType(); |
| qType = qType.getUnderlyingType(); |
| |
| JClassType jlo = program.getTypeJavaLangObject(); |
| if (type == qType || qType == jlo) { |
| return true; |
| } |
| |
| if (type instanceof JArrayType) { |
| |
| JArrayType aType = (JArrayType) type; |
| if (qType instanceof JArrayType) { |
| JArrayType qaType = (JArrayType) qType; |
| JType leafType = aType.getLeafType(); |
| JType qLeafType = qaType.getLeafType(); |
| int dims = aType.getDims(); |
| int qDims = qaType.getDims(); |
| |
| // int[][] -> Object[] or null[] trivially true |
| if (dims > qDims && (qLeafType == jlo || qLeafType instanceof JNullType)) { |
| return true; |
| } |
| |
| if (dims == qDims) { |
| if (leafType instanceof JReferenceType && qLeafType instanceof JReferenceType) { |
| return canTriviallyCast((JReferenceType) leafType, (JReferenceType) qLeafType); |
| } |
| } |
| } |
| |
| if (qType == javaIoSerializable || qType == javaLangCloneable || qType == baseArrayType) { |
| return true; |
| } |
| } else if (type instanceof JClassType) { |
| |
| JClassType cType = (JClassType) type; |
| if (qType instanceof JClassType) { |
| JClassType qcType = (JClassType) qType; |
| if (isSuperClass(cType, qcType)) { |
| return true; |
| } |
| } else if (qType instanceof JInterfaceType) { |
| return implementsInterface(cType, (JInterfaceType) qType); |
| } |
| } else if (type instanceof JInterfaceType) { |
| |
| JInterfaceType iType = (JInterfaceType) type; |
| if (qType instanceof JInterfaceType) { |
| return extendsInterface(iType, (JInterfaceType) qType); |
| } |
| } else if (type instanceof JNullType) { |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| public boolean canTriviallyCast(JType type, JType qType) { |
| if (type instanceof JPrimitiveType && qType instanceof JPrimitiveType) { |
| return type == qType; |
| } else if (type instanceof JReferenceType && qType instanceof JReferenceType) { |
| return canTriviallyCast((JReferenceType) type, (JReferenceType) qType); |
| } |
| return false; |
| } |
| |
| public void computeBeforeAST() { |
| baseArrayType = program.getIndexedType("Array"); |
| javaLangObject = program.getTypeJavaLangObject(); |
| javaIoSerializable = program.getFromTypeMap(Serializable.class.getName()); |
| javaLangCloneable = program.getFromTypeMap(Cloneable.class.getName()); |
| |
| superClassMap.clear(); |
| subClassMap.clear(); |
| superInterfaceMap.clear(); |
| subInterfaceMap.clear(); |
| implementsMap.clear(); |
| couldImplementMap.clear(); |
| isImplementedMap.clear(); |
| couldBeImplementedMap.clear(); |
| jsoSingleImpls.clear(); |
| dualImpls.clear(); |
| |
| for (JDeclaredType type : program.getDeclaredTypes()) { |
| if (type instanceof JClassType) { |
| recordSuperSubInfo((JClassType) type); |
| } else { |
| recordSuperSubInfo((JInterfaceType) type); |
| } |
| } |
| |
| /* |
| * Now that the basic type hierarchy is computed, compute which JSOs |
| * implement interfaces singlely or dually. |
| */ |
| JClassType jsoType = program.getJavaScriptObject(); |
| List<JClassType> jsoSubTypes = Lists.create(); |
| if (jsoType != null) { |
| jsoSubTypes = new ArrayList<JClassType>(get(subClassMap, jsoType)); |
| Collections.sort(jsoSubTypes, new HasNameSort()); |
| for (JClassType jsoSubType : jsoSubTypes) { |
| for (JInterfaceType intf : jsoSubType.getImplements()) { |
| jsoSingleImpls.put(intf, jsoSubType); |
| for (JInterfaceType superIntf : get(superInterfaceMap, intf)) { |
| if (!jsoSingleImpls.containsKey(superIntf)) { |
| jsoSingleImpls.put(superIntf, jsoSubType); |
| } |
| } |
| } |
| } |
| } |
| |
| for (JDeclaredType type : program.getDeclaredTypes()) { |
| if (type instanceof JClassType) { |
| computeImplements((JClassType) type); |
| } |
| } |
| for (JDeclaredType type : program.getDeclaredTypes()) { |
| if (type instanceof JClassType) { |
| computeCouldImplement((JClassType) type); |
| } |
| } |
| for (JDeclaredType type : program.getDeclaredTypes()) { |
| if (type instanceof JClassType) { |
| computeVirtualUpRefs((JClassType) type); |
| } |
| } |
| |
| // Create dual mappings for any jso interface with a Java implementor. |
| for (JInterfaceType jsoIntf : jsoSingleImpls.keySet()) { |
| Set<JClassType> implementors = get(isImplementedMap, jsoIntf); |
| for (JClassType implementor : implementors) { |
| if (!program.isJavaScriptObject(implementor)) { |
| dualImpls.add(jsoIntf); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * References to any methods which this method implementation might override |
| * or implement in any instantiable class, including strange cases where there |
| * is no direct relationship between the methods except in a subclass that |
| * inherits one and implements the other. Example: |
| * |
| * <pre> |
| * interface IFoo { |
| * foo(); |
| * } |
| * class Unrelated { |
| * foo() { ... } |
| * } |
| * class Foo extends Unrelated implements IFoo { |
| * } |
| * </pre> |
| * |
| * In this case, <code>Unrelated.foo()</code> virtually implements |
| * <code>IFoo.foo()</code> in subclass <code>Foo</code>. |
| */ |
| public Set<JMethod> getAllOverrides(JMethod method) { |
| Set<JMethod> results = new IdentityHashSet<JMethod>(); |
| getAllRealOverrides(method, results); |
| getAllVirtualOverrides(method, results); |
| return results; |
| } |
| |
| public Set<JReferenceType> getInstantiatedTypes() { |
| return instantiatedTypes; |
| } |
| |
| public JMethod getPolyMethod(JClassType type, String signature) { |
| return getOrCreatePolyMap(type).get(signature); |
| } |
| |
| public JClassType getSingleJsoImpl(JReferenceType maybeSingleJsoIntf) { |
| return jsoSingleImpls.get(maybeSingleJsoIntf.getUnderlyingType()); |
| } |
| |
| public boolean isDualJsoInterface(JReferenceType maybeDualImpl) { |
| return dualImpls.contains(maybeDualImpl.getUnderlyingType()); |
| } |
| |
| /** |
| * True if either a JSO, or is an interface that is ONLY implemented by a JSO. |
| */ |
| public boolean isEffectivelyJavaScriptObject(JType type) { |
| if (type instanceof JReferenceType) { |
| JReferenceType refType = (JReferenceType) type; |
| return program.isJavaScriptObject(refType) |
| || (isSingleJsoImpl(refType) && !isDualJsoInterface(refType)); |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Determine whether a type is instantiated. |
| */ |
| public boolean isInstantiatedType(JDeclaredType type) { |
| return instantiatedTypes == null || instantiatedTypes.contains(type); |
| } |
| |
| /** |
| * Determine whether a type is instantiated. |
| */ |
| public boolean isInstantiatedType(JReferenceType type) { |
| type = type.getUnderlyingType(); |
| if (instantiatedTypes == null || instantiatedTypes.contains(type)) { |
| return true; |
| } |
| |
| if (type.isExternal()) { |
| // TODO(tobyr) I don't know under what situations it is safe to assume |
| // that an external type won't be instantiated. For example, if we |
| // assumed that an external exception weren't instantiated, because we |
| // didn't see it constructed in our code, dead code elimination would |
| // incorrectly elide any catch blocks for that exception. |
| // |
| // We should see how this effects optimization and if we can limit its |
| // impact if necessary. |
| return true; |
| } |
| |
| if (type instanceof JNullType) { |
| return true; |
| } else if (type instanceof JArrayType) { |
| JArrayType arrayType = (JArrayType) type; |
| if (arrayType.getLeafType() instanceof JNullType) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean isSameOrSuper(JClassType type, JClassType qType) { |
| return (type == qType || isSuperClass(type, qType)); |
| } |
| |
| public boolean isSingleJsoImpl(JType type) { |
| return type instanceof JReferenceType && getSingleJsoImpl((JReferenceType) type) != null; |
| } |
| |
| /** |
| * Returns true if qType is a subclass of type, directly or indirectly. |
| */ |
| public boolean isSubClass(JClassType type, JClassType qType) { |
| return get(subClassMap, type).contains(qType); |
| } |
| |
| /** |
| * Returns true if qType is a superclass of type, directly or indirectly. |
| */ |
| public boolean isSuperClass(JClassType type, JClassType qType) { |
| return get(superClassMap, type).contains(qType); |
| } |
| |
| /** |
| * This method should be called after altering the types that are live in the |
| * associated JProgram. |
| */ |
| public void recomputeAfterOptimizations() { |
| Set<JDeclaredType> computed = new IdentityHashSet<JDeclaredType>(); |
| |
| for (JDeclaredType type : program.getDeclaredTypes()) { |
| computeClinitTarget(type, computed); |
| } |
| nextDual : for (Iterator<JInterfaceType> it = dualImpls.iterator(); it.hasNext();) { |
| JInterfaceType dualIntf = it.next(); |
| Set<JClassType> implementors = get(isImplementedMap, dualIntf); |
| for (JClassType implementor : implementors) { |
| if (isInstantiatedType(implementor) && !program.isJavaScriptObject(implementor)) { |
| // This dual is still implemented by a Java class. |
| continue nextDual; |
| } |
| } |
| // No Java implementors. |
| it.remove(); |
| } |
| |
| // Prune jsoSingleImpls when implementor isn't live |
| Iterator<JClassType> jit = jsoSingleImpls.values().iterator(); |
| while (jit.hasNext()) { |
| if (!isInstantiatedType(jit.next())) { |
| jit.remove(); |
| } |
| } |
| } |
| |
| public void setInstantiatedTypes(Set<JReferenceType> instantiatedTypes) { |
| this.instantiatedTypes = instantiatedTypes; |
| polyClassMethodMap.keySet().retainAll(instantiatedTypes); |
| } |
| |
| private <K, V> void add(Map<K, Set<V>> map, K key, V value) { |
| getOrCreate(map, key).add(value); |
| } |
| |
| private void computeClinitTarget(JDeclaredType type, Set<JDeclaredType> computed) { |
| if (type.isExternal() || !type.hasClinit() || computed.contains(type)) { |
| return; |
| } |
| JClassType superClass = null; |
| if (type instanceof JClassType) { |
| superClass = ((JClassType) type).getSuperClass(); |
| } |
| if (superClass != null) { |
| /* |
| * Compute super first so that it's already been tightened to the tightest |
| * possible target; this ensures if we're tightened as well it's to the |
| * transitively tightest target. |
| */ |
| computeClinitTarget(superClass, computed); |
| } |
| if (type.getClinitTarget() != type) { |
| // I already have a trivial clinit, just follow my super chain. |
| type.setClinitTarget(superClass.getClinitTarget()); |
| } else { |
| // I still have a real clinit, actually compute. |
| JDeclaredType target = |
| computeClinitTargetRecursive(type, computed, new IdentityHashSet<JDeclaredType>()); |
| type.setClinitTarget(target); |
| } |
| computed.add(type); |
| } |
| |
| private JDeclaredType computeClinitTargetRecursive(JDeclaredType type, |
| Set<JDeclaredType> computed, Set<JDeclaredType> alreadySeen) { |
| // Track that we've been seen. |
| alreadySeen.add(type); |
| |
| JMethod method = type.getClinitMethod(); |
| assert (JProgram.isClinit(method)); |
| CheckClinitVisitor v = new CheckClinitVisitor(); |
| v.accept(method); |
| if (v.hasLiveCode()) { |
| return type; |
| } |
| // Check for trivial super clinit. |
| JDeclaredType[] clinitTargets = v.getClinitTargets(); |
| if (clinitTargets.length == 1) { |
| JDeclaredType singleTarget = clinitTargets[0]; |
| if (type instanceof JClassType && singleTarget instanceof JClassType |
| && isSuperClass((JClassType) type, (JClassType) singleTarget)) { |
| return singleTarget.getClinitTarget(); |
| } |
| } |
| for (JDeclaredType target : clinitTargets) { |
| if (!target.hasClinit()) { |
| // A false result is always accurate. |
| continue; |
| } |
| |
| /* |
| * If target has a clinit, so do I; but only if target has already been |
| * recomputed this run. |
| */ |
| if (target.hasClinit() && computed.contains(target)) { |
| return type; |
| } |
| |
| /* |
| * Prevent recursion sickness: ignore this call for now since this call is |
| * being accounted for higher on the stack. |
| */ |
| if (alreadySeen.contains(target)) { |
| continue; |
| } |
| |
| if (computeClinitTargetRecursive(target, computed, alreadySeen) != null) { |
| // Calling a non-empty clinit means I am a real clinit. |
| return type; |
| } else { |
| // This clinit is okay, keep going. |
| continue; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Compute all of the things I might conceivably implement, either through |
| * super types or sub types. |
| */ |
| private void computeCouldImplement(JClassType type) { |
| Set<JInterfaceType> couldImplementSet = new IdentityHashSet<JInterfaceType>(); |
| // all of my direct implements are trivially true |
| couldImplementSet.addAll(get(implementsMap, type)); |
| List<JClassType> subclasses = new ArrayList<JClassType>(); |
| subclasses.addAll(get(subClassMap, type)); |
| for (JClassType subclass : subclasses) { |
| for (JInterfaceType intf : subclass.getImplements()) { |
| couldImplementSet.add(intf); |
| for (JInterfaceType isup : get(superInterfaceMap, intf)) { |
| couldImplementSet.add(isup); |
| } |
| } |
| } |
| if (!couldImplementSet.isEmpty()) { |
| couldImplementMap.put(type, IdentitySets.normalize(couldImplementSet)); |
| for (JInterfaceType couldImpl : couldImplementSet) { |
| add(couldBeImplementedMap, couldImpl, type); |
| } |
| } |
| } |
| |
| /** |
| * Compute all of the things I implement directly, through super types. |
| */ |
| private void computeImplements(JClassType type) { |
| Set<JInterfaceType> implementsSet = new IdentityHashSet<JInterfaceType>(); |
| List<JClassType> list = new ArrayList<JClassType>(); |
| list.add(type); |
| list.addAll(get(superClassMap, type)); |
| for (JClassType superclass : list) { |
| for (JInterfaceType intf : superclass.getImplements()) { |
| implementsSet.add(intf); |
| for (JInterfaceType isup : get(superInterfaceMap, intf)) { |
| implementsSet.add(isup); |
| } |
| } |
| } |
| if (!implementsSet.isEmpty()) { |
| implementsMap.put(type, IdentitySets.normalize(implementsSet)); |
| for (JInterfaceType impl : implementsSet) { |
| add(isImplementedMap, impl, type); |
| } |
| } |
| } |
| |
| /** |
| * WEIRD: Suppose class Foo declares void f(){} and unrelated interface I also |
| * declares void f(). Then suppose Bar extends Foo implements I and doesn't |
| * override f(). We need to record a "virtual" upref from Foo.f() to I.f() so |
| * that if I.f() is rescued AND Bar is instantiable, Foo.f() does not get |
| * pruned. |
| */ |
| private void computeVirtualUpRefs(JClassType type) { |
| if (type.getSuperClass() == null || type.getSuperClass() == javaLangObject) { |
| return; |
| } |
| |
| /* |
| * For each interface I directly implement, check all methods and make sure |
| * I define implementations for them. If I don't, then check all my super |
| * classes to find virtual overrides. |
| */ |
| for (JInterfaceType intf : type.getImplements()) { |
| computeVirtualUpRefs(type, intf); |
| for (JInterfaceType superIntf : get(superInterfaceMap, intf)) { |
| computeVirtualUpRefs(type, superIntf); |
| } |
| } |
| } |
| |
| /** |
| * For each interface I directly implement, check all methods and make sure I |
| * define implementations for them. If I don't, then check all my super |
| * classes to find virtual overrides. |
| */ |
| private void computeVirtualUpRefs(JClassType type, JInterfaceType intf) { |
| outer : for (JMethod intfMethod : intf.getMethods()) { |
| for (JMethod classMethod : type.getMethods()) { |
| if (methodsDoMatch(intfMethod, classMethod)) { |
| // this class directly implements the interface method |
| continue outer; |
| } |
| } |
| |
| // this class does not directly implement the interface method |
| // if any super classes do, create a virtual up ref |
| for (JClassType superType = type.getSuperClass(); superType != javaLangObject; superType = |
| superType.getSuperClass()) { |
| for (JMethod superMethod : superType.getMethods()) { |
| if (methodsDoMatch(intfMethod, superMethod)) { |
| // this super class directly implements the interface method |
| // create a virtual up ref |
| |
| // System.out.println("Virtual upref from " + superType.getName() |
| // + "." + superMethod.getName() + " to " + intf.getName() + "." |
| // + intfMethod.getName() + " via " + type.getName()); |
| |
| Map<JClassType, Set<JMethod>> classToMethodMap = |
| getOrCreateMap(virtualUpRefMap, superMethod); |
| add(classToMethodMap, type, intfMethod); |
| |
| // do not search additional super types |
| continue outer; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns true if type extends the interface represented by qType, either |
| * directly or indirectly. |
| */ |
| private boolean extendsInterface(JInterfaceType type, JInterfaceType qType) { |
| return get(superInterfaceMap, type).contains(qType); |
| } |
| |
| private <K, V> Set<V> get(Map<K, Set<V>> map, K key) { |
| Set<V> set = map.get(key); |
| if (set == null) { |
| return Collections.emptySet(); |
| } |
| return set; |
| } |
| |
| private void getAllRealOverrides(JMethod method, Set<JMethod> results) { |
| for (JMethod possibleOverride : method.getOverrides()) { |
| results.add(possibleOverride); |
| } |
| } |
| |
| private void getAllVirtualOverrides(JMethod method, Set<JMethod> results) { |
| Map<JClassType, Set<JMethod>> overrideMap = virtualUpRefMap.get(method); |
| if (overrideMap != null) { |
| for (Map.Entry<JClassType, Set<JMethod>> entry : overrideMap.entrySet()) { |
| JClassType classType = entry.getKey(); |
| if (isInstantiatedType(classType)) { |
| results.addAll(entry.getValue()); |
| } |
| } |
| } |
| } |
| |
| private <K, V> Set<V> getOrCreate(Map<K, Set<V>> map, K key) { |
| Set<V> set = map.get(key); |
| if (set == null) { |
| set = new IdentityHashSet<V>(); |
| map.put(key, set); |
| } |
| return set; |
| } |
| |
| private <K, K2, V> Map<K2, V> getOrCreateMap(Map<K, Map<K2, V>> map, K key) { |
| Map<K2, V> map2 = map.get(key); |
| if (map2 == null) { |
| map2 = new IdentityHashMap<K2, V>(); |
| map.put(key, map2); |
| } |
| return map2; |
| } |
| |
| private Map<String, JMethod> getOrCreatePolyMap(JClassType type) { |
| Map<String, JMethod> polyMap = polyClassMethodMap.get(type); |
| if (polyMap == null) { |
| JClassType superClass = type.getSuperClass(); |
| if (superClass == null) { |
| polyMap = new HashMap<String, JMethod>(); |
| } else { |
| Map<String, JMethod> superPolyMap = getOrCreatePolyMap(type.getSuperClass()); |
| polyMap = new HashMap<String, JMethod>(superPolyMap); |
| } |
| for (JMethod method : type.getMethods()) { |
| if (method.canBePolymorphic()) { |
| polyMap.put(method.getSignature(), method); |
| } |
| } |
| polyMap = Maps.normalize(polyMap); |
| polyClassMethodMap.put(type, polyMap); |
| } |
| return polyMap; |
| } |
| |
| /** |
| * Returns true if type implements the interface represented by qType, either |
| * directly or indirectly. |
| */ |
| private boolean implementsInterface(JClassType type, JInterfaceType qType) { |
| return get(implementsMap, type).contains(qType); |
| } |
| |
| /** |
| * Record the all of my super classes (and myself as a subclass of them). |
| */ |
| private void recordSuperSubInfo(JClassType type) { |
| Set<JClassType> superSet = new IdentityHashSet<JClassType>(); |
| for (JClassType t = type.getSuperClass(); t != null; t = t.getSuperClass()) { |
| superSet.add(t); |
| add(subClassMap, t, type); |
| } |
| if (!superSet.isEmpty()) { |
| superClassMap.put(type, IdentitySets.normalize(superSet)); |
| } |
| } |
| |
| /** |
| * Record the all of my super interfaces (and myself as a sub interface of |
| * them). |
| */ |
| private void recordSuperSubInfo(JInterfaceType type) { |
| if (!type.getImplements().isEmpty()) { |
| Set<JInterfaceType> superSet = new IdentityHashSet<JInterfaceType>(); |
| recordSuperSubInfo(type, superSet, type); |
| superInterfaceMap.put(type, IdentitySets.normalize(superSet)); |
| } |
| } |
| |
| /** |
| * Recursively record all of my super interfaces. |
| */ |
| private void recordSuperSubInfo(JInterfaceType base, Set<JInterfaceType> superSet, |
| JInterfaceType cur) { |
| for (JInterfaceType intf : cur.getImplements()) { |
| superSet.add(intf); |
| add(subInterfaceMap, intf, base); |
| recordSuperSubInfo(base, superSet, intf); |
| } |
| } |
| |
| } |