| /* |
| * 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.impl; |
| |
| import static com.google.gwt.dev.js.JsUtils.createAssignment; |
| import static com.google.gwt.dev.js.JsUtils.createInvocationOrPropertyAccess; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.linker.impl.StandardSymbolData; |
| import com.google.gwt.dev.CompilerContext; |
| import com.google.gwt.dev.MinimalRebuildCache; |
| import com.google.gwt.dev.PrecompileTaskOptions; |
| import com.google.gwt.dev.cfg.PermutationProperties; |
| import com.google.gwt.dev.common.InliningMode; |
| import com.google.gwt.dev.javac.JsInteropUtil; |
| import com.google.gwt.dev.jjs.HasSourceInfo; |
| 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.Context; |
| import com.google.gwt.dev.jjs.ast.HasEnclosingType; |
| import com.google.gwt.dev.jjs.ast.HasJsInfo.JsMemberType; |
| import com.google.gwt.dev.jjs.ast.HasName; |
| import com.google.gwt.dev.jjs.ast.JAbstractMethodBody; |
| import com.google.gwt.dev.jjs.ast.JArrayLength; |
| import com.google.gwt.dev.jjs.ast.JArrayRef; |
| import com.google.gwt.dev.jjs.ast.JArrayType; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperation; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperator; |
| import com.google.gwt.dev.jjs.ast.JBlock; |
| import com.google.gwt.dev.jjs.ast.JBreakStatement; |
| import com.google.gwt.dev.jjs.ast.JCaseStatement; |
| import com.google.gwt.dev.jjs.ast.JCastMap; |
| import com.google.gwt.dev.jjs.ast.JCastOperation; |
| import com.google.gwt.dev.jjs.ast.JClassLiteral; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JConditional; |
| import com.google.gwt.dev.jjs.ast.JConstructor; |
| import com.google.gwt.dev.jjs.ast.JContinueStatement; |
| import com.google.gwt.dev.jjs.ast.JDeclarationStatement; |
| import com.google.gwt.dev.jjs.ast.JDeclaredType; |
| import com.google.gwt.dev.jjs.ast.JDoStatement; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JExpressionStatement; |
| import com.google.gwt.dev.jjs.ast.JField; |
| import com.google.gwt.dev.jjs.ast.JFieldRef; |
| import com.google.gwt.dev.jjs.ast.JForStatement; |
| import com.google.gwt.dev.jjs.ast.JIfStatement; |
| import com.google.gwt.dev.jjs.ast.JInterfaceType; |
| import com.google.gwt.dev.jjs.ast.JLabel; |
| import com.google.gwt.dev.jjs.ast.JLabeledStatement; |
| import com.google.gwt.dev.jjs.ast.JLiteral; |
| import com.google.gwt.dev.jjs.ast.JLocal; |
| import com.google.gwt.dev.jjs.ast.JLocalRef; |
| import com.google.gwt.dev.jjs.ast.JMember; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodBody; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JNameOf; |
| import com.google.gwt.dev.jjs.ast.JNewInstance; |
| import com.google.gwt.dev.jjs.ast.JNode; |
| import com.google.gwt.dev.jjs.ast.JNullLiteral; |
| import com.google.gwt.dev.jjs.ast.JNumericEntry; |
| import com.google.gwt.dev.jjs.ast.JParameter; |
| import com.google.gwt.dev.jjs.ast.JParameterRef; |
| import com.google.gwt.dev.jjs.ast.JPermutationDependentValue; |
| import com.google.gwt.dev.jjs.ast.JPostfixOperation; |
| import com.google.gwt.dev.jjs.ast.JPrefixOperation; |
| import com.google.gwt.dev.jjs.ast.JPrimitiveType; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| import com.google.gwt.dev.jjs.ast.JReturnStatement; |
| import com.google.gwt.dev.jjs.ast.JRunAsync; |
| import com.google.gwt.dev.jjs.ast.JStatement; |
| import com.google.gwt.dev.jjs.ast.JSwitchStatement; |
| import com.google.gwt.dev.jjs.ast.JThisRef; |
| import com.google.gwt.dev.jjs.ast.JThrowStatement; |
| import com.google.gwt.dev.jjs.ast.JTransformer; |
| import com.google.gwt.dev.jjs.ast.JTryStatement; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JUnaryOperator; |
| import com.google.gwt.dev.jjs.ast.JVariable; |
| import com.google.gwt.dev.jjs.ast.JVisitor; |
| import com.google.gwt.dev.jjs.ast.JWhileStatement; |
| import com.google.gwt.dev.jjs.ast.RuntimeConstants; |
| import com.google.gwt.dev.jjs.ast.js.JDebuggerStatement; |
| import com.google.gwt.dev.jjs.ast.js.JMultiExpression; |
| import com.google.gwt.dev.jjs.ast.js.JsniClassLiteral; |
| import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; |
| import com.google.gwt.dev.jjs.ast.js.JsonArray; |
| import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeMapper; |
| import com.google.gwt.dev.js.JsStackEmulator; |
| import com.google.gwt.dev.js.JsUtils; |
| import com.google.gwt.dev.js.JsUtils.InvocationStyle; |
| import com.google.gwt.dev.js.ast.JsArrayAccess; |
| import com.google.gwt.dev.js.ast.JsArrayLiteral; |
| import com.google.gwt.dev.js.ast.JsBinaryOperation; |
| import com.google.gwt.dev.js.ast.JsBinaryOperator; |
| import com.google.gwt.dev.js.ast.JsBlock; |
| import com.google.gwt.dev.js.ast.JsBreak; |
| import com.google.gwt.dev.js.ast.JsCase; |
| import com.google.gwt.dev.js.ast.JsCatch; |
| import com.google.gwt.dev.js.ast.JsConditional; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsContinue; |
| import com.google.gwt.dev.js.ast.JsDebugger; |
| import com.google.gwt.dev.js.ast.JsDefault; |
| import com.google.gwt.dev.js.ast.JsDoWhile; |
| import com.google.gwt.dev.js.ast.JsEmpty; |
| import com.google.gwt.dev.js.ast.JsExprStmt; |
| import com.google.gwt.dev.js.ast.JsExpression; |
| import com.google.gwt.dev.js.ast.JsFor; |
| import com.google.gwt.dev.js.ast.JsFunction; |
| import com.google.gwt.dev.js.ast.JsIf; |
| import com.google.gwt.dev.js.ast.JsInvocation; |
| import com.google.gwt.dev.js.ast.JsLabel; |
| import com.google.gwt.dev.js.ast.JsLiteral; |
| import com.google.gwt.dev.js.ast.JsModVisitor; |
| import com.google.gwt.dev.js.ast.JsName; |
| import com.google.gwt.dev.js.ast.JsNameOf; |
| import com.google.gwt.dev.js.ast.JsNameRef; |
| import com.google.gwt.dev.js.ast.JsNew; |
| import com.google.gwt.dev.js.ast.JsNode; |
| import com.google.gwt.dev.js.ast.JsNormalScope; |
| import com.google.gwt.dev.js.ast.JsNullLiteral; |
| import com.google.gwt.dev.js.ast.JsNumberLiteral; |
| import com.google.gwt.dev.js.ast.JsNumericEntry; |
| import com.google.gwt.dev.js.ast.JsObjectLiteral; |
| import com.google.gwt.dev.js.ast.JsParameter; |
| import com.google.gwt.dev.js.ast.JsPositionMarker; |
| import com.google.gwt.dev.js.ast.JsPositionMarker.Type; |
| import com.google.gwt.dev.js.ast.JsPostfixOperation; |
| import com.google.gwt.dev.js.ast.JsPrefixOperation; |
| import com.google.gwt.dev.js.ast.JsProgram; |
| import com.google.gwt.dev.js.ast.JsReturn; |
| import com.google.gwt.dev.js.ast.JsRootScope; |
| import com.google.gwt.dev.js.ast.JsScope; |
| import com.google.gwt.dev.js.ast.JsStatement; |
| import com.google.gwt.dev.js.ast.JsStringLiteral; |
| import com.google.gwt.dev.js.ast.JsSwitch; |
| import com.google.gwt.dev.js.ast.JsSwitchMember; |
| import com.google.gwt.dev.js.ast.JsThisRef; |
| import com.google.gwt.dev.js.ast.JsThrow; |
| import com.google.gwt.dev.js.ast.JsTry; |
| import com.google.gwt.dev.js.ast.JsUnaryOperator; |
| import com.google.gwt.dev.js.ast.JsVars; |
| import com.google.gwt.dev.js.ast.JsVars.JsVar; |
| import com.google.gwt.dev.js.ast.JsWhile; |
| import com.google.gwt.dev.util.Pair; |
| import com.google.gwt.dev.util.StringInterner; |
| import com.google.gwt.dev.util.arg.OptionMethodNameDisplayMode; |
| import com.google.gwt.dev.util.arg.OptionOptimize; |
| import com.google.gwt.dev.util.collect.Stack; |
| import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; |
| import com.google.gwt.thirdparty.guava.common.base.Function; |
| import com.google.gwt.thirdparty.guava.common.base.Joiner; |
| import com.google.gwt.thirdparty.guava.common.base.Predicate; |
| import com.google.gwt.thirdparty.guava.common.base.Predicates; |
| import com.google.gwt.thirdparty.guava.common.collect.FluentIterable; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableList; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedSet; |
| import com.google.gwt.thirdparty.guava.common.collect.Iterables; |
| import com.google.gwt.thirdparty.guava.common.collect.Lists; |
| import com.google.gwt.thirdparty.guava.common.collect.Maps; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeMap; |
| |
| /** |
| * Creates a JavaScript AST from a <code>JProgram</code> node. |
| */ |
| public class GenerateJavaScriptAST { |
| |
| /** |
| * Finds the nodes that are targets of JNameOf so that a name is assigned to them. |
| */ |
| private class FindNameOfTargets extends JVisitor { |
| @Override |
| public void endVisit(JNameOf x, Context ctx) { |
| nameOfTargets.add(x.getNode()); |
| } |
| } |
| |
| private class CreateNamesAndScopesVisitor extends JVisitor { |
| |
| /** |
| * Cache of computed Java source file names to URI strings for symbol |
| * export. By using a cache we also ensure the miminum number of String |
| * instances are serialized. |
| */ |
| private final Map<String, String> fileNameToUriString = Maps.newHashMap(); |
| |
| private final Stack<JsScope> scopeStack = new Stack<JsScope>(); |
| |
| private JMethod currentMethod; |
| |
| @Override |
| public boolean visit(JProgram x, Context ctx) { |
| // Scopes and name objects need to be calculated within all types, even reference-only ones. |
| // This information is used to be able to detect and avoid name collisions during pretty or |
| // obfuscated JS variable name generation. |
| x.visitAllTypes(this); |
| return false; |
| } |
| |
| @Override |
| public void endVisit(JArrayType x, Context ctx) { |
| JsName name = topScope.declareName(x.getName()); |
| names.put(x, name); |
| recordSymbol(x, name); |
| } |
| |
| @Override |
| public void endVisit(JClassType x, Context ctx) { |
| scopeStack.pop(); |
| } |
| |
| @Override |
| public void endVisit(JField x, Context ctx) { |
| JsName jsName; |
| if (x.isStatic()) { |
| jsName = topScope.declareName(mangleName(x), x.getName()); |
| } else { |
| jsName = JjsUtils.exposesJsName(x) |
| ? scopeStack.peek().declareUnobfuscatableName(x.getJsName()) |
| : scopeStack.peek().declareName(mangleName(x), x.getName()); |
| } |
| names.put(x, jsName); |
| recordSymbol(x, jsName); |
| } |
| |
| @Override |
| public void endVisit(JInterfaceType x, Context ctx) { |
| scopeStack.pop(); |
| } |
| |
| @Override |
| public void endVisit(JLabel x, Context ctx) { |
| if (names.get(x) != null) { |
| return; |
| } |
| names.put(x, scopeStack.peek().declareName(x.getName())); |
| } |
| |
| @Override |
| public void endVisit(JLocal x, Context ctx) { |
| // locals can conflict, that's okay just reuse the same variable |
| JsScope scope = scopeStack.peek(); |
| JsName jsName = scope.declareName(x.getName()); |
| names.put(x, jsName); |
| } |
| |
| @Override |
| public void endVisit(JMethod x, Context ctx) { |
| if (doesNotHaveConcreteImplementation(x)) { |
| return; |
| } |
| scopeStack.pop(); |
| } |
| |
| @Override |
| public void endVisit(JParameter x, Context ctx) { |
| if (x.isVarargs() && currentMethod.isJsMethodVarargs()) { |
| names.put(x, scopeStack.peek().declareUnobfuscatableName("arguments")); |
| return; |
| } |
| names.put(x, scopeStack.peek().declareName(x.getName())); |
| } |
| |
| @Override |
| public void endVisit(JProgram x, Context ctx) { |
| /* |
| * put the null method and field into objectScope since they can be |
| * referenced as instance on null-types (as determined by type flow) |
| */ |
| JMethod nullMethod = x.getNullMethod(); |
| polymorphicNames.put(nullMethod, objectScope.declareName("$_nullMethod")); |
| JField nullField = x.getNullField(); |
| JsName nullFieldName = objectScope.declareName("$_nullField"); |
| names.put(nullField, nullFieldName); |
| |
| /* |
| * Create names for instantiable array types since JProgram.traverse() |
| * doesn't iterate over them. |
| */ |
| for (JArrayType arrayType : program.getAllArrayTypes()) { |
| if (program.typeOracle.isInstantiatedType(arrayType)) { |
| accept(arrayType); |
| } |
| } |
| } |
| |
| @Override |
| public boolean visit(JClassType x, Context ctx) { |
| // have I already been visited as a super type? |
| JsScope myScope = classScopes.get(x); |
| if (myScope != null) { |
| scopeStack.push(myScope); |
| return false; |
| } |
| |
| // My seed function name |
| JsName jsName = topScope.declareName(JjsUtils.mangledNameString(x), x.getShortName()); |
| names.put(x, jsName); |
| recordSymbol(x, jsName); |
| |
| // My class scope |
| if (x.getSuperClass() == null) { |
| myScope = objectScope; |
| } else { |
| JsScope parentScope = classScopes.get(x.getSuperClass()); |
| // Run my superclass first! |
| if (parentScope == null) { |
| accept(x.getSuperClass()); |
| } |
| parentScope = classScopes.get(x.getSuperClass()); |
| assert (parentScope != null); |
| /* |
| * WEIRD: we wedge the global interface scope in between object and all |
| * of its subclasses; this ensures that interface method names trump all |
| * (except Object method names) |
| */ |
| if (parentScope == objectScope) { |
| parentScope = interfaceScope; |
| } |
| myScope = new JsNormalScope(parentScope, "class " + x.getShortName()); |
| } |
| classScopes.put(x, myScope); |
| |
| scopeStack.push(myScope); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JInterfaceType x, Context ctx) { |
| // interfaces have no name at run time |
| scopeStack.push(interfaceScope); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JMethod x, Context ctx) { |
| currentMethod = x; |
| // my polymorphic name |
| String name = x.getName(); |
| if (x.needsDynamicDispatch()) { |
| if (polymorphicNames.get(x) == null) { |
| JsName polyName = JjsUtils.exposesJsName(x) |
| ? interfaceScope.declareUnobfuscatableName(x.getJsName()) |
| : interfaceScope.declareName(mangleNameForPoly(x), name); |
| polymorphicNames.put(x, polyName); |
| } |
| } |
| |
| if (doesNotHaveConcreteImplementation(x)) { |
| return false; |
| } |
| |
| // my global name |
| JsName globalName = null; |
| assert x.getEnclosingType() != null; |
| String mangleName = mangleNameForGlobal(x); |
| |
| if (JProgram.isClinit(x)) { |
| name = name + "_" + x.getEnclosingType().getShortName(); |
| } |
| |
| /* |
| * Only allocate a name for a function if it is native, not polymorphic, |
| * is a JNameOf target or stack-stripping is disabled. |
| */ |
| if (!stripStack || !polymorphicNames.containsKey(x) || x.isJsniMethod() |
| || nameOfTargets.contains(x)) { |
| globalName = topScope.declareName(mangleName, name); |
| names.put(x, globalName); |
| recordSymbol(x, globalName); |
| } |
| JsFunction function; |
| if (x.isJsniMethod()) { |
| // set the global name of the JSNI peer |
| JsniMethodBody body = (JsniMethodBody) x.getBody(); |
| function = body.getFunc(); |
| function.setName(globalName); |
| } else { |
| /* |
| * It would be more correct here to check for an inline assignment, such |
| * as var foo = function blah() {} and introduce a separate scope for |
| * the function's name according to EcmaScript-262, but this would mess |
| * up stack traces by allowing two inner scope function names to |
| * obfuscate to the same identifier, making function names no longer a |
| * 1:1 mapping to obfuscated symbols. Leaving them in global scope |
| * causes no harm. |
| */ |
| function = new JsFunction(x.getSourceInfo(), topScope, globalName, !x.isJsNative()); |
| } |
| |
| jsFunctionsByJavaMethodBody.put(x.getBody(), function); |
| scopeStack.push(function.getScope()); |
| |
| // Don't traverse the method body of methods in referenceOnly types since those method bodies |
| // only exist in JS output of other modules it is their responsibility to handle their naming. |
| return !program.isReferenceOnly(x.getEnclosingType()); |
| } |
| |
| @Override |
| public boolean visit(JTryStatement x, Context ctx) { |
| accept(x.getTryBlock()); |
| for (JTryStatement.CatchClause clause : x.getCatchClauses()) { |
| JLocalRef arg = clause.getArg(); |
| JBlock catchBlock = clause.getBlock(); |
| JsCatch jsCatch = |
| new JsCatch(x.getSourceInfo(), scopeStack.peek(), arg.getTarget().getName()); |
| JsParameter jsParam = jsCatch.getParameter(); |
| names.put(arg.getTarget(), jsParam.getName()); |
| catchMap.put(catchBlock, jsCatch); |
| catchParamIdentifiers.add(jsParam.getName()); |
| |
| scopeStack.push(jsCatch.getScope()); |
| accept(catchBlock); |
| scopeStack.pop(); |
| } |
| |
| // TODO: normalize this so it's never null? |
| if (x.getFinallyBlock() != null) { |
| accept(x.getFinallyBlock()); |
| } |
| return false; |
| } |
| |
| /** |
| * Generate a file name URI string for a source info, for symbol data |
| * export. |
| */ |
| private String makeUriString(HasSourceInfo x) { |
| String fileName = x.getSourceInfo().getFileName(); |
| if (fileName == null) { |
| return null; |
| } |
| String uriString = fileNameToUriString.get(fileName); |
| if (uriString == null) { |
| uriString = StandardSymbolData.toUriString(fileName); |
| fileNameToUriString.put(fileName, uriString); |
| } |
| return uriString; |
| } |
| |
| private void recordSymbol(JReferenceType type, JsName jsName) { |
| if (getRuntimeTypeReference(type) == null || !program.typeOracle.isInstantiatedType(type)) { |
| return; |
| } |
| |
| String typeId = getRuntimeTypeReference(type).toSource(); |
| StandardSymbolData symbolData = |
| StandardSymbolData.forClass(type.getName(), type.getSourceInfo().getFileName(), |
| type.getSourceInfo().getStartLine(), typeId); |
| assert !symbolTable.containsKey(symbolData); |
| symbolTable.put(symbolData, jsName); |
| } |
| |
| private <T extends HasEnclosingType & HasName & HasSourceInfo> void recordSymbol(T member, |
| JsName jsName) { |
| /* |
| * NB: The use of member.getName() can produce confusion in cases where a type |
| * has both polymorphic and static dispatch for a method, because you |
| * might see HashSet::$add() and HashSet::add(). Logically, these methods |
| * should be treated equally, however they will be implemented with |
| * separate global functions and must be recorded independently. |
| * |
| * Automated systems that process the symbol information can easily map |
| * the statically-dispatched function by looking for method names that |
| * begin with a dollar-sign and whose first parameter is the enclosing |
| * type. |
| */ |
| |
| String methodSignature = null; |
| if (member instanceof JMethod) { |
| JMethod method = ((JMethod) member); |
| methodSignature = |
| StringInterner.get().intern(method.getSignature().substring(method.getName().length())); |
| } |
| |
| StandardSymbolData symbolData = |
| StandardSymbolData.forMember(member.getEnclosingType().getName(), member.getName(), |
| methodSignature, makeUriString(member), member.getSourceInfo().getStartLine()); |
| assert !symbolTable.containsKey(symbolData) : "Duplicate symbol recorded " + jsName.getIdent() |
| + " for " + member.getName() + " and key " + symbolData.getJsniIdent(); |
| symbolTable.put(symbolData, jsName); |
| } |
| } |
| |
| private class GenerateJavaScriptTransformer extends JTransformer<JsNode> { |
| |
| public static final String GOOG_ABSTRACT_METHOD = "goog.abstractMethod"; |
| public static final String GOOG_INHERITS = "goog.inherits"; |
| public static final String GOOG_OBJECT_CREATE_SET = "goog.object.createSet"; |
| |
| private final Set<JDeclaredType> alreadyRan = Sets.newLinkedHashSet(); |
| |
| private final Map<JDeclaredType, JsFunction> clinitFunctionForType = Maps.newHashMap(); |
| |
| private JMethod currentMethod = null; |
| |
| private final JsName arrayLength = objectScope.declareUnobfuscatableName("length"); |
| private final JsName globalTemp = topScope.declareUnobfuscatableName("_"); |
| private final JsName prototype = objectScope.declareUnobfuscatableName("prototype"); |
| |
| @Override |
| public JsExpression transformArrayLength(JArrayLength expression) { |
| assert expression.getInstance() != null : "Can't access the length of a null array"; |
| return arrayLength.makeQualifiedRef(expression.getSourceInfo(), |
| transform(expression.getInstance())); |
| } |
| |
| @Override |
| public JsExpression transformArrayRef(JArrayRef arrayRef) { |
| JsArrayAccess jsArrayAccess = new JsArrayAccess(arrayRef.getSourceInfo()); |
| jsArrayAccess.setIndexExpr(transform(arrayRef.getIndexExpr())); |
| jsArrayAccess.setArrayExpr(transform(arrayRef.getInstance())); |
| return jsArrayAccess; |
| } |
| |
| @Override |
| public JsExpression transformBinaryOperation(JBinaryOperation binaryOperation) { |
| JsExpression lhs = transform(binaryOperation.getLhs()); |
| JsExpression rhs = transform(binaryOperation.getRhs()); |
| JsBinaryOperator op = JavaToJsOperatorMap.get(binaryOperation.getOp()); |
| |
| /* |
| * Use === and !== on reference types, or else you can get wrong answers |
| * when Object.toString() == 'some string'. |
| */ |
| if (binaryOperation.getLhs().getType() instanceof JReferenceType |
| && binaryOperation.getRhs().getType() instanceof JReferenceType) { |
| switch (op) { |
| case EQ: |
| op = JsBinaryOperator.REF_EQ; |
| break; |
| case NEQ: |
| op = JsBinaryOperator.REF_NEQ; |
| break; |
| } |
| } |
| return new JsBinaryOperation(binaryOperation.getSourceInfo(), op, lhs, rhs); |
| } |
| |
| @Override |
| public JsStatement transformBlock(JBlock block) { |
| JsBlock jsBlock = new JsBlock(block.getSourceInfo()); |
| List<JsStatement> stmts = jsBlock.getStatements(); |
| |
| transformIntoExcludingNulls(block.getStatements(), stmts); |
| Iterables.removeIf(stmts, Predicates.instanceOf(JsEmpty.class)); |
| return jsBlock; |
| } |
| |
| @Override |
| public JsNode transformBreakStatement(JBreakStatement breakStatement) { |
| SourceInfo info = breakStatement.getSourceInfo(); |
| return new JsBreak(info, transformIntoLabelReference(info, breakStatement.getLabel())); |
| } |
| |
| @Override |
| public JsNode transformCaseStatement(JCaseStatement caseStatement) { |
| if (caseStatement.getExpr() == null) { |
| return new JsDefault(caseStatement.getSourceInfo()); |
| } else { |
| JsCase jsCase = new JsCase(caseStatement.getSourceInfo()); |
| jsCase.setCaseExpr(transform(caseStatement.getExpr())); |
| return jsCase; |
| } |
| } |
| |
| @Override |
| public JsNode transformCastOperation(JCastOperation castOperation) { |
| // These are left in when cast checking is disabled. |
| return transform(castOperation.getExpr()); |
| } |
| |
| @Override |
| public JsNode transformClassLiteral(JClassLiteral classLiteral) { |
| JsName classLit = names.get(classLiteral.getField()); |
| return classLit.makeRef(classLiteral.getSourceInfo()); |
| } |
| |
| @Override |
| public JsNode transformDeclaredType(JDeclaredType type) { |
| // Don't generate JS for types not in current module if separate compilation is on. |
| if (program.isReferenceOnly(type)) { |
| return null; |
| } |
| |
| if (alreadyRan.contains(type)) { |
| return null; |
| } |
| |
| alreadyRan.add(type); |
| |
| if (type.isJsNative()) { |
| // Emit JsOverlay static methods for native JsTypes. |
| emitStaticMethods(type); |
| // Emit JsOverlay (static) fields for native JsTypes. |
| emitFields(type); |
| return null; |
| } |
| |
| checkForDuplicateMethods(type); |
| |
| assert program.getTypeClassLiteralHolder() != type; |
| assert !program.immortalCodeGenTypes.contains(type); |
| // Super classes should be emitted before the actual class. |
| assert type.getSuperClass() == null || program.isReferenceOnly(type.getSuperClass()) || |
| alreadyRan.contains(type.getSuperClass()); |
| |
| emitStaticMethods(type); |
| |
| generateTypeSetup(type); |
| |
| emitFields(type); |
| return null; |
| } |
| |
| @Override |
| public JsNode transformConditional(JConditional conditional) { |
| JsExpression ifTest = transform(conditional.getIfTest()); |
| JsExpression thenExpr = transform(conditional.getThenExpr()); |
| JsExpression elseExpr = transform(conditional.getElseExpr()); |
| return new JsConditional(conditional.getSourceInfo(), ifTest, thenExpr, elseExpr); |
| } |
| |
| @Override |
| public JsNode transformContinueStatement(JContinueStatement continueStatement) { |
| SourceInfo info = continueStatement.getSourceInfo(); |
| return new JsContinue(info, transformIntoLabelReference(info, continueStatement.getLabel())); |
| } |
| |
| @Override |
| public JsNode transformDebuggerStatement(JDebuggerStatement debuggerStatement) { |
| return new JsDebugger(debuggerStatement.getSourceInfo()); |
| } |
| |
| @Override |
| public JsNode transformDeclarationStatement(JDeclarationStatement declarationStatement) { |
| if (declarationStatement.getInitializer() == null) { |
| return null; |
| } |
| |
| JVariable target = declarationStatement.getVariableRef().getTarget(); |
| if (target instanceof JField && initializeAtTopScope((JField) target)) { |
| // Will initialize at top scope; no need to double-initialize. |
| return null; |
| } |
| |
| JsExpression initializer = transform(declarationStatement.getInitializer()); |
| JsNameRef localRef = transform(declarationStatement.getVariableRef()); |
| |
| SourceInfo info = declarationStatement.getSourceInfo(); |
| return JsUtils.createAssignment(info, localRef, initializer).makeStmt(); |
| } |
| |
| @Override |
| public JsNode transformDoStatement(JDoStatement doStatement) { |
| JsDoWhile stmt = new JsDoWhile(doStatement.getSourceInfo()); |
| stmt.setCondition(transform(doStatement.getTestExpr())); |
| stmt.setBody(jsEmptyIfNull(doStatement.getSourceInfo(), transform(doStatement.getBody()))); |
| return stmt; |
| } |
| |
| @Override |
| public JsNode transformExpressionStatement(JExpressionStatement statement) { |
| return ((JsExpression) transform(statement.getExpr())).makeStmt(); |
| } |
| |
| @Override |
| public JsNode transformFieldRef(JFieldRef fieldRef) { |
| JsExpression qualifier = transform(fieldRef.getInstance()); |
| boolean isStatic = fieldRef.getField().isStatic(); |
| return isStatic ? dispatchToStaticField(fieldRef, qualifier) |
| : dispatchToInstanceField(fieldRef, qualifier); |
| } |
| |
| private JsExpression dispatchToStaticField( |
| JFieldRef fieldRef, JsExpression unnecessaryQualifier) { |
| /* |
| * Note: the comma expressions here would cause an illegal tree state if |
| * the result expression ended up on the lhs of an assignment. |
| * {@link JsNormalizer} will fix this situation. |
| */ |
| |
| JsExpression result = createStaticReference(fieldRef.getField(), fieldRef.getSourceInfo()); |
| |
| return JsUtils.createCommaExpression( |
| unnecessaryQualifier, maybeCreateClinitCall(fieldRef.getField()), result); |
| } |
| |
| private JsExpression dispatchToInstanceField(JFieldRef x, JsExpression instance) { |
| return names.get(x.getField()).makeQualifiedRef(x.getSourceInfo(), instance); |
| } |
| |
| @Override |
| public JsNode transformForStatement(JForStatement forStatement) { |
| JsFor result = new JsFor(forStatement.getSourceInfo()); |
| |
| JsExpression initExpr = null; |
| List<JsExprStmt> initStmts = transform(forStatement.getInitializers()); |
| for (int i = 0; i < initStmts.size(); ++i) { |
| JsExprStmt initStmt = initStmts.get(i); |
| if (initStmt != null) { |
| initExpr = JsUtils.createCommaExpression(initExpr, initStmt.getExpression()); |
| } |
| } |
| result.setInitExpr(initExpr); |
| result.setCondition(transform(forStatement.getCondition())); |
| result.setIncrExpr(transform(forStatement.getIncrements())); |
| result.setBody(jsEmptyIfNull(forStatement.getSourceInfo(), transform(forStatement.getBody()))); |
| |
| return result; |
| } |
| |
| @Override |
| public JsNode transformIfStatement(JIfStatement ifStatement) { |
| JsIf result = new JsIf(ifStatement.getSourceInfo()); |
| |
| result.setIfExpr(transform(ifStatement.getIfExpr())); |
| result.setThenStmt(jsEmptyIfNull(ifStatement.getSourceInfo(), |
| transform(ifStatement.getThenStmt()))); |
| result.setElseStmt(transform(ifStatement.getElseStmt())); |
| |
| return result; |
| } |
| |
| @Override |
| public JsLabel transformLabel(JLabel label) { |
| return new JsLabel(label.getSourceInfo(), names.get(label)); |
| } |
| |
| @Override |
| public JsStatement transformLabeledStatement(JLabeledStatement labeledStatement) { |
| JsLabel label = transform(labeledStatement.getLabel()); |
| label.setStmt(transform(labeledStatement.getBody())); |
| return label; |
| } |
| |
| @Override |
| public JsLiteral transformLiteral(JLiteral literal) { |
| return JjsUtils.translateLiteral(literal); |
| } |
| |
| @Override |
| public JsNode transformLocalRef(JLocalRef localRef) { |
| return names.get(localRef.getTarget()).makeRef(localRef.getSourceInfo()); |
| } |
| |
| @Override |
| public JsNode transformMethod(JMethod method) { |
| if (method.isAbstract()) { |
| return generateAbstractMethodDefinition(method); |
| } else if (doesNotHaveConcreteImplementation(method)) { |
| return null; |
| } |
| currentMethod = method; |
| |
| JsFunction function = transform(method.getBody()); |
| function.setInliningMode(method.getInliningMode()); |
| |
| if (!method.isJsniMethod()) { |
| // Setup params on the generated function. A native method already got |
| // its jsParams set when parsed from JSNI. |
| List<JParameter> parameterList = method.getParams(); |
| if (method.isJsMethodVarargs()) { |
| parameterList = parameterList.subList(0, parameterList.size() - 1); |
| } |
| transformInto(parameterList, function.getParameters()); |
| } |
| |
| JsInvocation jsInvocation = maybeCreateClinitCall(method); |
| if (jsInvocation != null) { |
| function.getBody().getStatements().add(0, jsInvocation.makeStmt()); |
| } |
| |
| if (JProgram.isClinit(method)) { |
| function.markAsClinit(); |
| } |
| |
| currentMethod = null; |
| return function; |
| } |
| |
| @Override |
| public JsNode transformMethodBody(JMethodBody methodBody) { |
| |
| JsBlock body = transform(methodBody.getBlock()); |
| |
| JsFunction function = jsFunctionsByJavaMethodBody.get(methodBody); |
| function.setBody(body); |
| |
| /* |
| * Emit a statement to declare the method's complete set of local |
| * variables. JavaScript doesn't have the same concept of lexical scoping |
| * as Java, so it's okay to just predeclare all local vars at the top of |
| * the function, which saves us having to use the "var" keyword over and |
| * over. |
| * |
| * Note: it's fine to use the same JS ident to represent two different |
| * Java locals of the same name since they could never conflict with each |
| * other in Java. We use the alreadySeen set to make sure we don't declare |
| * the same-named local var twice. |
| */ |
| JsVars vars = new JsVars(methodBody.getSourceInfo()); |
| Set<String> alreadySeen = Sets.newHashSet(); |
| for (JLocal local : methodBody.getLocals()) { |
| JsName name = names.get(local); |
| String ident = name.getIdent(); |
| if (!alreadySeen.contains(ident) |
| // Catch block params don't need var declarations |
| && !catchParamIdentifiers.contains(name)) { |
| alreadySeen.add(ident); |
| vars.add(new JsVar(methodBody.getSourceInfo(), name)); |
| } |
| } |
| |
| if (!vars.isEmpty()) { |
| function.getBody().getStatements().add(0, vars); |
| } |
| |
| return function; |
| } |
| |
| @Override |
| public JsNode transformMethodCall(JMethodCall methodCall) { |
| JMethod method = methodCall.getTarget(); |
| if (JProgram.isClinit(method)) { |
| /* |
| * It is possible for clinits to be referenced here that have actually |
| * been retargeted (see {@link |
| * JTypeOracle.recomputeAfterOptimizations}). Most of the time, these |
| * will get cleaned up by other optimization passes prior to this point, |
| * but it's not guaranteed. In this case we need to replace the method |
| * call with the replaced clinit, unless the replacement is null, in |
| * which case we generate a JsNullLiteral as a place-holder expression. |
| */ |
| JDeclaredType type = method.getEnclosingType(); |
| JDeclaredType clinitTarget = type.getClinitTarget(); |
| if (clinitTarget == null) { |
| // generate a null expression, which will get optimized out |
| return JsNullLiteral.INSTANCE; |
| } |
| method = clinitTarget.getClinitMethod(); |
| } |
| |
| JsExpression qualifier = transform(methodCall.getInstance()); |
| List<JsExpression> args = transform(methodCall.getArgs()); |
| SourceInfo sourceInfo = methodCall.getSourceInfo(); |
| if (method.isStatic()) { |
| return dispatchToStatic(qualifier, method, args, sourceInfo); |
| } else if (methodCall.isStaticDispatchOnly()) { |
| return dispatchToSuper(qualifier, method, args, sourceInfo); |
| } else if (method.isOrOverridesJsFunctionMethod()) { |
| return dispatchToJsFunction(qualifier, method, args, sourceInfo); |
| } else { |
| return dispatchToInstanceMethod(qualifier, method, args, sourceInfo); |
| } |
| } |
| |
| private JsExpression dispatchToStatic(JsExpression unnecessaryQualifier, JMethod method, |
| List<JsExpression> args, SourceInfo sourceInfo) { |
| JsNameRef methodName = createStaticReference(method, sourceInfo); |
| return JsUtils.createCommaExpression( |
| unnecessaryQualifier, |
| createInvocationOrPropertyAccess( |
| InvocationStyle.NORMAL, sourceInfo, method, null, methodName, args)); |
| } |
| |
| private JsExpression dispatchToSuper( |
| JsExpression instance, JMethod method, List<JsExpression> args, SourceInfo sourceInfo) { |
| JsNameRef methodNameRef; |
| if (method.isJsNative()) { |
| // Construct Constructor.prototype.jsname or Constructor. |
| methodNameRef = createGlobalQualifier(method.getQualifiedJsName(), sourceInfo); |
| } else if (method.isConstructor()) { |
| /* |
| * Constructor calls through {@code this} and {@code super} are always dispatched statically |
| * using the constructor function name (constructors are always defined as top level |
| * functions). |
| * |
| * Because constructors are modeled like instance methods they have an implicit {@code this} |
| * parameter, hence they are invoked like: "constructor.call(this, ...)". |
| */ |
| methodNameRef = names.get(method).makeRef(sourceInfo); |
| } else { |
| // These are regular super method call. These calls are always dispatched statically and |
| // optimizations will devirtualize them (except in a few cases, like being target of |
| // {@link Impl.getNameOf} or calls to the native classes. |
| |
| JDeclaredType superClass = method.getEnclosingType(); |
| JsExpression protoRef = getPrototypeQualifierViaLookup(superClass, sourceInfo); |
| methodNameRef = polymorphicNames.get(method).makeQualifiedRef(sourceInfo, protoRef); |
| } |
| |
| return JsUtils.createInvocationOrPropertyAccess(InvocationStyle.SUPER, |
| sourceInfo, method, instance, methodNameRef, args); |
| } |
| |
| private JsExpression getPrototypeQualifierViaLookup(JDeclaredType type, SourceInfo sourceInfo) { |
| if (closureCompilerFormatEnabled) { |
| return getPrototypeQualifierOf(type, type.getSourceInfo()); |
| } else { |
| // Construct JCHSU.getPrototypeFor(type).polyname |
| // TODO(rluble): Ideally we would want to construct the inheritance chain the JS way and |
| // then we could do Type.prototype.polyname.call(this, ...). Currently prototypes do not |
| // have global names instead they are stuck into the prototypesByTypeId array. |
| return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_GET_CLASS_PROTOTYPE, |
| (JsExpression) transform(getRuntimeTypeReference(type))); |
| } |
| } |
| |
| private JsExpression dispatchToJsFunction(JsExpression instance, JMethod method, |
| List<JsExpression> args, SourceInfo sourceInfo) { |
| return createInvocationOrPropertyAccess( |
| InvocationStyle.FUNCTION, sourceInfo, method, instance, null, args); |
| } |
| |
| private JsExpression dispatchToInstanceMethod(JsExpression instance, JMethod method, |
| List<JsExpression> args, SourceInfo sourceInfo) { |
| JsNameRef reference = polymorphicNames.get(method).makeQualifiedRef(sourceInfo, instance); |
| return createInvocationOrPropertyAccess( |
| InvocationStyle.NORMAL, sourceInfo, method, instance, reference, args); |
| } |
| |
| @Override |
| public JsNode transformMultiExpression(JMultiExpression multiExpression) { |
| if (multiExpression.isEmpty()) { |
| // the multi-expression was empty; use undefined |
| return JsRootScope.INSTANCE.getUndefined().makeRef(multiExpression.getSourceInfo()); |
| } |
| |
| List<JsExpression> exprs = transform(multiExpression.getExpressions()); |
| JsExpression cur = null; |
| for (int i = 0; i < exprs.size(); ++i) { |
| JsExpression next = exprs.get(i); |
| cur = JsUtils.createCommaExpression(cur, next); |
| } |
| return cur; |
| } |
| |
| @Override |
| public JsNode transformNameOf(JNameOf nameof) { |
| JsName name = names.get(nameof.getNode()); |
| if (name == null) { |
| return JsRootScope.INSTANCE.getUndefined().makeRef(nameof.getSourceInfo()); |
| } |
| return new JsNameOf(nameof.getSourceInfo(), name); |
| } |
| |
| @Override |
| public JsNode transformNewInstance(JNewInstance newInstance) { |
| SourceInfo sourceInfo = newInstance.getSourceInfo(); |
| JConstructor ctor = newInstance.getTarget(); |
| JsName ctorName = names.get(ctor); |
| JsNameRef reference = ctor.isJsNative() |
| ? createGlobalQualifier(ctor.getQualifiedJsName(), sourceInfo) |
| : ctorName.makeRef(sourceInfo); |
| List<JsExpression> arguments = transform(newInstance.getArgs()); |
| |
| if (newInstance.getClassType().isJsFunctionImplementation()) { |
| // Synthesize makeLambdaFunction(samMethodReference, constructorReference, ctorArguments) |
| // which will create the function instance and run the constructor on it. |
| // TODO(rluble): optimize the constructor call away if it is empty. |
| return constructJsFunctionObject( |
| sourceInfo, |
| newInstance.getClassType(), |
| ctorName, |
| reference, |
| new JsArrayLiteral(sourceInfo, arguments)); |
| } |
| |
| return JsUtils.createInvocationOrPropertyAccess( |
| InvocationStyle.NEWINSTANCE, sourceInfo, ctor, null, reference, arguments); |
| } |
| |
| private JsExpression constructJsFunctionObject(SourceInfo sourceInfo, JClassType type, |
| JsName ctorName, JsNameRef ctorReference, JsExpression ctorArguments) { |
| // Foo.prototype.functionMethodName |
| JMethod jsFunctionMethod = getJsFunctionMethod(type); |
| JsNameRef funcNameRef = JsUtils.createQualifiedNameRef(sourceInfo, |
| ctorName, prototype, polymorphicNames.get(jsFunctionMethod)); |
| |
| // makeLambdaFunction(Foo.prototype.functionMethodName, new Foo(...)) |
| return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_MAKE_LAMBDA_FUNCTION, |
| funcNameRef, ctorReference, ctorArguments); |
| } |
| |
| private JMethod getJsFunctionMethod(JClassType type) { |
| for (JMethod method : type.getMethods()) { |
| if (method.isOrOverridesJsFunctionMethod()) { |
| return method; |
| } |
| } |
| throw new AssertionError("Should never reach here."); |
| } |
| |
| @Override |
| public JsNode transformNumericEntry(JNumericEntry entry) { |
| return new JsNumericEntry(entry.getSourceInfo(), entry.getKey(), entry.getValue()); |
| } |
| |
| @Override |
| public JsNode transformParameter(JParameter parameter) { |
| assert !(currentMethod.isJsMethodVarargs() && parameter.isVarargs()); |
| return new JsParameter(parameter.getSourceInfo(), names.get(parameter)); |
| } |
| |
| @Override |
| public JsNode transformParameterRef(JParameterRef parameterRef) { |
| return names.get(parameterRef.getTarget()).makeRef(parameterRef.getSourceInfo()); |
| } |
| |
| @Override |
| public JsNode transformPermutationDependentValue(JPermutationDependentValue dependentValue) { |
| throw new AssertionError("AST should not contain permutation dependent values at " + |
| "this point but contains " + dependentValue); |
| } |
| |
| @Override |
| public JsNode transformPostfixOperation(JPostfixOperation expression) { |
| return new JsPostfixOperation(expression.getSourceInfo(), JavaToJsOperatorMap.get(expression.getOp()), |
| transform(expression.getArg())); |
| } |
| |
| @Override |
| public JsNode transformPrefixOperation(JPrefixOperation expression) { |
| return new JsPrefixOperation(expression.getSourceInfo(), |
| JavaToJsOperatorMap.get(expression.getOp()), transform(expression.getArg())); |
| } |
| |
| /** |
| * Embeds properties into permProps for easy access from JavaScript. |
| */ |
| private void embedBindingProperties() { |
| SourceInfo sourceInfo = SourceOrigin.UNKNOWN; |
| |
| // Generates a list of lists of pairs: [[["key", "value"], ...], ...] |
| // The outermost list is indexed by soft permutation id. Each item represents |
| // a map from binding properties to their values, but is stored as a list of pairs |
| // for easy iteration. |
| JsArrayLiteral permutationProperties = new JsArrayLiteral(sourceInfo); |
| for (Map<String, String> propertyValueByPropertyName : |
| properties.findEmbeddedProperties(TreeLogger.NULL)) { |
| JsArrayLiteral entryList = new JsArrayLiteral(sourceInfo); |
| for (Entry<String, String> entry : propertyValueByPropertyName.entrySet()) { |
| JsArrayLiteral pair = new JsArrayLiteral(sourceInfo, |
| new JsStringLiteral(sourceInfo, entry.getKey()), |
| new JsStringLiteral(sourceInfo, entry.getValue())); |
| entryList.getExpressions().add(pair); |
| } |
| permutationProperties.getExpressions().add(entryList); |
| } |
| |
| getGlobalStatements().add( |
| constructInvocation(sourceInfo, "ModuleUtils.setGwtProperty", |
| new JsStringLiteral(sourceInfo, "permProps"), permutationProperties).makeStmt()); |
| } |
| |
| @Override |
| public JsNode transformReturnStatement(JReturnStatement returnStatement) { |
| return new JsReturn(returnStatement.getSourceInfo(), transform(returnStatement.getExpr())); |
| } |
| |
| @Override |
| public JsNode transformRunAsync(JRunAsync runAsync) { |
| return transform(runAsync.getRunAsyncCall()); |
| } |
| |
| @Override |
| public JsNode transformCastMap(JCastMap castMap) { |
| SourceInfo sourceInfo = castMap.getSourceInfo(); |
| List<JsExpression> jsCastToTypes = transform(castMap.getCanCastToTypes()); |
| return buildJsCastMapLiteral(jsCastToTypes, sourceInfo); |
| } |
| |
| @Override |
| public JsNameRef transformJsniMethodRef(JsniMethodRef jsniMethodRef) { |
| JMethod method = jsniMethodRef.getTarget(); |
| if (method.isJsNative()) { |
| // Construct Constructor.prototype.jsname or Constructor. |
| return createGlobalQualifier(method.getQualifiedJsName(), jsniMethodRef.getSourceInfo()); |
| } |
| return names.get(method).makeRef(jsniMethodRef.getSourceInfo()); |
| } |
| |
| @Override |
| public JsArrayLiteral transformJsonArray(JsonArray jsonArray) { |
| JsArrayLiteral jsArrayLiteral = new JsArrayLiteral(jsonArray.getSourceInfo()); |
| transformInto(jsonArray.getExpressions(), jsArrayLiteral.getExpressions()); |
| return jsArrayLiteral; |
| } |
| |
| @Override |
| public JsNode transformThisRef(JThisRef thisRef) { |
| return new JsThisRef(thisRef.getSourceInfo()); |
| } |
| |
| @Override |
| public JsNode transformThrowStatement(JThrowStatement throwStatement) { |
| return new JsThrow(throwStatement.getSourceInfo(), transform(throwStatement.getExpr())); |
| } |
| |
| @Override |
| public JsNode transformTryStatement(JTryStatement tryStatement) { |
| JsTry jsTry = new JsTry(tryStatement.getSourceInfo()); |
| |
| jsTry.setTryBlock(transform(tryStatement.getTryBlock())); |
| |
| int size = tryStatement.getCatchClauses().size(); |
| assert (size < 2); |
| if (size == 1) { |
| JBlock block = tryStatement.getCatchClauses().get(0).getBlock(); |
| JsCatch jsCatch = catchMap.get(block); |
| jsCatch.setBody(transform(block)); |
| jsTry.getCatches().add(jsCatch); |
| } |
| |
| JsBlock finallyBlock = transform(tryStatement.getFinallyBlock()); |
| if (finallyBlock != null && finallyBlock.getStatements().size() > 0) { |
| jsTry.setFinallyBlock(finallyBlock); |
| } |
| |
| return jsTry; |
| } |
| |
| @Override |
| public JsNode transformWhileStatement(JWhileStatement whileStatement) { |
| SourceInfo info = whileStatement.getSourceInfo(); |
| JsWhile stmt = new JsWhile(info); |
| stmt.setCondition(transform(whileStatement.getTestExpr())); |
| stmt.setBody(jsEmptyIfNull(info, transform(whileStatement.getBody()))); |
| return stmt; |
| } |
| |
| public JsStatement jsEmptyIfNull(SourceInfo info, JsStatement statement) { |
| return statement != null ? statement : new JsEmpty(info); |
| } |
| |
| private void insertInTopologicalOrder(JDeclaredType type, |
| Set<JDeclaredType> topologicallySortedSet) { |
| if (type == null || topologicallySortedSet.contains(type) || program.isReferenceOnly(type)) { |
| return; |
| } |
| insertInTopologicalOrder(type.getSuperClass(), topologicallySortedSet); |
| |
| for (JInterfaceType intf : type.getImplements()) { |
| if (program.typeOracle.isInstantiatedType(type)) { |
| insertInTopologicalOrder(intf, topologicallySortedSet); |
| } |
| } |
| |
| topologicallySortedSet.add(type); |
| } |
| |
| @Override |
| public JsNode transformProgram(JProgram program) { |
| // Handle the visiting here as we need to slightly change the order. |
| // 1.1 (preamble) Immortal code gentypes. |
| // 1.2 (preamble) Classes in the preamble, i.e. all the classes that are needed |
| // to support creation of class literals (reachable through Class.createFor* ). |
| // 1.3 (preamble) Class literals for classes in the preamble. |
| // 2. (body) Normal classes, each with its corresponding class literal (if live). |
| // 3. (epilogue) Code to start the execution of the program (gwtOnLoad, etc). |
| |
| Set<JDeclaredType> preambleTypes = generatePreamble(program); |
| |
| if (incremental) { |
| // Record the names of preamble types so that it's possible to invalidate caches when the |
| // preamble types are known to have become stale. |
| if (!minimalRebuildCache.hasPreambleTypeNames()) { |
| Set<String> preambleTypeNames = Sets.newHashSet(); |
| for (JDeclaredType preambleType : preambleTypes) { |
| preambleTypeNames.add(preambleType.getName()); |
| } |
| minimalRebuildCache.setPreambleTypeNames(logger, preambleTypeNames); |
| } |
| } |
| |
| // Sort normal types according to superclass relationship. |
| Set<JDeclaredType> topologicallySortedBodyTypes = Sets.newLinkedHashSet(); |
| for (JDeclaredType type : program.getModuleDeclaredTypes()) { |
| insertInTopologicalOrder(type, topologicallySortedBodyTypes); |
| } |
| // Remove all preamble types that might have been inserted here. |
| topologicallySortedBodyTypes.removeAll(preambleTypes); |
| |
| // Iterate over each type in the right order. |
| markPosition("Program", Type.PROGRAM_START); |
| for (JDeclaredType type : topologicallySortedBodyTypes) { |
| markPosition(type.getName(), Type.CLASS_START); |
| transform(type); |
| maybeGenerateClassLiteral(type); |
| installClassLiterals(Arrays.asList(type)); |
| markPosition(type.getName(), Type.CLASS_END); |
| } |
| markPosition("Program", Type.PROGRAM_END); |
| |
| generateEpilogue(); |
| |
| // All done, do not visit children. |
| return null; |
| } |
| |
| private Set<JDeclaredType> generatePreamble(JProgram program) { |
| // Reserve the "_" identifier. |
| JsVars vars = new JsVars(jsProgram.getSourceInfo()); |
| vars.add(new JsVar(jsProgram.getSourceInfo(), globalTemp)); |
| addVarsIfNotEmpty(vars); |
| |
| // Generate immortal types in the preamble. |
| generateImmortalTypes(vars); |
| |
| // Perform necessary polyfills. |
| addTypeDefinitionStatement( |
| program.getIndexedType(RuntimeConstants.RUNTIME), |
| constructInvocation(program.getSourceInfo(), RuntimeConstants.RUNTIME_BOOTSTRAP) |
| .makeStmt()); |
| |
| Set<JDeclaredType> alreadyProcessed = |
| Sets.<JDeclaredType>newLinkedHashSet(program.immortalCodeGenTypes); |
| alreadyProcessed.add(program.getTypeClassLiteralHolder()); |
| alreadyRan.addAll(alreadyProcessed); |
| |
| List<JDeclaredType> classLiteralSupportClasses = |
| computeClassLiteralsSupportClasses(program, alreadyProcessed); |
| |
| // Make sure immortal classes are not doubly processed. |
| classLiteralSupportClasses.removeAll(alreadyProcessed); |
| for (JDeclaredType type : classLiteralSupportClasses) { |
| transform(type); |
| } |
| generateClassLiterals(classLiteralSupportClasses); |
| installClassLiterals(classLiteralSupportClasses); |
| |
| Set<JDeclaredType> preambleTypes = Sets.newLinkedHashSet(alreadyProcessed); |
| preambleTypes.addAll(classLiteralSupportClasses); |
| return preambleTypes; |
| } |
| |
| private JsNameRef transformIntoLabelReference(SourceInfo info, JLabel label) { |
| if (label == null) { |
| return null; |
| } |
| |
| return ((JsLabel) transform(label)).getName().makeRef(info); |
| } |
| |
| private void installClassLiterals(List<JDeclaredType> classLiteralTypesToInstall) { |
| if (!closureCompilerFormatEnabled) { |
| // let createForClass() install them until a follow on CL |
| // TODO(cromwellian) remove after approval from rluble in follow up CL |
| return; |
| } |
| |
| for (JDeclaredType type : classLiteralTypesToInstall) { |
| if (shouldNotEmitTypeDefinition(type)) { |
| continue; |
| } |
| |
| JsNameRef classLiteralRef = createClassLiteralReference(type); |
| if (classLiteralRef == null) { |
| continue; |
| } |
| |
| SourceInfo sourceInfo = type.getSourceInfo(); |
| JsExpression protoRef = getPrototypeQualifierOf(type, sourceInfo); |
| JsNameRef clazzField = |
| getIndexedFieldJsName(RuntimeConstants.OBJECT_CLAZZ).makeRef(sourceInfo); |
| clazzField.setQualifier(protoRef); |
| JsExprStmt stmt = createAssignment(clazzField, classLiteralRef).makeStmt(); |
| addTypeDefinitionStatement(type, stmt); |
| } |
| } |
| |
| private boolean shouldNotEmitTypeDefinition(JDeclaredType type) { |
| // Interfaces, Unboxed Types, JSOs, Native Types, JsFunction, and uninstantiated types |
| // Do not have vtables/prototype setup |
| return type instanceof JInterfaceType && !closureCompilerFormatEnabled |
| || program.isRepresentedAsNativeJsPrimitive(type) |
| || !program.typeOracle.isInstantiatedType(type) |
| || type.isJsoType() |
| || type.isJsNative() |
| || type.isJsFunction(); |
| } |
| |
| private List<JDeclaredType> computeClassLiteralsSupportClasses(JProgram program, |
| Set<JDeclaredType> alreadyProcessedTypes) { |
| if (program.isReferenceOnly(program.getIndexedType("Class"))) { |
| return Collections.emptyList(); |
| } |
| // Include in the preamble all classes that are reachable for Class.createForClass, |
| // Class.createForInterface |
| SortedSet<JDeclaredType> reachableClasses = |
| computeReachableTypes(METHODS_PROVIDED_BY_PREAMBLE); |
| |
| assert !incremental || checkCoreModulePreambleComplete(program, |
| program.getTypeClassLiteralHolder().getClinitMethod()); |
| |
| Set<JDeclaredType> orderedPreambleClasses = Sets.newLinkedHashSet(); |
| for (JDeclaredType type : reachableClasses) { |
| if (alreadyProcessedTypes.contains(type)) { |
| continue; |
| } |
| insertInTopologicalOrder(type, orderedPreambleClasses); |
| } |
| |
| // TODO(rluble): The set of preamble types might be overly large, in particular will include |
| // JSOs that need clinit. This is due to {@link ControlFlowAnalyzer} making all JSOs live if |
| // there is a cast to that type anywhere in the program. See the use of |
| // {@link JTypeOracle.getInstantiatedJsoTypesViaCast} in the constructor. |
| return Lists.newArrayList(orderedPreambleClasses); |
| } |
| |
| /** |
| * Check that in modular compiles the preamble is complete. |
| * <p> |
| * In modular compiles the preamble has to include code for creating all 4 types of class |
| * literals. |
| */ |
| private boolean checkCoreModulePreambleComplete(JProgram program, |
| JMethod classLiteralInitMethod) { |
| final Set<JMethod> calledMethods = Sets.newHashSet(); |
| new JVisitor() { |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| calledMethods.add(x.getTarget()); |
| } |
| }.accept(classLiteralInitMethod); |
| |
| for (String createForMethodName : METHODS_PROVIDED_BY_PREAMBLE) { |
| if (!calledMethods.contains(program.getIndexedMethod(createForMethodName))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Computes the set of types whose methods or fields are reachable from {@code methods}. |
| */ |
| private SortedSet<JDeclaredType> computeReachableTypes(Iterable<String> methodNames) { |
| ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(program); |
| for (String methodName : methodNames) { |
| JMethod method = program.getIndexedMethodOrNull(methodName); |
| // Only traverse it if it has not been pruned. |
| if (method != null) { |
| cfa.traverseFrom(method); |
| } |
| } |
| |
| // Get the list of enclosing classes that were not excluded. |
| SortedSet<JDeclaredType> reachableTypes = |
| ImmutableSortedSet.copyOf(HasName.BY_NAME_COMPARATOR, |
| Iterables.filter( |
| Iterables.transform(cfa.getLiveFieldsAndMethods(), |
| new Function<JNode, JDeclaredType>() { |
| @Override |
| public JDeclaredType apply(JNode member) { |
| if (member instanceof JMethod) { |
| return ((JMethod) member).getEnclosingType(); |
| } else if (member instanceof JField) { |
| return ((JField) member).getEnclosingType(); |
| } else { |
| assert member instanceof JParameter || member instanceof JLocal; |
| // Discard locals and parameters, only need the enclosing instances of reachable |
| // fields and methods. |
| return null; |
| } |
| } |
| }), Predicates.notNull())); |
| return reachableTypes; |
| } |
| |
| private JsExpression generateAbstractMethodDefinition(JMethod method) { |
| return (closureCompilerFormatEnabled) ? |
| JsUtils.createQualifiedNameRef(GOOG_ABSTRACT_METHOD, method.getSourceInfo()) : null; |
| } |
| |
| private void generateEpilogue() { |
| generateRemainingClassLiterals(); |
| |
| // add all @JsExport assignments |
| generateExports(); |
| |
| // Generate entry methods. Needs to be after class literal insertion since class literal will |
| // be referenced by runtime rebind and property provider bootstrapping. |
| setupGwtOnLoad(); |
| |
| embedBindingProperties(); |
| } |
| |
| private void generateRemainingClassLiterals() { |
| if (!incremental) { |
| // Emit classliterals that are references but whose classes are not live. |
| generateClassLiterals(Iterables.filter(classLiteralDeclarationsByType.keySet(), |
| Predicates.not(Predicates.<JType>in(alreadyRan)))); |
| return; |
| } |
| |
| // In incremental, class literal references to class literals that were not generated |
| // as part of the current compile have to be from reference only classes. |
| assert FluentIterable.from(classLiteralDeclarationsByType.keySet()) |
| .filter(Predicates.instanceOf(JDeclaredType.class)) |
| .filter(Predicates.not(Predicates.<JType>in(alreadyRan))) |
| .filter( |
| new Predicate<JType>() { |
| @Override |
| public boolean apply(JType type) { |
| return !program.isReferenceOnly((JDeclaredType) type); |
| } |
| }) |
| .isEmpty(); |
| |
| // In incremental only the class literals for the primitive types should be part of the |
| // epilogue. |
| generateClassLiterals(JPrimitiveType.types); |
| } |
| |
| private void generateClassLiterals(Iterable<? extends JType> orderedTypes) { |
| for (JType type : orderedTypes) { |
| maybeGenerateClassLiteral(type); |
| } |
| } |
| |
| private void generateExports() { |
| Map<String, Object> exportedMembersByExportName = new TreeMap<String, Object>(); |
| Set<JDeclaredType> hoistedClinits = Sets.newHashSet(); |
| JsInteropExportsGenerator exportGenerator = |
| closureCompilerFormatEnabled |
| ? new ClosureJsInteropExportsGenerator(getGlobalStatements(), names) |
| : new DefaultJsInteropExportsGenerator(getGlobalStatements(), globalTemp, |
| getIndexedMethodJsName(RuntimeConstants.RUNTIME_PROVIDE)); |
| |
| // Gather exported things in JsNamespace order. |
| for (JDeclaredType type : program.getDeclaredTypes()) { |
| if (type.isJsNative()) { |
| // JsNative types have no implementation and so shouldn't export anything. |
| continue; |
| } |
| |
| if (type.isJsType()) { |
| exportedMembersByExportName.put(type.getQualifiedJsName(), type); |
| } |
| |
| for (JMember member : type.getMembers()) { |
| if (member.isJsInteropEntryPoint()) { |
| if (member.getJsMemberType() == JsMemberType.PROPERTY && !member.isFinal()) { |
| // TODO(goktug): Remove the warning when we export via Object.defineProperty |
| logger.log( |
| TreeLogger.Type.WARN, |
| "Exporting effectively non-final field " |
| + member.getQualifiedName() |
| + ". Due to the way exporting works, the value of the" |
| + " exported field will not be reflected across Java/JavaScript border."); |
| } |
| exportedMembersByExportName.put(member.getQualifiedJsName(), member); |
| } |
| } |
| } |
| |
| // Output the exports. |
| for (Object exportedEntity : exportedMembersByExportName.values()) { |
| if (exportedEntity instanceof JDeclaredType) { |
| exportGenerator.exportType((JDeclaredType) exportedEntity); |
| } else { |
| JMember member = (JMember) exportedEntity; |
| maybeHoistClinit(hoistedClinits, member); |
| exportGenerator.exportMember(member, names.get(member).makeRef(member.getSourceInfo())); |
| } |
| } |
| } |
| |
| private void maybeHoistClinit(Set<JDeclaredType> hoistedClinits, JMember member) { |
| JDeclaredType enclosingType = member.getEnclosingType(); |
| if (hoistedClinits.contains(enclosingType)) { |
| return; |
| } |
| |
| JsInvocation clinitCall = member instanceof JMethod ? maybeCreateClinitCall((JMethod) member) |
| : maybeCreateClinitCall((JField) member); |
| if (clinitCall != null) { |
| hoistedClinits.add(enclosingType); |
| getGlobalStatements().add(clinitCall.makeStmt()); |
| } |
| } |
| |
| @Override |
| public JsFunction transformJsniMethodBody(JsniMethodBody jsniMethodBody) { |
| final Map<String, JNode> nodeByJsniReference = Maps.newHashMap(); |
| for (JsniClassLiteral ref : jsniMethodBody.getClassRefs()) { |
| nodeByJsniReference.put(ref.getIdent(), ref.getField()); |
| } |
| for (JsniFieldRef ref : jsniMethodBody.getJsniFieldRefs()) { |
| nodeByJsniReference.put(ref.getIdent(), ref.getField()); |
| } |
| for (JsniMethodRef ref : jsniMethodBody.getJsniMethodRefs()) { |
| nodeByJsniReference.put(ref.getIdent(), ref.getTarget()); |
| } |
| |
| final JsFunction function = jsniMethodBody.getFunc(); |
| |
| // replace all JSNI idents with a real JsName now that we know it |
| new JsModVisitor() { |
| |
| /** |
| * Marks a ctor that is a direct child of an invocation. Instead of |
| * replacing the ctor with a tear-off, we replace the invocation with a |
| * new operation. |
| */ |
| private JsNameRef dontReplaceCtor; |
| |
| @Override |
| public void endVisit(JsInvocation x, JsContext ctx) { |
| // TODO(rluble): this fixup should be done during the initial JSNI processing in |
| // GwtAstBuilder.JsniReferenceCollector. |
| if (!(x.getQualifier() instanceof JsNameRef)) { |
| // If the invocation does not have a name as a qualifier (it might be an expression). |
| return; |
| } |
| JsNameRef ref = (JsNameRef) x.getQualifier(); |
| if (!ref.isJsniReference()) { |
| // The invocation is not to a JSNI method. |
| return; |
| } |
| // Only constructors reach this point, all other JSNI references in the method body |
| // would have already been replaced at endVisit(JsNameRef). |
| |
| // Replace invocation to ctor with a new op. |
| String ident = ref.getIdent(); |
| JNode node = nodeByJsniReference.get(ident); |
| assert node instanceof JConstructor; |
| assert ref.getQualifier() == null; |
| JsName jsName = names.get(node); |
| assert (jsName != null); |
| ref.resolve(jsName); |
| JsNew jsNew = new JsNew(x.getSourceInfo(), ref); |
| jsNew.getArguments().addAll(x.getArguments()); |
| ctx.replaceMe(jsNew); |
| } |
| |
| @Override |
| public void endVisit(JsNameRef x, JsContext ctx) { |
| if (!x.isJsniReference()) { |
| return; |
| } |
| |
| String ident = x.getIdent(); |
| JNode node = nodeByJsniReference.get(ident); |
| assert (node != null); |
| if (node instanceof JField) { |
| JField field = (JField) node; |
| JsName jsName = names.get(field); |
| assert (jsName != null); |
| x.resolve(jsName); |
| |
| // See if we need to add a clinit call to a static field ref |
| JsInvocation clinitCall = maybeCreateClinitCall(field); |
| if (clinitCall != null) { |
| JsExpression commaExpr = JsUtils.createCommaExpression(clinitCall, x); |
| ctx.replaceMe(commaExpr); |
| } |
| } else if (node instanceof JConstructor) { |
| if (x == dontReplaceCtor) { |
| // Do nothing, parent will handle. |
| } else { |
| // Replace with a local closure function. |
| // function(a,b,c){return new Obj(a,b,c);} |
| JConstructor ctor = (JConstructor) node; |
| JsName jsName = names.get(ctor); |
| assert (jsName != null); |
| x.resolve(jsName); |
| SourceInfo info = x.getSourceInfo(); |
| JsFunction closureFunc = new JsFunction(info, function.getScope()); |
| for (JParameter p : ctor.getParams()) { |
| JsName name = closureFunc.getScope().declareName(p.getName()); |
| closureFunc.getParameters().add(new JsParameter(info, name)); |
| } |
| JsNew jsNew = new JsNew(info, x); |
| for (JsParameter p : closureFunc.getParameters()) { |
| jsNew.getArguments().add(p.getName().makeRef(info)); |
| } |
| JsBlock block = new JsBlock(info); |
| block.getStatements().add(new JsReturn(info, jsNew)); |
| closureFunc.setBody(block); |
| ctx.replaceMe(closureFunc); |
| } |
| } else { |
| JMethod method = (JMethod) node; |
| if (x.getQualifier() == null) { |
| JsName jsName = names.get(method); |
| assert (jsName != null); |
| x.resolve(jsName); |
| } else { |
| JsName jsName = polymorphicNames.get(method); |
| if (jsName == null) { |
| // this can occur when JSNI references an instance method on a |
| // type that was never actually instantiated. |
| jsName = getIndexedMethodJsName(RuntimeConstants.RUNTIME_EMPTY_METHOD); |
| } |
| x.resolve(jsName); |
| } |
| } |
| } |
| |
| @Override |
| public boolean visit(JsInvocation x, JsContext ctx) { |
| if (x.getQualifier() instanceof JsNameRef) { |
| dontReplaceCtor = (JsNameRef) x.getQualifier(); |
| } |
| return true; |
| } |
| }.accept(function); |
| |
| return function; |
| } |
| |
| @Override |
| public JsStatement transformSwitchStatement(JSwitchStatement switchStatement) { |
| /* |
| * What a pain.. JSwitchStatement and JsSwitch are modeled completely |
| * differently. Here we try to resolve those differences. |
| */ |
| JsSwitch jsSwitch = new JsSwitch(switchStatement.getSourceInfo()); |
| jsSwitch.setExpr(transform(switchStatement.getExpr())); |
| |
| List<JStatement> bodyStmts = switchStatement.getBody().getStatements(); |
| List<JsStatement> curStatements = null; |
| for (JStatement stmt : bodyStmts) { |
| if (stmt instanceof JCaseStatement) { |
| // create a new switch member |
| JsSwitchMember switchMember = transform((JNode) stmt); |
| jsSwitch.getCases().add(switchMember); |
| curStatements = switchMember.getStmts(); |
| } else { |
| // add to statements for current case |
| assert (curStatements != null); |
| JsStatement newStmt = transform(stmt); |
| if (newStmt != null) { |
| // Empty JDeclarationStatement produces a null |
| curStatements.add(newStmt); |
| } |
| } |
| } |
| |
| return jsSwitch; |
| } |
| |
| private JsExpression buildJsCastMapLiteral(List<JsExpression> runtimeTypeIdLiterals, |
| SourceInfo sourceInfo) { |
| if (JjsUtils.closureStyleLiteralsNeeded(incremental, closureCompilerFormatEnabled)) { |
| return buildClosureStyleCastMapFromArrayLiteral(runtimeTypeIdLiterals, sourceInfo); |
| } |
| |
| JsNumberLiteral one = new JsNumberLiteral(sourceInfo, 1); |
| JsObjectLiteral.Builder objectLiteralBuilder = JsObjectLiteral.builder(sourceInfo) |
| .setInternable(); |
| for (JsExpression runtimeTypeIdLiteral : runtimeTypeIdLiterals) { |
| objectLiteralBuilder.add(runtimeTypeIdLiteral, one); |
| } |
| return objectLiteralBuilder.build(); |
| } |
| |
| private JsExpression buildClosureStyleCastMapFromArrayLiteral( |
| List<JsExpression> runtimeTypeIdLiterals, SourceInfo sourceInfo) { |
| /* |
| * goog.object.createSet('foo', 'bar', 'baz') is optimized by closure compiler into |
| * {'foo': !0, 'bar': !0, baz: !0} |
| */ |
| JsNameRef createSet = new JsNameRef(sourceInfo, GOOG_OBJECT_CREATE_SET); |
| JsInvocation jsInvocation = new JsInvocation(sourceInfo, createSet); |
| |
| for (JsExpression expr : runtimeTypeIdLiterals) { |
| jsInvocation.getArguments().add(expr); |
| } |
| |
| return jsInvocation; |
| } |
| |
| private void checkForDuplicateMethods(JDeclaredType type) { |
| // Sanity check to see that all methods are uniquely named. |
| List<JMethod> methods = type.getMethods(); |
| Set<String> methodSignatures = Sets.newHashSet(); |
| for (JMethod method : methods) { |
| String sig = method.getSignature(); |
| if (methodSignatures.contains(sig)) { |
| throw new InternalCompilerException("Signature collision in Type " + type.getName() |
| + " for method " + sig); |
| } |
| methodSignatures.add(sig); |
| } |
| } |
| |
| private JsNameRef createStaticReference(JMember member, SourceInfo sourceInfo) { |
| assert member.isStatic(); |
| return member.isJsNative() |
| ? createGlobalQualifier(member.getQualifiedJsName(), sourceInfo) |
| : names.get(member).makeRef(sourceInfo); |
| } |
| |
| private void emitFields(JDeclaredType type) { |
| JsVars vars = new JsVars(type.getSourceInfo()); |
| for (JField field : type.getFields()) { |
| if (field.isJsNative()) { |
| // Nothing to output for native fields. |
| continue; |
| } |
| JsExpression initializer = null; |
| // if we need an initial value, create an assignment |
| if (initializeAtTopScope(field)) { |
| // setup the constant value |
| initializer = transform(field.getLiteralInitializer()); |
| } else if (field.getType().getDefaultValue() == JNullLiteral.INSTANCE) { |
| // Fields whose default value is null are left uninitialized and will |
| // have a JS value of undefined. |
| } else { |
| // setup the default value, see Issue 380 |
| initializer = transform(field.getType().getDefaultValue()); |
| } |
| |
| JsName name = names.get(field); |
| |
| if (field.isStatic()) { |
| // setup a var for the static |
| JsVar var = new JsVar(type.getSourceInfo(), name); |
| var.setInitExpr(initializer); |
| vars.add(var); |
| } else if (initializer != null) { |
| // Instance field initilized at top. |
| JsNameRef fieldRef = |
| name.makeQualifiedRef(field.getSourceInfo(), getPrototypeQualifierOf(field)); |
| addTypeDefinitionStatement(type, createAssignment(fieldRef, initializer).makeStmt()); |
| } |
| } |
| addVarsIfNotEmpty(vars); |
| } |
| |
| private void emitStaticMethods(JDeclaredType type) { |
| // declare all methods into the global scope |
| for (JMethod method : type.getMethods()) { |
| if (method.needsDynamicDispatch()) { |
| continue; |
| } |
| |
| JsFunction function = transform(method); |
| if (function == null) { |
| continue; |
| } |
| |
| if (JProgram.isClinit(method)) { |
| handleClinit(type, function); |
| } |
| |
| emitMethodImplementation(method, |
| function.getName().makeRef(function.getSourceInfo()), function.makeStmt()); |
| } |
| } |
| |
| private JsExpression generateCastableTypeMap(JDeclaredType type) { |
| JCastMap castMap = program.getCastMap(type); |
| JsName castableTypeMapName = getIndexedFieldJsName(RuntimeConstants.OBJECT_CASTABLE_TYPE_MAP); |
| |
| if (castMap != null && castableTypeMapName != null) { |
| return transform(castMap); |
| } |
| return JsObjectLiteral.EMPTY; |
| } |
| |
| private JField getClassLiteralField(JType type) { |
| JDeclarationStatement decl = classLiteralDeclarationsByType.get(type); |
| if (decl == null) { |
| return null; |
| } |
| |
| return (JField) decl.getVariableRef().getTarget(); |
| } |
| |
| private void maybeGenerateClassLiteral(JType type) { |
| |
| JField field = getClassLiteralField(type); |
| if (field == null) { |
| return; |
| } |
| |
| // TODO(rluble): refactor so that all output related to a class is decided together. |
| if (type != null && type instanceof JDeclaredType |
| && program.isReferenceOnly((JDeclaredType) type)) { |
| // Only generate class literals for classes in the current module. |
| // TODO(rluble): In separate compilation some class literals will be duplicated, which if |
| // not done with care might violate java semantics of getClass(). There are class literals |
| // for primitives and arrays. Currently, because they will be assigned to the same field |
| // the one defined later will be the one used and Java semantics are preserved. |
| return; |
| } |
| |
| JsVars vars = new JsVars(jsProgram.getSourceInfo()); |
| JsName jsName = names.get(field); |
| JsExpression classLiteralObject = transform(field.getInitializer()); |
| JsVar var = new JsVar(field.getSourceInfo(), jsName); |
| var.setInitExpr(classLiteralObject); |
| vars.add(var); |
| addVarsIfNotEmpty(vars); |
| } |
| |
| private JsNameRef createClassLiteralReference(JType type) { |
| JField field = getClassLiteralField(type); |
| if (field == null) { |
| return null; |
| } |
| JsName jsName = names.get(field); |
| return jsName.makeRef(type.getSourceInfo()); |
| } |
| |
| private void generateTypeSetup(JDeclaredType type) { |
| if (program.isRepresentedAsNativeJsPrimitive(type) |
| && program.typeOracle.isInstantiatedType(type)) { |
| setupCastMapForUnboxedType(type, |
| program.getRepresentedAsNativeTypesDispatchMap().get(type).getCastMapField()); |
| return; |
| } |
| |
| if (shouldNotEmitTypeDefinition(type)) { |
| return; |
| } |
| |
| generateClassDefinition(type); |
| generatePrototypeDefinitions(type); |
| |
| maybeGenerateObjectMethodsAliases(type); |
| } |
| |
| private void markPosition(String name, Type type) { |
| getGlobalStatements().add(new JsPositionMarker(SourceOrigin.UNKNOWN, name, type)); |
| } |
| |
| /** |
| * Sets up gwtOnLoad bootstrapping code. Unusually, the created code is executed as part of |
| * source loading and runs in the global scope (not inside of any function scope). |
| */ |
| private void setupGwtOnLoad() { |
| /** |
| * <pre> |
| * var $entry = Impl.registerEntry(); |
| * var gwtOnLoad = ModuleUtils.gwtOnLoad(); |
| * ModuleUtils.addInitFunctions(init1, init2,...) |
| * </pre> |
| */ |
| |
| final SourceInfo sourceInfo = SourceOrigin.UNKNOWN; |
| |
| // var $entry = ModuleUtils.registerEntry(); |
| JsStatement entryVars = constructFunctionCallStatement( |
| topScope.declareName("$entry"), "ModuleUtils.registerEntry"); |
| getGlobalStatements().add(entryVars); |
| |
| // var gwtOnLoad = ModuleUtils.gwtOnLoad; |
| JsName gwtOnLoad = topScope.findExistingUnobfuscatableName("gwtOnLoad"); |
| JsVar varGwtOnLoad = new JsVar(sourceInfo, gwtOnLoad); |
| varGwtOnLoad.setInitExpr(createAssignment(gwtOnLoad.makeRef(sourceInfo), |
| getIndexedMethodJsName(RuntimeConstants.MODULE_UTILS_GWT_ON_LOAD).makeRef(sourceInfo))); |
| getGlobalStatements().add(new JsVars(sourceInfo, varGwtOnLoad)); |
| |
| // ModuleUtils.addInitFunctions(init1, init2,...) |
| List<JsExpression> arguments = Lists.newArrayList(); |
| for (JMethod entryPointMethod : program.getEntryMethods()) { |
| JsFunction entryFunction = getJsFunctionFor(entryPointMethod); |
| arguments.add(entryFunction.getName().makeRef(sourceInfo)); |
| } |
| |
| JsStatement createGwtOnLoadFunctionCall = |
| constructInvocation("ModuleUtils.addInitFunctions", arguments).makeStmt(); |
| |
| getGlobalStatements().add(createGwtOnLoadFunctionCall); |
| } |
| |
| /** |
| * Creates a (var) assignment a statement for a function call to an indexed function. |
| */ |
| private JsStatement constructFunctionCallStatement(JsName assignToVariableName, |
| String indexedFunctionName, JsExpression... args) { |
| return constructFunctionCallStatement(assignToVariableName, indexedFunctionName, |
| Arrays.asList(args)); |
| } |
| |
| /** |
| * Creates a (var) assignment a statement for a function call to an indexed function. |
| */ |
| private JsStatement constructFunctionCallStatement(JsName assignToVariableName, |
| String indexedFunctionName, List<JsExpression> args) { |
| |
| SourceInfo sourceInfo = SourceOrigin.UNKNOWN; |
| JsInvocation invocation = constructInvocation(indexedFunctionName, args); |
| JsVar var = new JsVar(sourceInfo, assignToVariableName); |
| var.setInitExpr(invocation); |
| JsVars entryVars = new JsVars(sourceInfo); |
| entryVars.add(var); |
| return entryVars; |
| } |
| |
| /** |
| * Constructs an invocation for an indexed function. |
| */ |
| private JsInvocation constructInvocation(SourceInfo sourceInfo, |
| String indexedFunctionName, JsExpression... args) { |
| return constructInvocation(sourceInfo, indexedFunctionName, Arrays.asList(args)); |
| } |
| |
| /** |
| * Constructs an invocation for an indexed function. |
| */ |
| private JsInvocation constructInvocation(String indexedFunctionName, |
| List<JsExpression> args) { |
| SourceInfo sourceInfo = SourceOrigin.UNKNOWN; |
| return constructInvocation(sourceInfo, indexedFunctionName, args); |
| } |
| |
| /** |
| * Constructs an invocation for an indexed function. |
| */ |
| private JsInvocation constructInvocation(SourceInfo sourceInfo, |
| String indexedFunctionName, List<JsExpression> args) { |
| JsName functionToInvoke = getIndexedMethodJsName(indexedFunctionName); |
| return new JsInvocation(sourceInfo, functionToInvoke.makeRef(sourceInfo), args); |
| } |
| |
| private void generateImmortalTypes(JsVars globals) { |
| List<JClassType> immortalTypesReversed = Lists.reverse(program.immortalCodeGenTypes); |
| // visit in reverse order since insertions start at head |
| for (JClassType x : immortalTypesReversed) { |
| // Don't generate JS for referenceOnly types. |
| if (program.isReferenceOnly(x)) { |
| continue; |
| } |
| // should not be pruned |
| assert x.getMethods().size() > 0; |
| // insert all static methods |
| for (JMethod method : x.getMethods()) { |
| /* |
| * Skip virtual methods and constructors. Even in cases where there is no constructor |
| * defined, the compiler will synthesize a default constructor which invokes |
| * a synthesized $init() method. We must skip both of these inserted methods. |
| */ |
| if (method.needsDynamicDispatch() || method instanceof JConstructor |
| || doesNotHaveConcreteImplementation(method)) { |
| continue; |
| } |
| // add after var declaration, but before everything else |
| JsFunction function = transform(method); |
| assert function.getName() != null; |
| addMethodDefinitionStatement(1, method, function.makeStmt()); |
| } |
| |
| // insert fields into global var declaration |
| for (JField field : x.getFields()) { |
| assert field.isStatic() : "'" + field.getName() |
| + "' is not static. Only static fields are allowed on immortal types"; |
| assert field.getInitializer() == field.getLiteralInitializer() : "'" + field.getName() |
| + "' is not initilialized to a literal." |
| + " Only literal initializers are allowed on immortal types"; |
| |
| JsVar var = new JsVar(x.getSourceInfo(), names.get(field)); |
| var.setInitExpr(transform(field.getLiteralInitializer())); |
| globals.add(var); |
| } |
| } |
| } |
| |
| private void generateCallToDefineClass(JClassType type, List<JsNameRef> constructorArgs) { |
| JClassType superClass = type.getSuperClass(); |
| JExpression superTypeId = (superClass == null) ? JNullLiteral.INSTANCE : |
| getRuntimeTypeReference(superClass); |
| String jsPrototype = getSuperPrototype(type); |
| |
| List<JsExpression> defineClassArguments = Lists.newArrayList(); |
| |
| defineClassArguments.add(transform(getRuntimeTypeReference(type))); |
| defineClassArguments.add(jsPrototype == null ? transform(superTypeId) : |
| createGlobalQualifier(jsPrototype, type.getSourceInfo())); |
| defineClassArguments.add(generateCastableTypeMap(type)); |
| defineClassArguments.addAll(constructorArgs); |
| |
| // Runtime.defineClass(typeId, superTypeId, castableMap, constructors) |
| JsStatement defineClassStatement = constructInvocation(type.getSourceInfo(), |
| RuntimeConstants.RUNTIME_DEFINE_CLASS, defineClassArguments).makeStmt(); |
| addTypeDefinitionStatement(type, defineClassStatement); |
| |
| maybeCopyJavaLangObjectProperties( |
| type, |
| getPrototypeQualifierViaLookup(program.getTypeJavaLangObject(), type.getSourceInfo()), |
| globalTemp.makeRef(type.getSourceInfo())); |
| } |
| |
| private void maybeCopyJavaLangObjectProperties( |
| JDeclaredType type, JsExpression javaLangObjectPrototype, JsExpression toPrototype) { |
| if (getSuperPrototype(type) != null && !type.isJsFunctionImplementation()) { |
| JsStatement statement = |
| constructInvocation( |
| type.getSourceInfo(), |
| RuntimeConstants.RUNTIME_COPY_OBJECT_PROPERTIES, |
| javaLangObjectPrototype, |
| toPrototype |
| ).makeStmt(); |
| addTypeDefinitionStatement(type, statement); |
| } |
| } |
| |
| private String getSuperPrototype(JDeclaredType type) { |
| if (type.isJsFunctionImplementation()) { |
| return "Function"; |
| } |
| JClassType superClass = type.getSuperClass(); |
| if (superClass != null && superClass.isJsNative()) { |
| return superClass.getQualifiedJsName(); |
| } |
| return null; |
| } |
| |
| private void generateClassDefinition(JDeclaredType type) { |
| assert !program.isRepresentedAsNativeJsPrimitive(type); |
| |
| if (closureCompilerFormatEnabled) { |
| generateClosureTypeDefinition(type); |
| } else { |
| generateJsClassDefinition((JClassType) type); |
| } |
| } |
| |
| /* |
| * Class definition for regular output looks like: |
| * |
| * defineClass(id, superId, castableTypeMap, ctor1, ctor2, ctor3); |
| * _.method1 = function() { ... } |
| * _.method2 = function() { ... } |
| */ |
| private void generateJsClassDefinition(JClassType classType) { |
| // Add constructors as varargs to define class. |
| List<JsNameRef> constructorArgs = Lists.newArrayList(); |
| for (JMethod method : getPotentiallyAliveConstructors(classType)) { |
| constructorArgs.add(names.get(method).makeRef(classType.getSourceInfo())); |
| } |
| |
| // defineClass(..., Ctor1, Ctor2, ...) |
| generateCallToDefineClass(classType, constructorArgs); |
| } |
| |
| /* |
| * Class definition for closure output looks like: |
| * |
| * function ClassName() {} |
| * ClassName.prototype.method1 = function() { ... }; |
| * ClassName.prototype.method2 = function() { ... }; |
| * ClassName.prototype.castableTypeMap = {...} |
| * ClassName.prototype.___clazz = classLit; |
| * function Ctor1() {} |
| * function Ctor2() {} |
| * |
| * goog$inherits(Ctor1, ClassName); |
| * goog$inherits(Ctor2, ClassName); |
| * |
| * The primary change is to make the prototype assignment look like regular closure code to help |
| * the compiler disambiguate which methods belong to which type. Elimination of defineClass() |
| * makes the setup more transparent and eliminates a global table holding a reference to |
| * every prototype. |
| */ |
| private void generateClosureTypeDefinition(JDeclaredType type) { |
| // function ClassName(){} |
| JsName classVar = declareSynthesizedClosureConstructor(type); |
| generateInlinedDefineClass(type, classVar); |
| |
| /* |
| * Closure style prefers 1 single ctor per type. To model this without radical changes, |
| * we simply model each concrete ctor as a subtype. This works because GWT doesn't use the |
| * native instanceof operator. So for example, class A() { A(int type){}, A(String s){} } |
| * becomes (pseudo code): |
| * |
| * function A() {} |
| * A.prototype.method = ... |
| * |
| * function A_int(x) {} |
| * function A_String(s) {} |
| * goog$inherits(A_int, A); |
| * goog$inherits(A_string, A); |
| * |
| */ |
| for (JMethod method : getPotentiallyAliveConstructors(type)) { |
| SourceInfo typeSourceInfo = type.getSourceInfo(); |
| JsNameRef googInherits = JsUtils.createQualifiedNameRef(GOOG_INHERITS, typeSourceInfo); |
| |
| SourceInfo methodSourceInfo = method.getSourceInfo(); |
| JsExprStmt callGoogInherits = new JsInvocation(typeSourceInfo, googInherits, |
| names.get(method).makeRef(methodSourceInfo), |
| names.get(method.getEnclosingType()).makeRef(methodSourceInfo)).makeStmt(); |
| addMethodDefinitionStatement(method, callGoogInherits); |
| } |
| } |
| |
| /** |
| * Does everything JCHSU.defineClass does, but inlined into global statements. Roughly |
| * parallels argument order of generateCallToDefineClass. |
| */ |
| private void generateInlinedDefineClass(JDeclaredType type, JsName classVar) { |
| if (type instanceof JInterfaceType) { |
| return; |
| } |
| JClassType superClass = type.getSuperClass(); |
| // check if there's an overriding prototype |
| String jsPrototype = getSuperPrototype(type); |
| SourceInfo info = type.getSourceInfo(); |
| JsNameRef parentCtor = jsPrototype != null ? |
| createGlobalQualifier(jsPrototype, info) : |
| superClass != null ? |
| names.get(superClass).makeRef(info) : |
| null; |
| |
| if (parentCtor != null) { |
| JsNameRef googInherits = JsUtils.createQualifiedNameRef(GOOG_INHERITS, info); |
| // Use goog$inherits(ChildCtor, ParentCtor) to setup inheritance |
| JsExprStmt callGoogInherits = new JsInvocation(info, googInherits, |
| classVar.makeRef(info), parentCtor).makeStmt(); |
| addTypeDefinitionStatement(type, callGoogInherits); |
| } |
| |
| if (type == program.getTypeJavaLangObject()) { |
| setupTypeMarkerOnJavaLangObjectPrototype(type); |
| } |
| |
| // inline assignment of castableTypeMap field instead of using defineClass() |
| setupCastMapOnPrototype(type); |
| maybeCopyJavaLangObjectProperties( |
| type, |
| getPrototypeQualifierOf(program.getTypeJavaLangObject(), info), |
| getPrototypeQualifierOf(type, info)); |
| } |
| |
| private void setupCastMapOnPrototype(JDeclaredType type) { |
| JsExpression castMap = generateCastableTypeMap(type); |
| generatePrototypeAssignmentForJavaField(type, "Object.castableTypeMap", castMap); |
| } |
| |
| private void setupTypeMarkerOnJavaLangObjectPrototype(JDeclaredType type) { |
| JsName typeMarkerMethod = getIndexedMethodJsName(RuntimeConstants.RUNTIME_TYPE_MARKER_FN); |
| generatePrototypeAssignmentForJavaField(type, RuntimeConstants.OBJECT_TYPEMARKER, |
| typeMarkerMethod.makeRef(type.getSourceInfo())); |
| } |
| |
| private void generatePrototypeAssignmentForJavaField(JDeclaredType type, String javaField, |
| JsExpression rhs) { |
| SourceInfo sourceInfo = type.getSourceInfo(); |
| JsNameRef protoRef = getPrototypeQualifierOf(type, sourceInfo); |
| JsNameRef fieldRef = getIndexedFieldJsName(javaField).makeQualifiedRef(sourceInfo, protoRef); |
| addTypeDefinitionStatement(type, createAssignment(fieldRef, rhs).makeStmt()); |
| } |
| |
| private void addMethodDefinitionStatement(JMethod method, |
| JsExprStmt methodDefinitionStatement) { |
| getGlobalStatements().add(methodDefinitionStatement); |
| methodByGlobalStatement.put(methodDefinitionStatement, method); |
| } |
| |
| private void addMethodDefinitionStatement(int position, JMethod method, |
| JsExprStmt methodDefinitionStatement) { |
| getGlobalStatements().add(position, methodDefinitionStatement); |
| methodByGlobalStatement.put(methodDefinitionStatement, method); |
| } |
| |
| private void addTypeDefinitionStatement(JDeclaredType x, JsStatement statement) { |
| getGlobalStatements().add(statement); |
| javaTypeByGlobalStatement.put(statement, x); |
| } |
| |
| /* |
| * Declare an empty synthesized constructor that looks like: |
| * function ClassName(){} |
| * |
| * Closure Compiler's RewriteFunctionExpressions pass can be enabled to turn these back |
| * into a factory method after optimizations. |
| * |
| * TODO(goktug): throw Error in the body to prevent instantiation via this constructor. |
| */ |
| private JsName declareSynthesizedClosureConstructor(JDeclaredType x) { |
| SourceInfo sourceInfo = x.getSourceInfo(); |
| JsName classVar = topScope.declareName(JjsUtils.mangledNameString(x)); |
| JsFunction closureCtor = JsUtils.createEmptyFunctionLiteral(sourceInfo, topScope, classVar); |
| JsExprStmt statement = closureCtor.makeStmt(); |
| // This synthetic statement must be in the initial fragment, do not add to typeDefinitions |
| getGlobalStatements().add(statement); |
| names.put(x, classVar); |
| return classVar; |
| } |
| |
| /* |
| * Sets up the castmap for type X |
| */ |
| private void setupCastMapForUnboxedType(JDeclaredType type, String castMapField) { |
| // Cast.[castMapName] = /* cast map */ { ..:1, ..:1} |
| JsName castableTypeMapName = getIndexedFieldJsName(castMapField); |
| JsNameRef castMapVarRef = castableTypeMapName.makeRef(type.getSourceInfo()); |
| |
| JsExpression castMapLiteral = generateCastableTypeMap(type); |
| addTypeDefinitionStatement(type, createAssignment(castMapVarRef, castMapLiteral).makeStmt()); |
| } |
| |
| private void maybeGenerateObjectMethodsAliases(JDeclaredType type) { |
| if (type == program.getTypeJavaLangObject()) { |
| // special: setup a "toString" alias for java.lang.Object.toString() |
| Set<JMethod> overridableJavaLangObjectMethods = ImmutableSet.of( |
| program.getIndexedMethodOrNull(RuntimeConstants.OBJECT_EQUALS), |
| program.getIndexedMethodOrNull(RuntimeConstants.OBJECT_HASHCODE), |
| program.getIndexedMethodOrNull(RuntimeConstants.OBJECT_TO_STRING)); |
| |
| for (JMethod method : type.getMethods()) { |
| if (overridableJavaLangObjectMethods.contains(method)) { |
| JsName methodJsName = objectScope.declareUnobfuscatableName(method.getName()); |
| generatePrototypeDefinitionAlias(method, methodJsName); |
| } |
| } |
| } |
| } |
| |
| private void generatePrototypeAssignment(JMethod method, JsName name, JsExpression rhs) { |
| generatePrototypeAssignment(method, name, rhs, method.getJsMemberType()); |
| } |
| |
| /** |
| * Create a vtable assignment of the form _.polyname = rhs; and register the line as |
| * created for {@code method}. |
| */ |
| private void generatePrototypeAssignment(JMethod method, JsName name, JsExpression rhs, |
| JsMemberType memberType) { |
| SourceInfo sourceInfo = method.getSourceInfo(); |
| JsNameRef prototypeQualifierOf = getPrototypeQualifierOf(method); |
| JsNameRef lhs = name.makeQualifiedRef(sourceInfo, prototypeQualifierOf); |
| switch (memberType) { |
| case GETTER: |
| case SETTER: |
| emitPropertyImplementation(method, prototypeQualifierOf, name.makeRef(sourceInfo), rhs); |
| break; |
| default: |
| emitMethodImplementation(method, lhs, createAssignment(lhs, rhs).makeStmt()); |
| break; |
| } |
| } |
| |
| private void emitPropertyImplementation(JMethod method, JsNameRef prototype, JsNameRef name, |
| JsExpression methodDefinitionStatement) { |
| SourceInfo sourceInfo = method.getSourceInfo(); |
| |
| // We use Object.defineProperties instead of Object.defineProperty to make sure the |
| // property name appears as an identifier and not as a string. |
| // Some JS optimizers, e.g. the closure compiler, relies on this subtle difference for |
| // obfuscating property names. |
| JsNameRef definePropertyMethod = |
| getIndexedMethodJsName(RuntimeConstants.RUNTIME_DEFINE_PROPERTIES).makeRef(sourceInfo); |
| |
| JsObjectLiteral definePropertyLiteral = |
| JsObjectLiteral.builder(sourceInfo) |
| // {name: {get: function() { ..... }} or {set : function (v) {....}}} |
| .add(name, JsObjectLiteral.builder(sourceInfo) |
| // {get: function() { ..... }} or {set : function (v) {....}} |
| .add(method.getJsMemberType().getPropertyAccessorKey(), methodDefinitionStatement) |
| .build()) |
| .build(); |
| |
| addMethodDefinitionStatement(method, new JsInvocation(sourceInfo, definePropertyMethod, |
| prototype, definePropertyLiteral).makeStmt()); |
| } |
| |
| private void emitMethodImplementation(JMethod method, JsNameRef functionNameRef, |
| JsExprStmt methodDefinitionStatement) { |
| addMethodDefinitionStatement(method, methodDefinitionStatement); |
| |
| if (shouldEmitDisplayNames()) { |
| JsExprStmt displayNameAssignment = outputDisplayName(functionNameRef, method); |
| addMethodDefinitionStatement(method, displayNameAssignment); |
| } |
| } |
| |
| private void generatePrototypeDefinitionAlias(JMethod method, JsName alias) { |
| JsName polyName = polymorphicNames.get(method); |
| JsExpression bridge = JsUtils.createBridge(method, polyName, topScope); |
| // Aliases are never property accessors. |
| generatePrototypeAssignment(method, alias, bridge, JsMemberType.NONE); |
| } |
| |
| private JsExprStmt outputDisplayName(JsNameRef function, JMethod method) { |
| JsNameRef displayName = new JsNameRef(function.getSourceInfo(), "displayName"); |
| displayName.setQualifier(function); |
| String displayStringName = getDisplayName(method); |
| JsStringLiteral displayMethodName = |
| new JsStringLiteral(function.getSourceInfo(), displayStringName); |
| return createAssignment(displayName, displayMethodName).makeStmt(); |
| } |
| |
| private boolean shouldEmitDisplayNames() { |
| return methodNameMappingMode != OptionMethodNameDisplayMode.Mode.NONE; |
| } |
| |
| private String getDisplayName(JMethod method) { |
| switch (methodNameMappingMode) { |
| case ONLY_METHOD_NAME: |
| return method.getName(); |
| case ABBREVIATED: |
| return method.getEnclosingType().getShortName() + "." + method.getName(); |
| case FULL: |
| return method.getEnclosingType().getName() + "." + method.getName(); |
| default: |
| assert false : "Invalid display mode option " + methodNameMappingMode; |
| } |
| return null; |
| } |
| |
| /** |
| * Creates the assignment for all polynames for a certain class, assumes that the global |
| * variable _ points the JavaScript prototype for {@code type}. |
| */ |
| private void generatePrototypeDefinitions(JDeclaredType type) { |
| assert !program.isRepresentedAsNativeJsPrimitive(type); |
| |
| // Emit synthetic methods first. In JsInterop we allow a more user written method to be named |
| // with the same name as a synthetic bridge (required due to generics) relying that the |
| // synthetic method is output first into the prototype slot and rewritten in this situation. |
| // TODO(rluble): this is a band aid. The user written method (and its overrides) should be |
| // automatically JsIgnored. Otherwise some semantics become looser. E.g. the synthetic bridge |
| // method may be casting some of the parameters. Such casts are lost in this scheme. |
| Iterable<JMethod> orderedInstanceMethods = Iterables.concat( |
| Iterables.filter(type.getMethods(), |
| Predicates.and( |
| JjsPredicates.IS_SYNTHETIC, |
| JjsPredicates.NEEDS_DYNAMIC_DISPATCH)), |
| Iterables.filter(type.getMethods(), |
| Predicates.and( |
| Predicates.not(JjsPredicates.IS_SYNTHETIC), |
| JjsPredicates.NEEDS_DYNAMIC_DISPATCH))); |
| |
| for (JMethod method : orderedInstanceMethods) { |
| generatePrototypeDefinition(method, (JsExpression) transformMethod(method)); |
| } |
| } |
| |
| private void generatePrototypeDefinition(JMethod method, JsExpression functionDefinition) { |
| if (functionDefinition != null) { |
| generatePrototypeAssignment(method, polymorphicNames.get(method), functionDefinition); |
| } |
| |
| if (method.exposesNonJsMember()) { |
| JsName internalMangledName = interfaceScope.declareName(mangleNameForPoly(method), |
| method.getName()); |
| generatePrototypeDefinitionAlias(method, internalMangledName); |
| } |
| |
| if (method.exposesPackagePrivateMethod()) { |
| // Here is the situation where this is needed: |
| // |
| // class a.A { m() {} } |
| // class b.B extends a.A { m() {} } |
| // interface I { m(); } |
| // class a.C { |
| // { A a = new b.B(); a.m() // calls A::m()} } |
| // { I i = new b.B(); a.m() // calls B::m()} } |
| // } |
| // |
| // Up to this point it is clear that package private names need to be different than |
| // public names. |
| // |
| // Add class a.D extends a.A implements I { public m() } |
| // |
| // a.D collapses A::m and I::m into the same function and it was clear that two |
| // two different names were already needed, hence when creating the vtable for a.D |
| // both names have to point to the same function. |
| generatePrototypeDefinitionAlias(method, getPackagePrivateName(method)); |
| } |
| } |
| |
| /** |
| * Returns either _ or ClassCtor.prototype depending on output mode. |
| */ |
| private JsNameRef getPrototypeQualifierOf(JMember member) { |
| return getPrototypeQualifierOf(member.getEnclosingType(), member.getSourceInfo()); |
| } |
| |
| /** |
| * Returns either _ or ClassCtor.prototype depending on output mode. |
| */ |
| private JsNameRef getPrototypeQualifierOf(JDeclaredType type, SourceInfo info) { |
| return closureCompilerFormatEnabled |
| ? prototype.makeQualifiedRef(info, names.get(type).makeRef(info)) |
| : globalTemp.makeRef(info); |
| } |
| |
| /** |
| * Returns the package private JsName for {@code method}. |
| */ |
| private JsName getPackagePrivateName(JMethod method) { |
| for (JMethod overridenMethod : method.getOverriddenMethods()) { |
| if (overridenMethod.isPackagePrivate()) { |
| JsName name = polymorphicNames.get(overridenMethod); |
| assert name != null; |
| return name; |
| } |
| } |
| throw new AssertionError( |
| method.toString() + " overrides a package private method but was not found."); |
| } |
| |
| private void handleClinit(JDeclaredType type, JsFunction clinitFunction) { |
| clinitFunctionForType.put(type, clinitFunction); |
| JDeclaredType superClass = type.getSuperClass(); |
| JsFunction superClinitFunction = superClass == null |
| ? null : clinitFunctionForType.get(superClass.getClinitTarget()); |
| |
| clinitFunction.setSuperClinit(superClinitFunction); |
| List<JsStatement> statements = clinitFunction.getBody().getStatements(); |
| SourceInfo sourceInfo = clinitFunction.getSourceInfo(); |
| // Self-assign to the global noop method immediately (to prevent reentrancy). In incremental |
| // mode the more costly Object constructor function is used as the noop method since doing so |
| // provides a better debug experience that does not step into already used clinits. |
| |
| JsName emptyFunctionFnName = incremental ? objectConstructorFunction.getName() |
| : getIndexedMethodJsName(RuntimeConstants.RUNTIME_EMPTY_METHOD); |
| JsExpression assignment = createAssignment(clinitFunction.getName().makeRef(sourceInfo), |
| emptyFunctionFnName.makeRef(sourceInfo)); |
| statements.add(0, assignment.makeStmt()); |
| } |
| |
| private boolean isMethodPotentiallyCalledAcrossClasses(JMethod method) { |
| assert incremental || crossClassTargets != null; |
| return crossClassTargets == null || crossClassTargets.contains(method) |
| || method.isJsInteropEntryPoint(); |
| } |
| |
| private Iterable<JMethod> getPotentiallyAliveConstructors(JDeclaredType x) { |
| return Iterables.filter(x.getMethods(), new Predicate<JMethod>() { |
| @Override |
| public boolean apply(JMethod m) { |
| return isMethodPotentiallyALiveConstructor(m); |
| } |
| }); |
| } |
| |
| /** |
| * Whether a method is a constructor that is actually newed. Note that in absence of whole |
| * world knowledge evey constructor is potentially live. |
| */ |
| private boolean isMethodPotentiallyALiveConstructor(JMethod method) { |
| if (!(method instanceof JConstructor)) { |
| return false; |
| } |
| assert incremental || liveCtors != null; |
| return liveCtors == null || liveCtors.contains(method); |
| } |
| |
| private JsInvocation maybeCreateClinitCall(JField x) { |
| if (!x.isStatic() || x.isCompileTimeConstant()) { |
| // Access to compile time constants do not trigger class initialization (JLS 12.4.1). |
| return null; |
| } |
| |
| JDeclaredType targetType = x.getEnclosingType().getClinitTarget(); |
| if (targetType == null |
| || targetType == program.getTypeClassLiteralHolder() |
| // When currentMethod == null, the clinit is being hoisted to the global scope. |
| || (currentMethod != null |
| && !currentMethod.getEnclosingType().checkClinitTo(targetType))) { |
| return null; |
| } |
| |
| JMethod clinitMethod = targetType.getClinitMethod(); |
| SourceInfo sourceInfo = x.getSourceInfo(); |
| return new JsInvocation(sourceInfo, names.get(clinitMethod).makeRef(sourceInfo)); |
| } |
| |
| private JsInvocation maybeCreateClinitCall(JMethod method) { |
| if (!isMethodPotentiallyCalledAcrossClasses(method)) { |
| // Global optimized compile can prune some clinit calls. |
| return null; |
| } |
| JDeclaredType enclosingType = method.getEnclosingType(); |
| if (method.canBePolymorphic() || (program.isStaticImpl(method) && |
| !method.isJsOverlay())) { |
| return null; |
| } |
| if (enclosingType == null || !enclosingType.hasClinit()) { |
| return null; |
| } |
| // Avoid recursion sickness. |
| if (JProgram.isClinit(method)) { |
| return null; |
| } |
| |
| JMethod clinitMethod = enclosingType.getClinitTarget().getClinitMethod(); |
| SourceInfo sourceInfo = method.getSourceInfo(); |
| return new JsInvocation(sourceInfo, names.get(clinitMethod).makeRef(sourceInfo)); |
| } |
| |
| /** |
| * If a field is a literal, we can potentially treat it as immutable and assign it once on the |
| * prototype, to be reused by all instances of the class, instead of re-assigning the same |
| * literal in each constructor. |
| */ |
| private boolean initializeAtTopScope(JField x) { |
| if (x.getEnclosingType().isJsFunctionImplementation()) { |
| // JsFunction implementation are plain JS functions with no class prototype, fields |
| // need to be initialized and placed on the instance itself. |
| return false; |
| } |
| if (x.getLiteralInitializer() == null) { |
| return false; |
| } |
| if (x.isFinal() || x.isStatic() || x.isCompileTimeConstant()) { |
| // we can definitely initialize at top-scope, as JVM does so as well |
| return true; |
| } |
| |
| return !uninitializedValuePotentiallyObservable.apply(x); |
| } |
| |
| /** |
| * Helpers to avoid casting (can be removed when compiling in Java 8). |
| */ |
| private <T extends JsExpression> T transform(JExpression expression) { |
| return transform((JNode) expression); |
| } |
| |
| private <T extends JsStatement> T transform(JStatement statement) { |
| return transform((JNode) statement); |
| } |
| |
| private JsBlock transform(JBlock statement) { |
| return transform((JNode) statement); |
| } |
| } |
| |
| private void addVarsIfNotEmpty(JsVars vars) { |
| if (!vars.isEmpty()) { |
| getGlobalStatements().add(vars); |
| } |
| } |
| |
| private List<JsStatement> getGlobalStatements() { |
| return jsProgram.getGlobalBlock().getStatements(); |
| } |
| |
| /** |
| * Return false if the method needs to be generated. Some methods do not need any output, |
| * in particular abstract methods and static intializers that are never called. |
| */ |
| private static boolean doesNotHaveConcreteImplementation(JMethod method) { |
| return method.isAbstract() |
| || method.isJsNative() |
| || JjsUtils.isJsMemberUnnecessaryAccidentalOverride(method) |
| || (JProgram.isClinit(method) |
| && method.getEnclosingType().getClinitTarget() != method.getEnclosingType()); |
| } |
| |
| private static class JavaToJsOperatorMap { |
| private static final Map<JBinaryOperator, JsBinaryOperator> bOpMap = |
| Maps.newEnumMap(JBinaryOperator.class); |
| private static final Map<JUnaryOperator, JsUnaryOperator> uOpMap = |
| Maps.newEnumMap(JUnaryOperator.class); |
| |
| static { |
| bOpMap.put(JBinaryOperator.MUL, JsBinaryOperator.MUL); |
| bOpMap.put(JBinaryOperator.DIV, JsBinaryOperator.DIV); |
| bOpMap.put(JBinaryOperator.MOD, JsBinaryOperator.MOD); |
| bOpMap.put(JBinaryOperator.ADD, JsBinaryOperator.ADD); |
| bOpMap.put(JBinaryOperator.CONCAT, JsBinaryOperator.ADD); |
| bOpMap.put(JBinaryOperator.SUB, JsBinaryOperator.SUB); |
| bOpMap.put(JBinaryOperator.SHL, JsBinaryOperator.SHL); |
| bOpMap.put(JBinaryOperator.SHR, JsBinaryOperator.SHR); |
| bOpMap.put(JBinaryOperator.SHRU, JsBinaryOperator.SHRU); |
| bOpMap.put(JBinaryOperator.LT, JsBinaryOperator.LT); |
| bOpMap.put(JBinaryOperator.LTE, JsBinaryOperator.LTE); |
| bOpMap.put(JBinaryOperator.GT, JsBinaryOperator.GT); |
| bOpMap.put(JBinaryOperator.GTE, JsBinaryOperator.GTE); |
| bOpMap.put(JBinaryOperator.EQ, JsBinaryOperator.EQ); |
| bOpMap.put(JBinaryOperator.NEQ, JsBinaryOperator.NEQ); |
| bOpMap.put(JBinaryOperator.BIT_AND, JsBinaryOperator.BIT_AND); |
| bOpMap.put(JBinaryOperator.BIT_XOR, JsBinaryOperator.BIT_XOR); |
| bOpMap.put(JBinaryOperator.BIT_OR, JsBinaryOperator.BIT_OR); |
| bOpMap.put(JBinaryOperator.AND, JsBinaryOperator.AND); |
| bOpMap.put(JBinaryOperator.OR, JsBinaryOperator.OR); |
| bOpMap.put(JBinaryOperator.ASG, JsBinaryOperator.ASG); |
| bOpMap.put(JBinaryOperator.ASG_ADD, JsBinaryOperator.ASG_ADD); |
| bOpMap.put(JBinaryOperator.ASG_CONCAT, JsBinaryOperator.ASG_ADD); |
| bOpMap.put(JBinaryOperator.ASG_SUB, JsBinaryOperator.ASG_SUB); |
| bOpMap.put(JBinaryOperator.ASG_MUL, JsBinaryOperator.ASG_MUL); |
| bOpMap.put(JBinaryOperator.ASG_DIV, JsBinaryOperator.ASG_DIV); |
| bOpMap.put(JBinaryOperator.ASG_MOD, JsBinaryOperator.ASG_MOD); |
| bOpMap.put(JBinaryOperator.ASG_SHL, JsBinaryOperator.ASG_SHL); |
| bOpMap.put(JBinaryOperator.ASG_SHR, JsBinaryOperator.ASG_SHR); |
| bOpMap.put(JBinaryOperator.ASG_SHRU, JsBinaryOperator.ASG_SHRU); |
| bOpMap.put(JBinaryOperator.ASG_BIT_AND, JsBinaryOperator.ASG_BIT_AND); |
| bOpMap.put(JBinaryOperator.ASG_BIT_OR, JsBinaryOperator.ASG_BIT_OR); |
| bOpMap.put(JBinaryOperator.ASG_BIT_XOR, JsBinaryOperator.ASG_BIT_XOR); |
| |
| uOpMap.put(JUnaryOperator.INC, JsUnaryOperator.INC); |
| uOpMap.put(JUnaryOperator.DEC, JsUnaryOperator.DEC); |
| uOpMap.put(JUnaryOperator.NEG, JsUnaryOperator.NEG); |
| uOpMap.put(JUnaryOperator.NOT, JsUnaryOperator.NOT); |
| uOpMap.put(JUnaryOperator.BIT_NOT, JsUnaryOperator.BIT_NOT); |
| } |
| |
| public static JsBinaryOperator get(JBinaryOperator op) { |
| return bOpMap.get(op); |
| } |
| |
| public static JsUnaryOperator get(JUnaryOperator op) { |
| return uOpMap.get(op); |
| } |
| } |
| |
| private class CollectJsFunctionsForInlining extends JVisitor { |
| |
| // JavaScript functions that arise from methods that were not inlined in the Java AST |
| // NOTE: We use a LinkedHashSet to preserve the order of insertion. So that the following passes |
| // that use this result are deterministic. |
| private Set<JsNode> functionsForJsInlining = Sets.newLinkedHashSet(); |
| private JMethod currentMethod; |
| |
| @Override |
| public void endVisit(JMethod x, Context ctx) { |
| if (x.isJsniMethod()) { |
| // These are methods whose bodies where not traversed by the Java method inliner. |
| JsFunction function = jsFunctionsByJavaMethodBody.get(x.getBody()); |
| if (function != null && function.getBody() != null) { |
| functionsForJsInlining.add(function); |
| } |
| // Add all functions declared inside JSNI blocks as well. |
| assert function != null; |
| new JsModVisitor() { |
| @Override |
| public void endVisit(JsFunction x, JsContext ctx) { |
| functionsForJsInlining.add(x); |
| } |
| }.accept(function); |
| } |
| |
| currentMethod = null; |
| } |
| |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JMethod target = x.getTarget(); |
| if (target.isInliningAllowed() && (target.isJsniMethod() |
| || program.getIndexedTypes().contains(target.getEnclosingType()) |
| || target.getInliningMode() == InliningMode.FORCE_INLINE)) { |
| // These are either: 1) callsites to JSNI functions, in which case MethodInliner did not |
| // attempt to inline; 2) inserted by normalizations passes AFTER all inlining or 3) |
| // calls to methods annotated with @ForceInline that were not inlined by the simple |
| // MethodInliner. |
| JsFunction function = jsFunctionsByJavaMethodBody.get(currentMethod.getBody()); |
| if (function != null && function.getBody() != null) { |
| functionsForJsInlining.add(function); |
| } |
| } |
| } |
| |
| @Override |
| public boolean visit(JMethod x, Context ctx) { |
| currentMethod = x; |
| return true; |
| } |
| |
| public Set<JsNode> getFunctionsForJsInlining() { |
| accept(program); |
| return functionsForJsInlining; |
| } |
| } |
| |
| /** |
| * Computes:<p> |
| * <ul> |
| * <li> 1. whether a constructors are live directly (through being in a new operation) or |
| * indirectly (only called by other constructors). Only directly live constructors become |
| * JS constructor, otherwise they will behave like regular static functions. |
| * </li> 2. whether there exists cross class (static) calls or accesses that would need clinits to |
| * be triggered. If not clinits need only be called in constructors. |
| * <li> |
| * </li> |
| * </ul> |
| */ |
| private class RecordCrossClassCallsAndConstructorLiveness extends JVisitor { |
| // TODO(rluble): This analysis should be extracted from GenerateJavaScriptAST into its own |
| // JAVA optimization pass. Constructors that are not newed can be transformed into statified |
| // regular methods; and methods that are not called from outside the class boundary can be |
| // privatized. Currently we do not use the private modifier to avoid emitting clinits, instead |
| // we use the result of this analysis (private methods CAN be called from JSNI in an unrelated |
| // class, touche!). |
| { |
| crossClassTargets = Sets.newHashSet(); |
| liveCtors = Sets.newIdentityHashSet(); |
| } |
| |
| private JMethod currentMethod; |
| |
| @Override |
| public void endVisit(JMethod x, Context ctx) { |
| // methods which are exported or static indexed methods may be called externally |
| if (x.isJsInteropEntryPoint() |
| || (x.isStatic() && program.getIndexedMethods().contains(x))) { |
| if (x instanceof JConstructor) { |
| // exported ctors always considered live |
| liveCtors.add((JConstructor) x); |
| } |
| // could be called from JS, so clinit must be called from body |
| crossClassTargets.add(x); |
| } |
| currentMethod = null; |
| } |
| |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JDeclaredType sourceType = currentMethod.getEnclosingType(); |
| JDeclaredType targetType = x.getTarget().getEnclosingType(); |
| if (sourceType.checkClinitTo(targetType)) { |
| crossClassTargets.add(x.getTarget()); |
| } |
| } |
| |
| @Override |
| public void endVisit(JNewInstance x, Context ctx) { |
| super.endVisit(x, ctx); |
| liveCtors.add(x.getTarget()); |
| } |
| |
| @Override |
| public void endVisit(JProgram x, Context ctx) { |
| // Entry methods can be called externally, so they must run clinit. |
| crossClassTargets.addAll(x.getEntryMethods()); |
| } |
| |
| @Override |
| public void endVisit(JsniMethodRef x, Context ctx) { |
| if (x.getTarget() instanceof JConstructor) { |
| liveCtors.add((JConstructor) x.getTarget()); |
| } |
| |
| endVisit((JMethodCall) x, ctx); |
| } |
| |
| @Override |
| public boolean visit(JMethod x, Context ctx) { |
| currentMethod = x; |
| return true; |
| } |
| } |
| |
| private static class SortVisitor extends JVisitor { |
| |
| @Override |
| public void endVisit(JClassType x, Context ctx) { |
| x.sortFields(HasName.BY_NAME_COMPARATOR); |
| x.sortMethods(JMethod.BY_SIGNATURE_COMPARATOR); |
| } |
| |
| @Override |
| public void endVisit(JInterfaceType x, Context ctx) { |
| x.sortFields(HasName.BY_NAME_COMPARATOR); |
| x.sortMethods(JMethod.BY_SIGNATURE_COMPARATOR); |
| } |
| |
| @Override |
| public void endVisit(JMethodBody x, Context ctx) { |
| x.sortLocals(HasName.BY_NAME_COMPARATOR); |
| } |
| |
| @Override |
| public void endVisit(JProgram x, Context ctx) { |
| Collections.sort(x.getEntryMethods(), JMethod.BY_SIGNATURE_COMPARATOR); |
| Collections.sort(x.getDeclaredTypes(), HasName.BY_NAME_COMPARATOR); |
| } |
| |
| @Override |
| public boolean visit(JMethodBody x, Context ctx) { |
| // No need to visit method bodies. |
| return false; |
| } |
| } |
| |
| /** |
| * This is the main entry point for the translation from Java to JavaScript. Starts from a |
| * Java AST and constructs a JavaScript AST while collecting other useful information that |
| * is used in subsequent passes. |
| * |
| * @param logger a TreeLogger |
| * @param program a Java AST |
| * @param jsProgram an (empty) JavaScript AST |
| * @param symbolTable an (empty) symbol table that will be populated here |
| * |
| * @return A pair containing a JavaToJavaScriptMap and a Set of JsFunctions that need to be |
| * considered for inlining. |
| */ |
| public static Pair<JavaToJavaScriptMap, Set<JsNode>> exec(TreeLogger logger, JProgram program, |
| JsProgram jsProgram, CompilerContext compilerContext, TypeMapper<?> typeMapper, |
| Map<StandardSymbolData, JsName> symbolTable, PermutationProperties props) { |
| |
| Event event = SpeedTracerLogger.start(CompilerEventType.GENERATE_JS_AST); |
| try { |
| GenerateJavaScriptAST generateJavaScriptAST = new GenerateJavaScriptAST(logger, program, |
| jsProgram, compilerContext, typeMapper, symbolTable, props); |
| return generateJavaScriptAST.execImpl(); |
| } finally { |
| event.end(); |
| } |
| } |
| |
| private static final ImmutableList<String> METHODS_PROVIDED_BY_PREAMBLE = ImmutableList.of( |
| "Class.createForClass", "Class.createForPrimitive", "Class.createForInterface", |
| "Class.createForEnum"); |
| |
| private final Map<JBlock, JsCatch> catchMap = Maps.newIdentityHashMap(); |
| |
| private final Set<JsName> catchParamIdentifiers = Sets.newHashSet(); |
| |
| private final Map<JClassType, JsScope> classScopes = Maps.newIdentityHashMap(); |
| |
| /** |
| * A list of methods that are called from another class (ie might need to |
| * clinit). |
| */ |
| private Set<JMethod> crossClassTargets = null; |
| |
| /** |
| * Contains JsNames for all interface methods. A special scope is needed so |
| * that independent classes will obfuscate their interface implementation |
| * methods the same way. |
| */ |
| private final JsScope interfaceScope; |
| |
| private final JsProgram jsProgram; |
| |
| private Set<JConstructor> liveCtors = null; |
| |
| /** |
| * Classes that could potentially see uninitialized values for fields that are initialized in the |
| * declaration. |
| */ |
| private Predicate<JField> uninitializedValuePotentiallyObservable; |
| |
| private final Map<JAbstractMethodBody, JsFunction> jsFunctionsByJavaMethodBody = |
| Maps.newIdentityHashMap(); |
| private final Map<HasName, JsName> names = Maps.newIdentityHashMap(); |
| |
| /** |
| * Contains JsNames for the Object instance methods, such as equals, hashCode, |
| * and toString. All other class scopes have this scope as an ultimate parent. |
| */ |
| private final JsScope objectScope; |
| private final Map<JMethod, JsName> polymorphicNames = Maps.newIdentityHashMap(); |
| private final JProgram program; |
| |
| /** |
| * SEt of all targets of JNameOf. |
| */ |
| private Set<HasName> nameOfTargets = Sets.newHashSet(); |
| |
| private final TreeLogger logger; |
| |
| /** |
| * Maps JsNames to machine-usable identifiers. |
| */ |
| private final Map<StandardSymbolData, JsName> symbolTable; |
| |
| /** |
| * Contains JsNames for all globals, such as static fields and methods. |
| */ |
| private final JsScope topScope; |
| |
| private final Map<JsStatement, JDeclaredType> javaTypeByGlobalStatement = Maps.newHashMap(); |
| |
| private final Map<JsStatement, JMethod> methodByGlobalStatement = Maps.newHashMap(); |
| |
| private final TypeMapper<?> typeMapper; |
| |
| private final MinimalRebuildCache minimalRebuildCache; |
| |
| private final PermutationProperties properties; |
| |
| private JsFunction objectConstructorFunction; |
| |
| private OptionMethodNameDisplayMode.Mode methodNameMappingMode; |
| |
| private final boolean closureCompilerFormatEnabled; |
| |
| private final boolean optimize; |
| |
| // This is also used to do some final optimizations. |
| // TODO(rluble) move optimizations to a Java AST optimization pass. |
| private final boolean incremental; |
| |
| /** |
| * If true, polymorphic functions are made anonymous vtable declarations and |
| * not assigned topScope identifiers. |
| */ |
| private final boolean stripStack; |
| |
| private GenerateJavaScriptAST(TreeLogger logger, JProgram program, JsProgram jsProgram, |
| CompilerContext compilerContext, TypeMapper<?> typeMapper, |
| Map<StandardSymbolData, JsName> symbolTable, PermutationProperties properties) { |
| this.logger = logger; |
| this.program = program; |
| this.jsProgram = jsProgram; |
| this.topScope = jsProgram.getScope(); |
| this.objectScope = jsProgram.getObjectScope(); |
| this.interfaceScope = new JsNormalScope(objectScope, "Interfaces"); |
| this.minimalRebuildCache = compilerContext.getMinimalRebuildCache(); |
| this.symbolTable = symbolTable; |
| this.typeMapper = typeMapper; |
| this.properties = properties; |
| |
| PrecompileTaskOptions options = compilerContext.getOptions(); |
| this.optimize = options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT; |
| this.methodNameMappingMode = options.getMethodNameDisplayMode(); |
| assert methodNameMappingMode != null; |
| this.incremental = options.isIncrementalCompileEnabled(); |
| |
| this.stripStack = JsStackEmulator.getStackMode(properties) == JsStackEmulator.StackMode.STRIP; |
| this.closureCompilerFormatEnabled = options.isClosureCompilerFormatEnabled(); |
| this.objectConstructorFunction = |
| new JsFunction(SourceOrigin.UNKNOWN, topScope, topScope.findExistingName("Object")); |
| } |
| |
| /** |
| * Retrieves the runtime typeId for {@code type}. |
| */ |
| JExpression getRuntimeTypeReference(JReferenceType type) { |
| return typeMapper.get(type); |
| } |
| |
| private String mangleName(JField x) { |
| return JjsUtils.mangleMemberName(x.getEnclosingType().getName(), x.getName()); |
| } |
| |
| private String mangleNameForGlobal(JMethod method) { |
| String s = |
| JjsUtils.mangleMemberName(method.getEnclosingType().getName(), method.getName()) + "__"; |
| for (JType type : method.getOriginalParamTypes()) { |
| s += type.getJavahSignatureName(); |
| } |
| s += method.getOriginalReturnType().getJavahSignatureName(); |
| return StringInterner.get().intern(s); |
| } |
| |
| private String mangleNameForPackagePrivatePoly(JMethod method) { |
| assert method.isPackagePrivate() && !method.isStatic(); |
| /* |
| * Package private instance methods in different package should not override each |
| * other, so they must have distinct polymorphic names. Therefore, add the |
| * package to the mangled name. |
| */ |
| String mangledName = Joiner.on("$").join( |
| "package_private", |
| JjsUtils.mangledNameString(method.getEnclosingType().getPackageName()), |
| JjsUtils.mangledNameString(method)); |
| return StringInterner.get().intern(JjsUtils.constructManglingSignature(method, mangledName)); |
| } |
| |
| private String mangleNameForPoly(JMethod method) { |
| if (method.isPrivate()) { |
| return mangleNameForPrivatePoly(method); |
| } else if (method.isPackagePrivate()) { |
| return mangleNameForPackagePrivatePoly(method); |
| } else { |
| return mangleNameForPublicPoly(method); |
| } |
| } |
| |
| private String mangleNameForPublicPoly(JMethod method) { |
| return StringInterner.get().intern( |
| JjsUtils.constructManglingSignature(method, JjsUtils.mangledNameString(method))); |
| } |
| |
| private String mangleNameForPrivatePoly(JMethod method) { |
| assert method.isPrivate() && !method.isStatic(); |
| /* |
| * Private instance methods in different classes should not override each |
| * other, so they must have distinct polymorphic names. Therefore, add the |
| * class name to the mangled name. |
| */ |
| String mangledName = Joiner.on("$").join( |
| "private", |
| JjsUtils.mangledNameString(method.getEnclosingType()), |
| JjsUtils.mangledNameString(method)); |
| |
| return StringInterner.get().intern(JjsUtils.constructManglingSignature(method, mangledName)); |
| } |
| |
| private final Map<JType, JDeclarationStatement> classLiteralDeclarationsByType = |
| Maps.newLinkedHashMap(); |
| |
| private void contructTypeToClassLiteralDeclarationMap() { |
| /* |
| * Must execute in clinit statement order, NOT field order, so that back |
| * refs to super classes are preserved. |
| */ |
| JMethodBody clinitBody = |
| (JMethodBody) program.getTypeClassLiteralHolder().getClinitMethod().getBody(); |
| for (JStatement stmt : clinitBody.getStatements()) { |
| if (!(stmt instanceof JDeclarationStatement)) { |
| continue; |
| } |
| JDeclarationStatement classLiteralDeclaration = (JDeclarationStatement) stmt; |
| |
| JType type = program.getTypeByClassLiteralField( |
| (JField) ((JDeclarationStatement) stmt).getVariableRef().getTarget()); |
| |
| assert !classLiteralDeclarationsByType.containsKey(type); |
| classLiteralDeclarationsByType.put(type, classLiteralDeclaration); |
| } |
| } |
| |
| private Pair<JavaToJavaScriptMap, Set<JsNode>> execImpl() { |
| NameClashesFixer.exec(program); |
| uninitializedValuePotentiallyObservable = optimize |
| ? ComputePotentiallyObservableUninitializedValues.analyze(program) |
| : Predicates.<JField>alwaysTrue(); |
| new FindNameOfTargets().accept(program); |
| new SortVisitor().accept(program); |
| if (!incremental) { |
| // TODO(rluble): pull out this analysis and make it a Java AST optimization pass. |
| new RecordCrossClassCallsAndConstructorLiveness().accept(program); |
| } |
| |
| // Map class literals to their respective types. |
| contructTypeToClassLiteralDeclarationMap(); |
| |
| new CreateNamesAndScopesVisitor().accept(program); |
| new GenerateJavaScriptTransformer().transform(program); |
| |
| // TODO(spoon): Instead of gathering the information here, get it via |
| // SourceInfo |
| JavaToJavaScriptMap jjsMap = new JavaToJavaScriptMapImpl(program.getDeclaredTypes(), |
| names, javaTypeByGlobalStatement, methodByGlobalStatement); |
| |
| Set<JsNode> functionsForJsInlining = incremental ? Collections.<JsNode>emptySet() : |
| new CollectJsFunctionsForInlining().getFunctionsForJsInlining(); |
| |
| return Pair.create(jjsMap, functionsForJsInlining); |
| } |
| |
| private JsFunction getJsFunctionFor(JMethod jMethod) { |
| return jsFunctionsByJavaMethodBody.get(jMethod.getBody()); |
| } |
| |
| private JsName getIndexedMethodJsName(String indexedName) { |
| return names.get(program.getIndexedMethod(indexedName)); |
| } |
| |
| private JsName getIndexedFieldJsName(String indexedName) { |
| return names.get(program.getIndexedField(indexedName)); |
| } |
| |
| private static JsNameRef createGlobalQualifier(String qualifier, SourceInfo sourceInfo) { |
| return JsUtils.createQualifiedNameRef(JsInteropUtil.normalizeQualifier(qualifier), sourceInfo); |
| } |
| } |