| /* |
| * 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.CorrelationFactory; |
| import com.google.gwt.dev.jjs.InternalCompilerException; |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.jjs.SourceOrigin; |
| import com.google.gwt.dev.jjs.Correlation.Literal; |
| import com.google.gwt.dev.jjs.ast.JField.Disposition; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; |
| import com.google.gwt.dev.jjs.ast.js.JsonObject; |
| import com.google.gwt.dev.jjs.impl.CodeSplitter; |
| import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs.RunAsyncReplacement; |
| import com.google.gwt.dev.util.collect.Lists; |
| import com.google.gwt.dev.util.collect.Maps; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| /** |
| * Root for the AST representing an entire Java program. |
| */ |
| public class JProgram extends JNode { |
| private static final class ArrayTypeComparator implements |
| Comparator<JArrayType>, Serializable { |
| public int compare(JArrayType o1, JArrayType o2) { |
| int comp = o1.getDims() - o2.getDims(); |
| if (comp != 0) { |
| return comp; |
| } |
| return o1.getName().compareTo(o2.getName()); |
| } |
| } |
| |
| public static final Set<String> CODEGEN_TYPES_SET = new LinkedHashSet<String>( |
| Arrays.asList( |
| "com.google.gwt.lang.Array", "com.google.gwt.lang.Cast", |
| "com.google.gwt.lang.CollapsedPropertyHolder", |
| "com.google.gwt.lang.Exceptions", "com.google.gwt.lang.LongLib", |
| "com.google.gwt.lang.Stats", "com.google.gwt.lang.Util")); |
| |
| public static final Set<String> INDEX_TYPES_SET = new LinkedHashSet<String>( |
| Arrays.asList( |
| "java.io.Serializable", "java.lang.Object", "java.lang.String", |
| "java.lang.Class", "java.lang.CharSequence", "java.lang.Cloneable", |
| "java.lang.Comparable", "java.lang.Enum", "java.lang.Iterable", |
| "java.util.Iterator", "java.lang.AssertionError", "java.lang.Boolean", |
| "java.lang.Byte", "java.lang.Character", "java.lang.Short", |
| "java.lang.Integer", "java.lang.Float", "java.lang.Double", |
| "com.google.gwt.core.client.GWT", |
| "com.google.gwt.core.client.JavaScriptObject", |
| "com.google.gwt.lang.ClassLiteralHolder", |
| "com.google.gwt.core.client.RunAsyncCallback", |
| "com.google.gwt.core.client.impl.AsyncFragmentLoader", |
| "com.google.gwt.core.client.impl.Impl", |
| "com.google.gwt.lang.EntryMethodHolder", |
| "com.google.gwt.core.client.prefetch.RunAsyncCode")); |
| |
| /** |
| * Only annotations defined in the following packages or sub-packages thereof |
| * will be recorded in the Java AST. |
| */ |
| public static final Set<String> RECORDED_ANNOTATION_PACKAGES = new LinkedHashSet<String>( |
| Arrays.asList("com.google.gwt.core.client.impl")); |
| |
| static final Map<String, Set<String>> traceMethods = new HashMap<String, Set<String>>(); |
| |
| private static final Comparator<JArrayType> ARRAYTYPE_COMPARATOR = new ArrayTypeComparator(); |
| |
| private static final int IS_ARRAY = 2; |
| |
| private static final int IS_CLASS = 3; |
| |
| private static final int IS_INTERFACE = 1; |
| |
| private static final int IS_NULL = 0; |
| |
| static { |
| INDEX_TYPES_SET.addAll(CODEGEN_TYPES_SET); |
| |
| /* |
| * The format to trace methods is a colon-separated list of |
| * "className.methodName", such as "Hello.onModuleLoad:Foo.bar". You can |
| * fully-qualify a class to disambiguate classes, and you can also append |
| * the JSNI signature of the method to disambiguate overloads, ala |
| * "Foo.bar(IZ)". |
| */ |
| String toTrace = System.getProperty("gwt.jjs.traceMethods"); |
| if (toTrace != null) { |
| String[] split = toTrace.split(":"); |
| for (String str : split) { |
| int pos = str.lastIndexOf('.'); |
| if (pos > 0) { |
| String className = str.substring(0, pos); |
| String methodName = str.substring(pos + 1); |
| Set<String> set = traceMethods.get(className); |
| if (set == null) { |
| set = new HashSet<String>(); |
| traceMethods.put(className, set); |
| } |
| set.add(methodName); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Helper to create an assignment, used to initalize fields, etc. |
| */ |
| public static JExpressionStatement createAssignmentStmt(SourceInfo info, |
| JExpression lhs, JExpression rhs) { |
| JBinaryOperation assign = new JBinaryOperation(info, lhs.getType(), |
| JBinaryOperator.ASG, lhs, rhs); |
| return assign.makeStatement(); |
| } |
| |
| public static JLocal createLocal(SourceInfo info, String name, JType type, |
| boolean isFinal, JMethodBody enclosingMethodBody) { |
| assert (name != null); |
| assert (type != null); |
| assert (enclosingMethodBody != null); |
| JLocal x = new JLocal(info, name, type, isFinal, enclosingMethodBody); |
| enclosingMethodBody.addLocal(x); |
| return x; |
| } |
| |
| public static JParameter createParameter(SourceInfo info, String name, |
| JType type, boolean isFinal, boolean isThis, JMethod enclosingMethod) { |
| assert (name != null); |
| assert (type != null); |
| assert (enclosingMethod != null); |
| |
| JParameter x = new JParameter(info, name, type, isFinal, isThis, |
| enclosingMethod); |
| |
| enclosingMethod.addParam(x); |
| return x; |
| } |
| |
| public static String getJsniSig(JMethod method) { |
| return getJsniSig(method, true); |
| } |
| |
| public static String getJsniSig(JMethod method, boolean addReturnType) { |
| StringBuffer sb = new StringBuffer(); |
| sb.append(method.getName()); |
| sb.append("("); |
| for (int i = 0; i < method.getOriginalParamTypes().size(); ++i) { |
| JType type = method.getOriginalParamTypes().get(i); |
| sb.append(type.getJsniSignatureName()); |
| } |
| sb.append(")"); |
| if (addReturnType) { |
| sb.append(method.getOriginalReturnType().getJsniSignatureName()); |
| } |
| return sb.toString(); |
| } |
| |
| public static boolean isClinit(JMethod method) { |
| JDeclaredType enclosingType = method.getEnclosingType(); |
| if ((enclosingType != null) |
| && (method == enclosingType.getMethods().get(0))) { |
| assert (method.getName().equals("$clinit")); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public static boolean isTracingEnabled() { |
| return traceMethods.size() > 0; |
| } |
| |
| /** |
| * The same as {@link #lastFragmentLoadingBefore(int, int...)}, except that |
| * all of the parameters must be passed explicitly. The instance method should |
| * be preferred whenever a JProgram instance is available. |
| * |
| * @param initialSeq The initial split point sequence of the program |
| * @param numSps The number of split points in the program |
| * @param firstFragment The first fragment to consider |
| * @param restFragments The rest of the fragments to consider |
| */ |
| public static int lastFragmentLoadingBefore(List<Integer> initialSeq, |
| int numSps, int firstFragment, int... restFragments) { |
| int latest = firstFragment; |
| for (int frag : restFragments) { |
| latest = pairwiseLastFragmentLoadingBefore(initialSeq, numSps, latest, |
| frag); |
| } |
| return latest; |
| } |
| |
| /** |
| * The main logic behind {@link #lastFragmentLoadingBefore(int, int...)} and |
| * {@link #lastFragmentLoadingBefore(List, int, int, int...)}. |
| */ |
| private static int pairwiseLastFragmentLoadingBefore( |
| List<Integer> initialSeq, int numSps, int frag1, int frag2) { |
| if (frag1 == frag2) { |
| return frag1; |
| } |
| |
| if (frag1 == 0) { |
| return 0; |
| } |
| |
| if (frag2 == 0) { |
| return 0; |
| } |
| |
| // See if either is in the initial sequence |
| int initPos1 = initialSeq.indexOf(frag1); |
| int initPos2 = initialSeq.indexOf(frag2); |
| |
| // If both are in the initial sequence, then pick the earlier |
| if (initPos1 >= 0 && initPos2 >= 0) { |
| if (initPos1 < initPos2) { |
| return frag1; |
| } |
| return frag2; |
| } |
| |
| // If exactly one is in the initial sequence, then it's the earlier one |
| if (initPos1 >= 0) { |
| return frag1; |
| } |
| if (initPos2 >= 0) { |
| return frag2; |
| } |
| |
| assert (initPos1 < 0 && initPos2 < 0); |
| assert (frag1 != frag2); |
| |
| // They are both leftovers or exclusive. Leftovers goes first in all cases. |
| return CodeSplitter.getLeftoversFragmentNumber(numSps); |
| } |
| |
| public final List<JClassType> codeGenTypes = new ArrayList<JClassType>(); |
| |
| /** |
| * There is a list containing the main entry methods as well as the entry |
| * methods for each split point. The main entry methods are at entry 0 of this |
| * list. Split points are numbered sequentially from 1, and the entry methods |
| * for split point <em>i</em> are at entry <em>i</em> of this list. |
| */ |
| public final List<List<JMethod>> entryMethods = new ArrayList<List<JMethod>>(); |
| |
| public final Map<String, HasEnclosingType> jsniMap = new HashMap<String, HasEnclosingType>(); |
| |
| public final JTypeOracle typeOracle = new JTypeOracle(this); |
| |
| /** |
| * Sorted to avoid nondeterministic iteration. |
| */ |
| private final Set<JArrayType> allArrayTypes = new TreeSet<JArrayType>( |
| ARRAYTYPE_COMPARATOR); |
| |
| /** |
| * Special serialization treatment. |
| */ |
| private transient List<JDeclaredType> allTypes = new ArrayList<JDeclaredType>(); |
| |
| private final Map<JType, JClassLiteral> classLiterals = new IdentityHashMap<JType, JClassLiteral>(); |
| |
| /** |
| * A factory to create correlations. |
| */ |
| private final CorrelationFactory correlator; |
| |
| /** |
| * Each entry is a HashMap(JType => JArrayType), arranged such that the number |
| * of dimensions is that index (plus one) at which the JArrayTypes having that |
| * number of dimensions resides. |
| */ |
| private final ArrayList<HashMap<JType, JArrayType>> dimensions = new ArrayList<HashMap<JType, JArrayType>>(); |
| |
| private final Map<String, JField> indexedFields = new HashMap<String, JField>(); |
| |
| private final Map<String, JMethod> indexedMethods = new HashMap<String, JMethod>(); |
| |
| private final Map<String, JDeclaredType> indexedTypes = new HashMap<String, JDeclaredType>(); |
| |
| private final Map<JMethod, JMethod> instanceToStaticMap = new IdentityHashMap<JMethod, JMethod>(); |
| |
| /** |
| * The root intrinsic source info. |
| */ |
| private final SourceInfo intrinsic; |
| |
| private IdentityHashMap<JReferenceType, JsonObject> castableTypeMaps; |
| |
| private Map<JReferenceType, JNonNullType> nonNullTypes = new IdentityHashMap<JReferenceType, JNonNullType>(); |
| |
| private JField nullField; |
| |
| private JMethod nullMethod; |
| |
| /** |
| * Turned on once optimizations begin. |
| */ |
| private boolean optimizationsStarted = false; |
| |
| private Map<JReferenceType, Integer> queryIds; |
| |
| /** |
| * Filled in by ReplaceRunAsync, once the numbers are known. |
| */ |
| private Map<Integer, RunAsyncReplacement> runAsyncReplacements = Maps.create(); |
| |
| private List<Integer> splitPointInitialSequence = Lists.create(); |
| |
| private final Map<JMethod, JMethod> staticToInstanceMap = new IdentityHashMap<JMethod, JMethod>(); |
| |
| private final Map<String, JStringLiteral> stringLiteralMap = new HashMap<String, JStringLiteral>(); |
| |
| private final SourceInfo stringPoolSourceInfo; |
| |
| private JClassType typeClass; |
| |
| private JInterfaceType typeJavaIoSerializable; |
| |
| private JInterfaceType typeJavaLangCloneable; |
| |
| private JClassType typeJavaLangEnum; |
| |
| private JClassType typeJavaLangObject; |
| |
| private final Map<String, JDeclaredType> typeNameMap = new HashMap<String, JDeclaredType>(); |
| |
| private JNonNullType typeNonNullString; |
| |
| private JClassType typeSpecialClassLiteralHolder; |
| |
| private JClassType typeSpecialJavaScriptObject; |
| |
| private JClassType typeString; |
| |
| public JProgram() { |
| this(new CorrelationFactory.DummyCorrelationFactory()); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param correlator Controls whether or not SourceInfo nodes created via the |
| * JProgram will record descendant information. Enabling this feature |
| * will collect extra data during the compilation cycle, but at a |
| * cost of memory and object allocations. |
| */ |
| public JProgram(CorrelationFactory correlator) { |
| super(correlator.makeSourceInfo(SourceOrigin.create(0, |
| JProgram.class.getName()))); |
| |
| this.correlator = correlator; |
| intrinsic = createSourceInfo(0, getClass().getName()); |
| |
| stringPoolSourceInfo = createLiteralSourceInfo("String pool", |
| Literal.STRING); |
| } |
| |
| public void addEntryMethod(JMethod entryPoint) { |
| addEntryMethod(entryPoint, 0); |
| } |
| |
| public void addEntryMethod(JMethod entryPoint, int fragmentNumber) { |
| assert entryPoint.isStatic(); |
| while (fragmentNumber >= entryMethods.size()) { |
| entryMethods.add(new ArrayList<JMethod>()); |
| } |
| List<JMethod> methods = entryMethods.get(fragmentNumber); |
| if (!methods.contains(entryPoint)) { |
| methods.add(entryPoint); |
| } |
| } |
| |
| /** |
| * Record the start of optimizations, which disables certain problematic |
| * constructions. In particular, new class literals cannot be created once |
| * optimization starts. |
| */ |
| public void beginOptimizations() { |
| optimizationsStarted = true; |
| } |
| |
| public JClassType createClass(SourceInfo info, String name, |
| boolean isAbstract, boolean isFinal) { |
| JClassType x = new JClassType(info, name, isAbstract, isFinal); |
| |
| allTypes.add(x); |
| putIntoTypeMap(name, x); |
| |
| if (CODEGEN_TYPES_SET.contains(name)) { |
| codeGenTypes.add(x); |
| } |
| if (INDEX_TYPES_SET.contains(name)) { |
| indexedTypes.put(x.getShortName(), x); |
| if (name.equals("java.lang.Object")) { |
| typeJavaLangObject = x; |
| } else if (name.equals("java.lang.String")) { |
| typeString = x; |
| typeNonNullString = getNonNullType(x); |
| } else if (name.equals("java.lang.Enum")) { |
| typeJavaLangEnum = x; |
| } else if (name.equals("java.lang.Class")) { |
| typeClass = x; |
| } else if (name.equals("com.google.gwt.core.client.JavaScriptObject")) { |
| typeSpecialJavaScriptObject = x; |
| } else if (name.equals("com.google.gwt.lang.ClassLiteralHolder")) { |
| typeSpecialClassLiteralHolder = x; |
| } |
| } |
| |
| return x; |
| } |
| |
| public JConstructor createConstructor(SourceInfo info, |
| JClassType enclosingType) { |
| JConstructor x = new JConstructor(info, enclosingType, |
| getNonNullType(enclosingType)); |
| x.setBody(new JMethodBody(info)); |
| if (indexedTypes.containsValue(enclosingType)) { |
| indexedMethods.put(enclosingType.getShortName() + '.' |
| + enclosingType.getShortName(), x); |
| } |
| |
| enclosingType.addMethod(x); |
| return x; |
| } |
| |
| public JEnumType createEnum(SourceInfo info, String name, boolean isAbstract) { |
| JEnumType x = new JEnumType(info, name, isAbstract); |
| x.setSuperClass(getTypeJavaLangEnum()); |
| |
| allTypes.add(x); |
| putIntoTypeMap(name, x); |
| |
| return x; |
| } |
| |
| public JField createEnumField(SourceInfo info, String name, |
| JEnumType enclosingType, JClassType type, int ordinal) { |
| assert (name != null); |
| assert (type != null); |
| assert (ordinal >= 0); |
| |
| JEnumField x = new JEnumField(info, name, ordinal, enclosingType, type); |
| enclosingType.addField(x); |
| return x; |
| } |
| |
| public JField createField(SourceInfo info, String name, |
| JDeclaredType enclosingType, JType type, boolean isStatic, |
| Disposition disposition) { |
| assert (name != null); |
| assert (enclosingType != null); |
| assert (type != null); |
| |
| JField x = new JField(info, name, enclosingType, type, isStatic, |
| disposition); |
| |
| if (indexedTypes.containsValue(enclosingType)) { |
| indexedFields.put(enclosingType.getShortName() + '.' + name, x); |
| } |
| |
| enclosingType.addField(x); |
| return x; |
| } |
| |
| public JInterfaceType createInterface(SourceInfo info, String name) { |
| JInterfaceType x = new JInterfaceType(info, name); |
| |
| allTypes.add(x); |
| putIntoTypeMap(name, x); |
| |
| if (INDEX_TYPES_SET.contains(name)) { |
| indexedTypes.put(x.getShortName(), x); |
| if (name.equals("java.lang.Cloneable")) { |
| typeJavaLangCloneable = x; |
| } else if (name.equals("java.io.Serializable")) { |
| typeJavaIoSerializable = x; |
| } |
| } |
| |
| return x; |
| } |
| |
| public JMethod createMethod(SourceInfo info, String name, |
| JDeclaredType enclosingType, JType returnType, boolean isAbstract, |
| boolean isStatic, boolean isFinal, boolean isPrivate, boolean isNative) { |
| assert (name != null); |
| assert (enclosingType != null); |
| assert (returnType != null); |
| assert (!isAbstract || !isNative); |
| JMethod x = new JMethod(info, name, enclosingType, returnType, isAbstract, |
| isStatic, isFinal, isPrivate); |
| if (isNative) { |
| x.setBody(new JsniMethodBody(info)); |
| } else if (!isAbstract) { |
| x.setBody(new JMethodBody(info)); |
| } |
| |
| if (!isPrivate && indexedTypes.containsValue(enclosingType)) { |
| indexedMethods.put(enclosingType.getShortName() + '.' + name, x); |
| } |
| |
| enclosingType.addMethod(x); |
| return x; |
| } |
| |
| /** |
| * Create a SourceInfo object when the source is derived from a physical |
| * location. |
| */ |
| public SourceInfo createSourceInfo(int startPos, int endPos, int startLine, |
| String fileName) { |
| return correlator.makeSourceInfo(SourceOrigin.create(startPos, endPos, |
| startLine, fileName)); |
| } |
| |
| /** |
| * Create a SourceInfo object when the source is derived from a physical |
| * location. |
| */ |
| public SourceInfo createSourceInfo(int startLine, String fileName) { |
| return correlator.makeSourceInfo(SourceOrigin.create(startLine, fileName)); |
| } |
| |
| /** |
| * Create a SourceInfo object when the source is created by the compiler |
| * itself. |
| */ |
| public SourceInfo createSourceInfoSynthetic(Class<?> caller, |
| String description) { |
| return createSourceInfo(0, caller.getName()).makeChild(caller, description); |
| } |
| |
| /** |
| * Return the least upper bound of a set of types. That is, the smallest type |
| * that is a supertype of all the input types. |
| */ |
| public JReferenceType generalizeTypes( |
| Collection<? extends JReferenceType> types) { |
| assert (types != null); |
| assert (!types.isEmpty()); |
| Iterator<? extends JReferenceType> it = types.iterator(); |
| JReferenceType curType = it.next(); |
| while (it.hasNext()) { |
| curType = generalizeTypes(curType, it.next()); |
| } |
| return curType; |
| } |
| |
| /** |
| * Return the least upper bound of two types. That is, the smallest type that |
| * is a supertype of both types. |
| */ |
| public JReferenceType generalizeTypes(JReferenceType type1, |
| JReferenceType type2) { |
| if (type1 == type2) { |
| return type1; |
| } |
| |
| if (type1 instanceof JNonNullType && type2 instanceof JNonNullType) { |
| // Neither can be null. |
| type1 = type1.getUnderlyingType(); |
| type2 = type2.getUnderlyingType(); |
| return getNonNullType(generalizeTypes(type1, type2)); |
| } else if (type1 instanceof JNonNullType) { |
| // type2 can be null, so the result can be null |
| type1 = type1.getUnderlyingType(); |
| } else if (type2 instanceof JNonNullType) { |
| // type1 can be null, so the result can be null |
| type2 = type2.getUnderlyingType(); |
| } |
| assert !(type1 instanceof JNonNullType); |
| assert !(type2 instanceof JNonNullType); |
| |
| int classify1 = classifyType(type1); |
| int classify2 = classifyType(type2); |
| |
| if (classify1 == IS_NULL) { |
| return type2; |
| } |
| |
| if (classify2 == IS_NULL) { |
| return type1; |
| } |
| |
| if (classify1 == classify2) { |
| |
| // same basic kind of type |
| if (classify1 == IS_INTERFACE) { |
| |
| if (typeOracle.canTriviallyCast(type1, type2)) { |
| return type2; |
| } |
| |
| if (typeOracle.canTriviallyCast(type2, type1)) { |
| return type1; |
| } |
| |
| // unrelated |
| return typeJavaLangObject; |
| |
| } else if (classify1 == IS_ARRAY) { |
| |
| JArrayType aType1 = (JArrayType) type1; |
| JArrayType aType2 = (JArrayType) type2; |
| int dims1 = aType1.getDims(); |
| int dims2 = aType2.getDims(); |
| |
| int minDims = Math.min(dims1, dims2); |
| /* |
| * At a bare minimum, any two arrays generalize to an Object array with |
| * one less dim than the lesser of the two; that is, int[][][][] and |
| * String[][][] generalize to Object[][]. If minDims is 1, then they |
| * just generalize to Object. |
| */ |
| JReferenceType minimalGeneralType; |
| if (minDims > 1) { |
| minimalGeneralType = getTypeArray(typeJavaLangObject, minDims - 1); |
| } else { |
| minimalGeneralType = typeJavaLangObject; |
| } |
| |
| if (dims1 == dims2) { |
| |
| // Try to generalize by leaf types |
| JType leafType1 = aType1.getLeafType(); |
| JType leafType2 = aType2.getLeafType(); |
| |
| if (!(leafType1 instanceof JReferenceType) |
| || !(leafType2 instanceof JReferenceType)) { |
| return minimalGeneralType; |
| } |
| |
| /* |
| * Both are reference types; the result is the generalization of the |
| * leaf types combined with the number of dims; that is, Foo[] and |
| * Bar[] generalize to X[] where X is the generalization of Foo and |
| * Bar. |
| */ |
| JReferenceType leafRefType1 = (JReferenceType) leafType1; |
| JReferenceType leafRefType2 = (JReferenceType) leafType2; |
| JReferenceType leafGeneralization = generalizeTypes(leafRefType1, |
| leafRefType2); |
| return getTypeArray(leafGeneralization, dims1); |
| |
| } else { |
| |
| // Conflicting number of dims |
| |
| // int[][] and Object[] generalize to Object[] |
| JArrayType lesser = dims1 < dims2 ? aType1 : aType2; |
| if (lesser.getLeafType() == typeJavaLangObject) { |
| return lesser; |
| } |
| |
| // Totally unrelated |
| return minimalGeneralType; |
| } |
| |
| } else { |
| |
| assert (classify1 == IS_CLASS); |
| |
| /* |
| * see how far each type is from object; walk the one who's farther up |
| * until they're even; then walk them up together until they meet (worst |
| * case at Object) |
| */ |
| int distance1 = countSuperTypes(type1); |
| int distance2 = countSuperTypes(type2); |
| for (; distance1 > distance2; --distance1) { |
| type1 = type1.getSuperClass(); |
| } |
| |
| for (; distance1 < distance2; --distance2) { |
| type2 = type2.getSuperClass(); |
| } |
| |
| while (type1 != type2) { |
| type1 = type1.getSuperClass(); |
| type2 = type2.getSuperClass(); |
| } |
| |
| return type1; |
| } |
| } else { |
| |
| // different kinds of types |
| int lesser = Math.min(classify1, classify2); |
| int greater = Math.max(classify1, classify2); |
| |
| JReferenceType tLesser = classify1 < classify2 ? type1 : type2; |
| JReferenceType tGreater = classify1 > classify2 ? type1 : type2; |
| |
| if (lesser == IS_INTERFACE && greater == IS_CLASS) { |
| |
| // just see if the class implements the interface |
| if (typeOracle.canTriviallyCast(tGreater, tLesser)) { |
| return tLesser; |
| } |
| |
| // unrelated |
| return typeJavaLangObject; |
| |
| } else if (greater == IS_ARRAY |
| && ((tLesser == typeJavaLangCloneable) || (tLesser == typeJavaIoSerializable))) { |
| return tLesser; |
| } else { |
| |
| // unrelated: the best commonality between an interface and array, or |
| // between an array and a class is Object |
| return typeJavaLangObject; |
| } |
| } |
| } |
| |
| /** |
| * Returns a sorted set of array types, so the returned set can be iterated |
| * over without introducing nondeterminism. |
| */ |
| public Set<JArrayType> getAllArrayTypes() { |
| return allArrayTypes; |
| } |
| |
| public List<JMethod> getAllEntryMethods() { |
| List<JMethod> allEntryMethods = new ArrayList<JMethod>(); |
| for (List<JMethod> entries : entryMethods) { |
| allEntryMethods.addAll(entries); |
| } |
| return allEntryMethods; |
| } |
| |
| public JsonObject getCastableTypeMap(JReferenceType referenceType) { |
| |
| // ensure jsonCastableTypeMaps has been initialized |
| // it might not have been if the CastNormalizer has not been run |
| if (castableTypeMaps == null) { |
| initTypeInfo(null); |
| } |
| |
| JsonObject returnMap = castableTypeMaps.get(referenceType); |
| if (returnMap == null) { |
| // add a new empty map |
| returnMap = new JsonObject(createSourceInfoSynthetic(JProgram.class, |
| "empty map"), getJavaScriptObject()); |
| castableTypeMaps.put(referenceType, returnMap); |
| } |
| |
| return returnMap; |
| } |
| |
| public CorrelationFactory getCorrelator() { |
| return correlator; |
| } |
| |
| public List<JDeclaredType> getDeclaredTypes() { |
| return allTypes; |
| } |
| |
| public int getEntryCount(int fragment) { |
| return entryMethods.get(fragment).size(); |
| } |
| |
| public JThisRef getExprThisRef(SourceInfo info, JClassType enclosingType) { |
| return new JThisRef(info, getNonNullType(enclosingType)); |
| } |
| |
| public int getFragmentCount() { |
| return entryMethods.size(); |
| } |
| |
| public JDeclaredType getFromTypeMap(String qualifiedBinaryOrSourceName) { |
| String srcTypeName = qualifiedBinaryOrSourceName.replace('$', '.'); |
| |
| return typeNameMap.get(srcTypeName); |
| } |
| |
| public JField getIndexedField(String string) { |
| JField field = indexedFields.get(string); |
| if (field == null) { |
| throw new InternalCompilerException("Unable to locate index field: " |
| + string); |
| } |
| return field; |
| } |
| |
| public JMethod getIndexedMethod(String string) { |
| JMethod method = indexedMethods.get(string); |
| if (method == null) { |
| throw new InternalCompilerException("Unable to locate index method: " |
| + string); |
| } |
| return method; |
| } |
| |
| public Collection<JMethod> getIndexedMethods() { |
| return Collections.unmodifiableCollection(indexedMethods.values()); |
| } |
| |
| public JDeclaredType getIndexedType(String string) { |
| JDeclaredType type = indexedTypes.get(string); |
| if (type == null) { |
| throw new InternalCompilerException("Unable to locate index type: " |
| + string); |
| } |
| return type; |
| } |
| |
| public JClassType getJavaScriptObject() { |
| return typeSpecialJavaScriptObject; |
| } |
| |
| public JExpression getLiteralAbsentArrayDimension() { |
| return JAbsentArrayDimension.INSTANCE; |
| } |
| |
| public JBooleanLiteral getLiteralBoolean(boolean value) { |
| return JBooleanLiteral.get(value); |
| } |
| |
| public JCharLiteral getLiteralChar(char value) { |
| return JCharLiteral.get(value); |
| } |
| |
| /** |
| * May not be called once optimizations begin; all possible class literals |
| * must be created up front. |
| */ |
| public JClassLiteral getLiteralClass(JType type) { |
| JClassLiteral classLiteral = classLiterals.get(type); |
| if (classLiteral == null) { |
| if (optimizationsStarted) { |
| throw new InternalCompilerException( |
| "New class literals cannot be created once optimizations have started; type '" |
| + type + "'"); |
| } |
| |
| SourceInfo info = typeSpecialClassLiteralHolder.getSourceInfo(); |
| |
| // Create the allocation expression FIRST since this may be recursive on |
| // super type (this forces the super type classLit to be created first). |
| boolean isObjectExternal = getTypeJavaLangObject().isExternal(); |
| JExpression alloc = isObjectExternal ? null : |
| JClassLiteral.computeClassObjectAllocation(this,info, type); |
| |
| // Create a field in the class literal holder to hold the object. |
| JField field = new JField(info, type.getJavahSignatureName() |
| + "_classLit", typeSpecialClassLiteralHolder, getTypeJavaLangClass(), |
| true, Disposition.FINAL); |
| typeSpecialClassLiteralHolder.addField(field); |
| |
| // Initialize the field. |
| if (alloc != null) { |
| JFieldRef fieldRef = new JFieldRef(info, null, field, |
| typeSpecialClassLiteralHolder); |
| JDeclarationStatement decl = new JDeclarationStatement(info, fieldRef, |
| alloc); |
| JMethodBody clinitBody = (JMethodBody) |
| typeSpecialClassLiteralHolder.getMethods().get(0).getBody(); |
| clinitBody.getBlock().addStmt(decl); |
| } |
| |
| SourceInfo literalInfo = createSourceInfoSynthetic(JProgram.class, |
| "class literal for " + type.getName()); |
| literalInfo.addCorrelation(correlator.by(Literal.CLASS)); |
| classLiteral = new JClassLiteral(literalInfo, type, field); |
| classLiterals.put(type, classLiteral); |
| } else { |
| // Make sure the field hasn't been pruned. |
| JField field = classLiteral.getField(); |
| if (optimizationsStarted |
| && !field.getEnclosingType().getFields().contains(field)) { |
| throw new InternalCompilerException( |
| "Getting a class literal whose field holder has already been pruned; type '" |
| + type + " '"); |
| } |
| } |
| return classLiteral; |
| } |
| |
| public JDoubleLiteral getLiteralDouble(double d) { |
| return JDoubleLiteral.get(d); |
| } |
| |
| public JFloatLiteral getLiteralFloat(float f) { |
| return JFloatLiteral.get(f); |
| } |
| |
| public JIntLiteral getLiteralInt(int value) { |
| return JIntLiteral.get(value); |
| } |
| |
| public JLongLiteral getLiteralLong(long value) { |
| return JLongLiteral.get(value); |
| } |
| |
| public JNullLiteral getLiteralNull() { |
| return JNullLiteral.INSTANCE; |
| } |
| |
| public JStringLiteral getLiteralString(SourceInfo sourceInfo, char[] s) { |
| return getLiteralString(sourceInfo, String.valueOf(s)); |
| } |
| |
| public JStringLiteral getLiteralString(SourceInfo sourceInfo, String s) { |
| JStringLiteral toReturn = stringLiteralMap.get(s); |
| if (toReturn == null) { |
| toReturn = new JStringLiteral(stringPoolSourceInfo.makeChild( |
| JProgram.class, "String literal: " + s), s, typeNonNullString); |
| stringLiteralMap.put(s, toReturn); |
| } |
| toReturn.getSourceInfo().merge(sourceInfo); |
| return toReturn; |
| } |
| |
| public JNonNullType getNonNullType(JReferenceType type) { |
| if (type instanceof JNonNullType) { |
| return (JNonNullType) type; |
| } |
| JNonNullType nonNullType = nonNullTypes.get(type); |
| if (nonNullType == null) { |
| nonNullType = new JNonNullType(type); |
| nonNullTypes.put(type, nonNullType); |
| } |
| return nonNullType; |
| } |
| |
| public JField getNullField() { |
| if (nullField == null) { |
| nullField = new JField(createSourceInfoSynthetic(JProgram.class, |
| "Null field"), "nullField", null, JNullType.INSTANCE, false, |
| Disposition.FINAL); |
| } |
| return nullField; |
| } |
| |
| public JMethod getNullMethod() { |
| if (nullMethod == null) { |
| nullMethod = new JMethod(createSourceInfoSynthetic(JProgram.class, |
| "Null method"), "nullMethod", null, JNullType.INSTANCE, false, false, |
| true, false); |
| nullMethod.setSynthetic(); |
| } |
| return nullMethod; |
| } |
| |
| public int getQueryId(JReferenceType elementType) { |
| assert (elementType == getRunTimeType(elementType)); |
| Integer integer = queryIds.get(elementType); |
| if (integer == null) { |
| return 0; |
| } |
| |
| return integer.intValue(); |
| } |
| |
| public Map<Integer, RunAsyncReplacement> getRunAsyncReplacements() { |
| return runAsyncReplacements; |
| } |
| |
| /** |
| * A run-time type is a type at the granularity that GWT tests at run time. |
| * These include declared types, arrays of declared types, arrays of |
| * primitives, and null. This is also the granularity for the notion of |
| * instantiability recorded in {@link JTypeOracle}. This method returns the |
| * narrowest supertype of <code>type</code> that is a run-time type. |
| */ |
| public JReferenceType getRunTimeType(JReferenceType type) { |
| type = type.getUnderlyingType(); |
| if (type instanceof JArrayType) { |
| JArrayType typeArray = (JArrayType) type; |
| if (typeArray.getLeafType() instanceof JNonNullType) { |
| JNonNullType leafType = (JNonNullType) typeArray.getLeafType(); |
| type = getTypeArray(leafType.getUnderlyingType(), typeArray.getDims()); |
| } |
| } |
| return type; |
| } |
| |
| public List<Integer> getSplitPointInitialSequence() { |
| return splitPointInitialSequence; |
| } |
| |
| public JMethod getStaticImpl(JMethod method) { |
| return instanceToStaticMap.get(method); |
| } |
| |
| public JArrayType getTypeArray(JType leafType, int dimensions) { |
| assert (!(leafType instanceof JArrayType)); |
| HashMap<JType, JArrayType> typeToArrayType; |
| |
| // Create typeToArrayType maps for index slots that don't exist yet. |
| // |
| for (int i = this.dimensions.size(); i < dimensions; ++i) { |
| typeToArrayType = new HashMap<JType, JArrayType>(); |
| this.dimensions.add(typeToArrayType); |
| } |
| |
| // Get the map for array having this number of dimensions (biased by one |
| // since we don't store non-arrays in there -- thus index 0 => 1 dim). |
| // |
| typeToArrayType = this.dimensions.get(dimensions - 1); |
| |
| JArrayType arrayType = typeToArrayType.get(leafType); |
| if (arrayType == null) { |
| JType elementType; |
| if (dimensions == 1) { |
| elementType = leafType; |
| } else { |
| elementType = getTypeArray(leafType, dimensions - 1); |
| } |
| arrayType = new JArrayType(elementType, leafType, dimensions, |
| typeJavaLangObject); |
| allArrayTypes.add(arrayType); |
| |
| /* |
| * TODO(later): should we setup the various array types as an inheritance |
| * heirarchy? Currently we're just doing all the heavy lifting in |
| * JTypeOracle. If we tried to setup inheritance, we'd have to recompute |
| * JTypeOracle if anything changed, so maybe this is better. |
| */ |
| typeToArrayType.put(leafType, arrayType); |
| } |
| |
| return arrayType; |
| } |
| |
| public JClassType getTypeClassLiteralHolder() { |
| return typeSpecialClassLiteralHolder; |
| } |
| |
| /** |
| * Returns the JType corresponding to a JSNI type reference. |
| */ |
| public JType getTypeFromJsniRef(String className) { |
| int dim = 0; |
| while (className.endsWith("[]")) { |
| dim++; |
| className = className.substring(0, className.length() - 2); |
| } |
| |
| JType type; |
| if ("Z".equals(className)) { |
| type = getTypePrimitiveBoolean(); |
| } else if ("B".equals(className)) { |
| type = getTypePrimitiveByte(); |
| } else if ("C".equals(className)) { |
| type = getTypePrimitiveChar(); |
| } else if ("D".equals(className)) { |
| type = getTypePrimitiveDouble(); |
| } else if ("F".equals(className)) { |
| type = getTypePrimitiveFloat(); |
| } else if ("I".equals(className)) { |
| type = getTypePrimitiveInt(); |
| } else if ("J".equals(className)) { |
| type = getTypePrimitiveLong(); |
| } else if ("S".equals(className)) { |
| type = getTypePrimitiveShort(); |
| } else if ("V".equals(className)) { |
| type = getTypeVoid(); |
| } else { |
| type = getFromTypeMap(className); |
| } |
| |
| if (type == null || dim == 0) { |
| return type; |
| } else { |
| return getTypeArray(type, dim); |
| } |
| } |
| |
| public JClassType getTypeJavaLangClass() { |
| return typeClass; |
| } |
| |
| public JClassType getTypeJavaLangEnum() { |
| return typeJavaLangEnum; |
| } |
| |
| public JClassType getTypeJavaLangObject() { |
| return typeJavaLangObject; |
| } |
| |
| public JClassType getTypeJavaLangString() { |
| return typeString; |
| } |
| |
| public JNullType getTypeNull() { |
| return JNullType.INSTANCE; |
| } |
| |
| public JPrimitiveType getTypePrimitiveBoolean() { |
| return JPrimitiveType.BOOLEAN; |
| } |
| |
| public JPrimitiveType getTypePrimitiveByte() { |
| return JPrimitiveType.BYTE; |
| } |
| |
| public JPrimitiveType getTypePrimitiveChar() { |
| return JPrimitiveType.CHAR; |
| } |
| |
| public JPrimitiveType getTypePrimitiveDouble() { |
| return JPrimitiveType.DOUBLE; |
| } |
| |
| public JPrimitiveType getTypePrimitiveFloat() { |
| return JPrimitiveType.FLOAT; |
| } |
| |
| public JPrimitiveType getTypePrimitiveInt() { |
| return JPrimitiveType.INT; |
| } |
| |
| public JPrimitiveType getTypePrimitiveLong() { |
| return JPrimitiveType.LONG; |
| } |
| |
| public JPrimitiveType getTypePrimitiveShort() { |
| return JPrimitiveType.SHORT; |
| } |
| |
| public JPrimitiveType getTypeVoid() { |
| return JPrimitiveType.VOID; |
| } |
| |
| public void initTypeInfo( |
| IdentityHashMap<JReferenceType,JsonObject> instantiatedTypeCastableTypeMaps) { |
| |
| castableTypeMaps = instantiatedTypeCastableTypeMaps; |
| |
| if (castableTypeMaps == null || castableTypeMaps.size() == 0) { |
| castableTypeMaps = new IdentityHashMap<JReferenceType, JsonObject>(); |
| } |
| } |
| |
| public boolean isJavaLangString(JType type) { |
| return type == typeString || type == typeNonNullString; |
| } |
| |
| public boolean isJavaScriptObject(JType type) { |
| if (type instanceof JReferenceType && typeSpecialJavaScriptObject != null) { |
| return typeOracle.canTriviallyCast((JReferenceType) type, |
| typeSpecialJavaScriptObject); |
| } |
| return false; |
| } |
| |
| public boolean isStaticImpl(JMethod method) { |
| return staticToInstanceMap.containsKey(method); |
| } |
| |
| /** |
| * Given a sequence of fragment numbers, return the latest fragment number |
| * possible that does not load later than any of these. It might be one of the |
| * supplied fragments, or it might be a common predecessor. |
| */ |
| public int lastFragmentLoadingBefore(int firstFragment, int... restFragments) { |
| return lastFragmentLoadingBefore(splitPointInitialSequence, |
| entryMethods.size() - 1, firstFragment, restFragments); |
| } |
| |
| public void putIntoTypeMap(String qualifiedBinaryName, JDeclaredType type) { |
| // Make it into a source type name. |
| String srcTypeName = qualifiedBinaryName.replace('$', '.'); |
| typeNameMap.put(srcTypeName, type); |
| } |
| |
| public void putStaticImpl(JMethod method, JMethod staticImpl) { |
| instanceToStaticMap.put(method, staticImpl); |
| staticToInstanceMap.put(staticImpl, method); |
| if (method.isTrace()) { |
| staticImpl.setTrace(); |
| } |
| } |
| |
| public void recordQueryIds(Map<JReferenceType, Integer> queryIds) { |
| this.queryIds = queryIds; |
| } |
| |
| public void setRunAsyncReplacements(Map<Integer, RunAsyncReplacement> map) { |
| assert runAsyncReplacements.isEmpty(); |
| runAsyncReplacements = map; |
| } |
| |
| public void setSplitPointInitialSequence(List<Integer> list) { |
| assert splitPointInitialSequence.isEmpty(); |
| splitPointInitialSequence = new ArrayList<Integer>(list); |
| } |
| |
| /** |
| * If <code>method</code> is a static impl method, returns the instance method |
| * that <code>method</code> is the implementation of. Otherwise, returns |
| * <code>null</code>. |
| */ |
| public JMethod staticImplFor(JMethod method) { |
| return staticToInstanceMap.get(method); |
| } |
| |
| /** |
| * Return the greatest lower bound of two types. That is, return the largest |
| * type that is a subtype of both inputs. |
| */ |
| public JReferenceType strongerType(JReferenceType type1, JReferenceType type2) { |
| if (type1 == type2) { |
| return type1; |
| } |
| |
| if (type1 instanceof JNonNullType != type2 instanceof JNonNullType) { |
| // If either is non-nullable, the result should be non-nullable. |
| return strongerType(getNonNullType(type1), getNonNullType(type2)); |
| } |
| |
| if (typeOracle.canTriviallyCast(type1, type2)) { |
| return type1; |
| } |
| |
| if (typeOracle.canTriviallyCast(type2, type1)) { |
| return type2; |
| } |
| |
| // cannot determine a strong type, just return the first one (this makes two |
| // "unrelated" interfaces work correctly in TypeTightener |
| return type1; |
| } |
| |
| public void traverse(JVisitor visitor, Context ctx) { |
| if (visitor.visit(this, ctx)) { |
| visitor.accept(allTypes); |
| } |
| visitor.endVisit(this, ctx); |
| } |
| |
| private int classifyType(JReferenceType type) { |
| assert !(type instanceof JNonNullType); |
| if (type instanceof JNullType) { |
| return IS_NULL; |
| } else if (type instanceof JInterfaceType) { |
| return IS_INTERFACE; |
| } else if (type instanceof JArrayType) { |
| return IS_ARRAY; |
| } else if (type instanceof JClassType) { |
| return IS_CLASS; |
| } |
| throw new InternalCompilerException("Unknown reference type"); |
| } |
| |
| private int countSuperTypes(JReferenceType type) { |
| if (type instanceof JArrayType) { |
| JType leafType = ((JArrayType) type).getLeafType(); |
| if (leafType instanceof JReferenceType) { |
| // however many steps from Foo[] -> Object[] + 1 for Object[]->Object |
| return countSuperTypes((JReferenceType) leafType) + 1; |
| } else { |
| // primitive array types can only cast up to object |
| return 1; |
| } |
| } |
| int count = 0; |
| while ((type = type.getSuperClass()) != null) { |
| ++count; |
| } |
| return count; |
| } |
| |
| private SourceInfo createLiteralSourceInfo(String description) { |
| return intrinsic.makeChild(getClass(), description); |
| } |
| |
| private SourceInfo createLiteralSourceInfo(String description, Literal literal) { |
| SourceInfo child = createLiteralSourceInfo(description); |
| child.addCorrelation(correlator.by(literal)); |
| return child; |
| } |
| |
| /** |
| * See notes in {@link #writeObject(ObjectOutputStream)}. |
| * |
| * @see #writeObject(ObjectOutputStream) |
| */ |
| @SuppressWarnings("unchecked") |
| private void readObject(ObjectInputStream stream) throws IOException, |
| ClassNotFoundException { |
| allTypes = (List<JDeclaredType>) stream.readObject(); |
| for (JDeclaredType type : allTypes) { |
| type.readMembers(stream); |
| } |
| for (JDeclaredType type : allTypes) { |
| type.readMethodBodies(stream); |
| } |
| stream.defaultReadObject(); |
| } |
| |
| /** |
| * Serializing the Java AST is a multi-step process to avoid blowing out the |
| * stack. |
| * |
| * <ol> |
| * <li>Write all declared types in a lightweight manner to establish object |
| * identity for types</li> |
| * <li>Write all fields; write all methods in a lightweight manner to |
| * establish object identity for methods</li> |
| * <li>Write all method bodies</li> |
| * <li>Write everything else, which will mostly refer to already-serialized |
| * objects.</li> |
| * <li>Write the bodies of the entry methods (unlike all other methods, these |
| * are not contained by any type.</li> |
| * </ol> |
| * |
| * The goal of this process to to avoid "running away" with the stack. Without |
| * special logic here, lots of things would reference types, method body code |
| * would reference both types and other methods, and really, really long |
| * recursion chains would result. |
| */ |
| private void writeObject(ObjectOutputStream stream) throws IOException { |
| stream.writeObject(allTypes); |
| for (JDeclaredType type : allTypes) { |
| type.writeMembers(stream); |
| } |
| for (JDeclaredType type : allTypes) { |
| type.writeMethodBodies(stream); |
| } |
| stream.defaultWriteObject(); |
| } |
| } |