/* * 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.common.InliningMode; import com.google.gwt.dev.javac.JsInteropUtil; 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.ast.js.JsniMethodBody; import com.google.gwt.dev.jjs.impl.JjsUtils; import com.google.gwt.dev.util.StringInterner; import com.google.gwt.dev.util.collect.Lists; import com.google.gwt.thirdparty.guava.common.collect.Iterables; import com.google.gwt.thirdparty.guava.common.collect.Sets; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; /** * A Java method implementation. */ public class JMethod extends JNode implements JMember, CanBeAbstract { public static final Comparator<JMethod> BY_SIGNATURE_COMPARATOR = new Comparator<JMethod>() { @Override public int compare(JMethod m1, JMethod m2) { return m1.getSignature().compareTo(m2.getSignature()); } }; private JsMemberType jsMemberType = JsMemberType.NONE; private String jsName; private String jsNamespace; private boolean exported; private boolean isJsOverlay = false; private Specialization specialization; private InliningMode inliningMode = InliningMode.NORMAL; private boolean preventDevirtualization = false; private boolean hasSideEffects = true; private boolean defaultMethod = false; private boolean syntheticAccidentalOverride = false; private Set<String> suppressedWarnings; @Override public void setJsMemberInfo(JsMemberType type, String namespace, String name, boolean exported) { this.jsMemberType = type; this.jsName = name; this.jsNamespace = namespace; this.exported = exported; } @Override public boolean isJsInteropEntryPoint() { return exported && !needsDynamicDispatch() && !isJsNative() && !isJsOverlay(); } @Override public boolean canBeReferencedExternally() { if (isJsOverlay() || (!needsDynamicDispatch() && isJsNative())) { // JsOverlays, native constructors and native static methods can not be referenced // externally return false; } for (JMethod method : getOverriddenMethodsIncludingSelf()) { if (method.exported || method.isJsFunctionMethod() || method.isJsNative()) { return true; } } return false; } @Override public boolean canBeImplementedExternally() { return isJsNative() || isJsFunctionMethod(); } /** * Adds a new final parameter to this method. */ public JParameter createFinalParameter(SourceInfo info, String name, JType type) { return createParameter(info, name, type, true, false, false, false); } /** * Adds a new parameter to this method. */ public JParameter createParameter(SourceInfo info, String name, JType type) { return createParameter(info, name, type, false, false, false, false); } /** * Adds a new parameter to this method. */ public JParameter createParameter(SourceInfo info, String name, JType type, boolean isFinal, boolean isVarargs) { return createParameter(info, name, type, isFinal, isVarargs, false, false); } /** * Adds a new parameter to this method that is a copy of {@code from}. */ public JParameter cloneParameter(JParameter from) { return createParameter(from.getSourceInfo(), from.getName(), from.getType(), from.isFinal(), from.isVarargs(), from.isThis(), from.isOptional()); } /** * Creates a parameter to hold the value of this in devirtualized methods. */ public JParameter createThisParameter(SourceInfo info, JType type) { return createParameter(info, "this$static", type, true, false, true, false); } private JParameter createParameter(SourceInfo info, String name, JType type, boolean isFinal, boolean isVarargs, boolean isThis, boolean isOptional) { assert (name != null); assert (type != null); JParameter parameter = new JParameter(info, name, type, isFinal, isVarargs, isThis, isOptional); addParameter(parameter); return parameter; } /** * Adds a parameter to this method. */ private void addParameter(JParameter x) { // Local types can capture local variables and sandwich the parameters of constructors between // the outer reference and the local captures. assert params.isEmpty() || !params.get(params.size() - 1).isVarargs() || getEnclosingType().getClassDisposition().isLocalType(); params = Lists.add(params, x); } private boolean isInterfaceMethod() { return enclosingType instanceof JInterfaceType; } @Override public String getJsNamespace() { if (jsNamespace == null) { jsNamespace = enclosingType.getQualifiedJsName(); } return jsNamespace; } @Override public String getQualifiedJsName() { String namespace = getJsNamespace(); String actualJsName = getJsName(); if (actualJsName.isEmpty()) { assert !needsDynamicDispatch(); return namespace; } else if (JsInteropUtil.isGlobal(namespace) || JsInteropUtil.isWindow(namespace)) { assert !needsDynamicDispatch(); return namespace + "." + actualJsName; } else { return namespace + (isStatic() ? "." : ".prototype.") + actualJsName; } } @Override public String getJsName() { for (JMethod method : getOverriddenMethodsIncludingSelf()) { if (method.jsName != null) { return method.jsName; } } return getJsMemberType().computeName(this); } public boolean isJsConstructor() { return getJsMemberType() == JsMemberType.CONSTRUCTOR; } /** * Returns {@code true} if this method is the first JsMember in the method hierarchy that exposes * an existing non-JsMember inside a class. */ public boolean exposesNonJsMember() { if (isInterfaceMethod() || enclosingType.isJsNative() || !JjsUtils.requiresJsName(this)) { return false; } boolean hasNonJsMemberParent = false; for (JMethod overriddenMethod : overriddenMethods) { if (!JjsUtils.requiresJsName(overriddenMethod)) { hasNonJsMemberParent = true; } if (overriddenMethod.exposesNonJsMember()) { return false; // some other method already exposed this method. } } return hasNonJsMemberParent; } @Override public JsMemberType getJsMemberType() { for (JMethod method : getOverriddenMethodsIncludingSelf()) { if (method.jsMemberType != JsMemberType.NONE) { return method.jsMemberType; } } return JsMemberType.NONE; } private boolean isJsFunctionMethod() { return enclosingType.isJsFunction() && isAbstract(); } public boolean isOrOverridesJsFunctionMethod() { for (JMethod method : getOverriddenMethodsIncludingSelf()) { if (method.isJsFunctionMethod()) { return true; } } return false; } @Override public boolean isJsNative() { return body == null || (!isJsOverlay() && getEnclosingType().isJsNative() && !JProgram.isClinit(this)); } @Override public boolean isJsOverlay() { return isJsOverlay || getEnclosingType().isJsoType(); } public void setSyntheticAccidentalOverride() { this.syntheticAccidentalOverride = true; } public boolean isSyntheticAccidentalOverride() { return syntheticAccidentalOverride; } public void setSpecialization(List<JType> paramTypes, JType returnsType, String targetMethod) { this.specialization = new Specialization(paramTypes, returnsType == null ? this.getOriginalReturnType() : returnsType, targetMethod); } public Specialization getSpecialization() { return specialization; } public void removeSpecialization() { specialization = null; } public boolean isInliningAllowed() { return inliningMode != InliningMode.DO_NOT_INLINE; } public InliningMode getInliningMode() { return inliningMode; } public void setInliningMode(InliningMode inliningMode) { this.inliningMode = inliningMode; } public boolean hasSideEffects() { return hasSideEffects; } public void setHasSideEffects(boolean hasSideEffects) { this.hasSideEffects = hasSideEffects; } public void setDefaultMethod(boolean defaultMethod) { this.defaultMethod = defaultMethod; } @Override public void setJsOverlay() { this.isJsOverlay = true; } public boolean isDefaultMethod() { return defaultMethod; } public boolean isDevirtualizationAllowed() { return !preventDevirtualization; } public void disallowDevirtualization() { this.preventDevirtualization = true; } @Override public boolean isJsMethodVarargs() { if (getParams().isEmpty() || !(canBeReferencedExternally() || canBeImplementedExternally())) { return false; } JParameter lastParameter = Iterables.getLast(getParams()); return lastParameter.isVarargs(); } /** * AST representation of @SpecializeMethod. */ public static class Specialization implements Serializable { private List<JType> params; private JType returns; private String target; private JMethod targetMethod; public Specialization(List<JType> params, JType returns, String target) { this.params = params; this.returns = returns; this.target = target; } public List<JType> getParams() { return params; } public JType getReturns() { return returns; } public String getTarget() { return target; } public JMethod getTargetMethod() { return targetMethod; } public void resolve(List<JType> resolvedParams, JType resolvedReturn, JMethod targetMethod) { this.params = resolvedParams; this.returns = resolvedReturn; this.targetMethod = targetMethod; } } private static class ExternalSerializedForm implements Serializable { private final JDeclaredType enclosingType; private final String signature; public ExternalSerializedForm(JMethod method) { enclosingType = method.getEnclosingType(); signature = method.getSignature(); } private Object readResolve() { return new JMethod(signature, enclosingType, false); } } private static class ExternalSerializedNullMethod implements Serializable { public static final ExternalSerializedNullMethod INSTANCE = new ExternalSerializedNullMethod(); private Object readResolve() { return NULL_METHOD; } } public static final JMethod NULL_METHOD = new JMethod(SourceOrigin.UNKNOWN, "nullMethod", JClassType.NULL_CLASS, JReferenceType.NULL_TYPE, false, false, true, AccessModifier.PUBLIC); static { NULL_METHOD.setSynthetic(); NULL_METHOD.freezeParamTypes(); } protected transient String signature; /** * The access modifier; stored as an int to reduce memory / serialization footprint. */ private int access; /** * Special serialization treatment. */ private transient JAbstractMethodBody body = null; private final JDeclaredType enclosingType; private boolean isAbstract; private boolean isFinal; private final boolean isStatic; private boolean isSynthetic = false; private boolean isForwarding = false; private final String name; private List<JType> originalParamTypes; private JType originalReturnType; /** * References to any methods which this method overrides. This should be an * EXHAUSTIVE list, that is, if C overrides B overrides A, then C's overrides * list will contain both A and B. */ private Set<JMethod> overriddenMethods = Sets.newLinkedHashSet(); private Set<JMethod> overridingMethods = Sets.newLinkedHashSet(); private List<JParameter> params = Collections.emptyList(); private JType returnType; private List<JClassType> thrownExceptions = Collections.emptyList(); /** * These are only supposed to be constructed by JProgram. */ public JMethod(SourceInfo info, String name, JDeclaredType enclosingType, JType returnType, boolean isAbstract, boolean isStatic, boolean isFinal, AccessModifier access) { super(info); this.name = StringInterner.get().intern(name); this.enclosingType = enclosingType; this.returnType = returnType; this.isAbstract = isAbstract; this.isStatic = isStatic; this.isFinal = isFinal; this.access = access.ordinal(); } /** * Creates an externalized representation for a method that needs to be resolved. * Useful to refer to methods of magic classes during GwtAstBuilder execution. * * @param fullClassName the class where the method is defined. * @param signature the signature of the method (including its name). * */ public static JMethod getExternalizedMethod(String fullClassName, String signature, boolean isStatic) { JClassType cls = new JClassType(fullClassName); return new JMethod(signature, cls, isStatic); } /** * Construct a bare-bones deserialized external method. */ private JMethod(String signature, JDeclaredType enclosingType, boolean isStatic) { this(SourceOrigin.UNKNOWN, StringInterner.get().intern( signature.substring(0, signature.indexOf('('))), enclosingType, null, false, isStatic, false, AccessModifier.PUBLIC); this.signature = signature; } /** * Add a method that this method overrides. */ public void addOverriddenMethod(JMethod overriddenMethod) { assert canBePolymorphic() : this + " is not polymorphic"; assert overriddenMethod != this : this + " cannot override itself"; overriddenMethods.add(overriddenMethod); } /** * Add a method that overrides this method. */ public void addOverridingMethod(JMethod overridingMethod) { assert canBePolymorphic() : this + " is not polymorphic"; assert overridingMethod != this : this + " cannot override itself"; overridingMethods.add(overridingMethod); } public void addThrownException(JClassType exceptionType) { thrownExceptions = Lists.add(thrownExceptions, exceptionType); } public void addThrownExceptions(List<JClassType> exceptionTypes) { thrownExceptions = Lists.addAll(thrownExceptions, exceptionTypes); } /** * Returns true if this method can participate in virtual dispatch. Returns * true for non-private instance methods; false for static methods, private * instance methods, and constructors. */ public boolean canBePolymorphic() { return !isStatic() && !isPrivate(); } public void freezeParamTypes() { List<JType> paramTypes = new ArrayList<JType>(); for (JParameter param : params) { paramTypes.add(param.getType()); } setOriginalTypes(returnType, paramTypes); } /** * Returns {@code true} if this method is the first method in the method hierarchy that increases * the visibility of a package private method. */ public boolean exposesPackagePrivateMethod() { if (isPrivate() || isPackagePrivate()) { return false; } boolean hasPackageVisibleParent = false; for (JMethod overriddenMethod : overriddenMethods) { if (overriddenMethod.isInterfaceMethod()) { continue; } if (!overriddenMethod.isPackagePrivate()) { return false; // some other method already exposed this method. } hasPackageVisibleParent = true; } return hasPackageVisibleParent; } public AccessModifier getAccess() { return AccessModifier.values()[access]; } public JAbstractMethodBody getBody() { assert !isExternal() : "External types do not have method bodies."; return body; } @Override public JDeclaredType getEnclosingType() { return enclosingType; } @Override public String getName() { return name; } @Override public String getQualifiedName() { return enclosingType.getName() + "." + getSignature(); } public List<JType> getOriginalParamTypes() { return originalParamTypes; } public JType getOriginalReturnType() { return originalReturnType; } /** * Returns the transitive closure of all the methods this method overrides; this set is ordered * from most specific to least specific, where class methods appear before interface methods. */ public Set<JMethod> getOverriddenMethods() { return overriddenMethods; } /** * Return all overridden methods including the method itself (where it is the first item). */ private Iterable<JMethod> getOverriddenMethodsIncludingSelf() { return Iterables.concat(Collections.singleton(this), overriddenMethods); } /** * Returns the transitive closure of all the methods that override this method; caveat this * list is only complete in monolithic compiles and should not be used in incremental compiles. * The returned set is ordered in such a way that most specific overriding methods appear after * less specific ones. */ public Set<JMethod> getOverridingMethods() { return overridingMethods; } /** * Returns the parameters of this method. */ public List<JParameter> getParams() { return params; } public String getSignature() { if (signature == null) { signature = StringInterner.get().intern(JjsUtils.computeSignature( name, getOriginalParamTypes(), getOriginalReturnType(), isConstructor())); } return signature; } public String getJsniSignature(boolean includeEnclosingClass, boolean includeReturnType) { StringBuilder sb = new StringBuilder(); if (includeEnclosingClass) { sb.append(getEnclosingType().getName()); sb.append("::"); } sb.append(name); sb.append('('); for (JType type : getOriginalParamTypes()) { sb.append(type.getJsniSignatureName()); } sb.append(')'); if (includeReturnType) { sb.append(originalReturnType.getJsniSignatureName()); } return sb.toString(); } public List<JClassType> getThrownExceptions() { return thrownExceptions; } @Override public JType getType() { return returnType; } @Override public boolean isAbstract() { return isAbstract; } public boolean isConstructor() { return false; } public boolean isPackagePrivate() { return access == AccessModifier.DEFAULT.ordinal(); } @Override public boolean isExternal() { return getEnclosingType().isExternal(); } @Override public boolean isFinal() { return isFinal; } public boolean isForwarding() { return isForwarding; } public boolean isJsniMethod() { if (body == null) { return false; } else { return body.isJsniMethodBody(); } } @Override public boolean isPrivate() { return access == AccessModifier.PRIVATE.ordinal(); } @Override public boolean isPublic() { return access == AccessModifier.PUBLIC.ordinal(); } @Override public boolean isStatic() { return isStatic; } @Override public boolean isSynthetic() { return isSynthetic; } /** * Returns <code>true</code> if this method can participate in instance * dispatch. */ @Override public boolean needsDynamicDispatch() { return !isStatic(); } /** * Removes the parameter at the specified index. */ public void removeParam(int index) { params = Lists.remove(params, index); if (isJsniMethod()) { ((JsniMethodBody) getBody()).getFunc().getParameters().remove(index); } } /** * Resolve an external references during AST stitching. */ public void resolve(JType originalReturnType, List<JType> originalParamTypes, JType returnType, List<JClassType> thrownExceptions) { if (getClass().desiredAssertionStatus()) { assert originalReturnType.replaces(this.originalReturnType); assert JType.replaces(originalParamTypes, this.originalParamTypes); assert returnType.replaces(this.returnType); assert JType.replaces(thrownExceptions, this.thrownExceptions); } this.originalReturnType = originalReturnType; this.originalParamTypes = Lists.normalize(originalParamTypes); this.returnType = returnType; this.thrownExceptions = Lists.normalize(thrownExceptions); } public void setAbstract(boolean isAbstract) { this.isAbstract = isAbstract; } public void setBody(JAbstractMethodBody body) { this.body = body; if (body != null) { body.setMethod(this); } } @Override public void setFinal() { setFinal(true); } public void setFinal(boolean isFinal) { this.isFinal = isFinal; } public void setForwarding() { isForwarding = true; } public void setOriginalTypes(JType returnType, List<JType> paramTypes) { if (originalParamTypes != null) { throw new InternalCompilerException("Param types already frozen"); } originalReturnType = returnType; originalParamTypes = Lists.normalize(paramTypes); } public void setSynthetic() { isSynthetic = true; } public void setType(JType newType) { returnType = newType; } @Override public Set<String> getSuppressedWarnings() { return suppressedWarnings; } @Override public void setSuppressedWarnings(Set<String> suppressedWarnings) { this.suppressedWarnings = suppressedWarnings; } @Override public void traverse(JVisitor visitor, Context ctx) { if (visitor.visit(this, ctx)) { visitChildren(visitor); } visitor.endVisit(this, ctx); } protected void visitChildren(JVisitor visitor) { params = visitor.acceptImmutable(params); if (body != null) { body = (JAbstractMethodBody) visitor.accept(body); } } protected Object writeReplace() { if (isExternal()) { return new ExternalSerializedForm(this); } else if (this == NULL_METHOD) { return ExternalSerializedNullMethod.INSTANCE; } else { return this; } } /** * See {@link #writeBody(ObjectOutputStream)}. * * @see #writeBody(ObjectOutputStream) */ void readBody(ObjectInputStream stream) throws IOException, ClassNotFoundException { body = (JAbstractMethodBody) stream.readObject(); } boolean replaces(JMethod originalMethod) { if (this == originalMethod) { return true; } return originalMethod.isExternal() && originalMethod.getSignature().equals(this.getSignature()) && this.getEnclosingType().replaces(originalMethod.getEnclosingType()); } /** * After all types, fields, and methods are written to the stream, this method * writes method bodies to the stream. * * @see JProgram#writeObject(ObjectOutputStream) */ void writeBody(ObjectOutputStream stream) throws IOException { stream.writeObject(body); } }