blob: 698b6abaf44bce1f4ab3209456eb3d582903e62e [file] [log] [blame]
/*
* 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 com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.linker.CastableTypeMap;
import com.google.gwt.core.ext.linker.impl.StandardCastableTypeMap;
import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.JsOutputOption;
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.HasName;
import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension;
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.JAssertStatement;
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.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.JGwtCreate;
import com.google.gwt.dev.jjs.ast.JIfStatement;
import com.google.gwt.dev.jjs.ast.JInstanceOf;
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.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JLongLiteral;
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.JNewArray;
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.JPostfixOperation;
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReboundEntryPoint;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JSeedIdOf;
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.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JTypeOracle;
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.js.JDebuggerStatement;
import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
import com.google.gwt.dev.jjs.ast.js.JsCastMap;
import com.google.gwt.dev.jjs.ast.js.JsCastMap.JsQueryType;
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.ast.js.JsonObject;
import com.google.gwt.dev.jjs.ast.js.JsonObject.JsonPropInit;
import com.google.gwt.dev.js.JsInliner;
import com.google.gwt.dev.js.JsParser;
import com.google.gwt.dev.js.JsStackEmulator;
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.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.JsPostfixOperation;
import com.google.gwt.dev.js.ast.JsPrefixOperation;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsPropertyInitializer;
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.JsSeedIdOf;
import com.google.gwt.dev.js.ast.JsStatement;
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.JsUnaryOperation;
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.DefaultTextOutput;
import com.google.gwt.dev.util.Pair;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.TextOutput;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.dev.util.collect.Maps;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.collect.Sets;
import com.google.gwt.thirdparty.guava.common.collect.LinkedHashMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
/**
* Creates a JavaScript AST from a <code>JProgram</code> node.
*/
public class GenerateJavaScriptAST {
/**
* The GWT Java AST might contain different local variables with the same name in the same
* scope. This fixup pass renames variables in the case they clash in a scope.
*/
private static class FixNameClashesVisitor extends JVisitor {
/**
* Represents the scope tree defined by nested statement blocks. It is a temporary
* structure to track local variable lifetimes.
*/
private static class Scope {
private Scope parent;
// Keeps track what names are used in children.
private Set<String> usedInChildScope = new HashSet<String>();
// Keeps track what names have this scope as its lifetime.
private Set<String> namesInThisScope = new HashSet<String>();
/**
* The depth at which this scope is in the tree.
*/
private int level;
private Scope() {
this.parent = null;
this.level = 0;
}
private Scope(Scope parent) {
this.parent = parent;
this.level = parent.level + 1;
}
private static Scope getInnermostEnclosingScope(Scope thisScope, Scope thatScope) {
if (thisScope == null) {
return thatScope;
}
if (thatScope == null) {
return thisScope;
}
if (thisScope == thatScope) {
return thisScope;
}
if (thisScope.level > thatScope.level) {
return getInnermostEnclosingScope(thatScope, thisScope);
}
if (thisScope.level == thatScope.level) {
return getInnermostEnclosingScope(thisScope.parent, thatScope.parent);
}
return getInnermostEnclosingScope(thisScope, thatScope.parent);
}
private void addChildUsage(String name) {
usedInChildScope.add(name);
if (parent != null) {
parent.addChildUsage(name);
}
}
protected void addUsedName(String name) {
namesInThisScope.add(name);
if (parent != null) {
parent.addChildUsage(name);
}
}
private boolean isUsedInParent(String name) {
return namesInThisScope.contains(name) ||
(parent != null && parent.isUsedInParent(name));
}
protected boolean isConflictingName(String name) {
return usedInChildScope.contains(name) || isUsedInParent(name);
}
}
private Scope currentScope;
private Map<JLocal, Scope> scopesByLocal;
private Multimap<String, JLocal> localsByName;
@Override
public boolean visit(JMethodBody x, Context ctx) {
// Start constructing the scope tree.
currentScope = new Scope();
scopesByLocal = new HashMap<JLocal, Scope>();
localsByName = LinkedHashMultimap.create();
return true;
}
@Override
public boolean visit(JBlock x, Context ctx) {
currentScope = new Scope(currentScope);
return true;
}
@Override
public void endVisit(JBlock x, Context ctx) {
currentScope = currentScope.parent;
}
@Override
public void endVisit(JLocalRef x, Context ctx) {
// We use the a block scope as a proxy for a lifetime which is safe to do albeit non optimal.
//
// Keep track of the scope that encloses a variable lifetime. E.g. assume the following code.
// { // scope 1
// { // scope 1.1
// ... a... b...
// { // scope 1.1.1
// ... a ...
// }
// }
// { // scope 1.2
// ... b...
// }
// }
// Scope 1.1 is the innermost scope that encloses the lifetime of variable a and
// scope 1 is the innermost scope that encloses the lifetime of variable b.
JLocal local = x.getLocal();
Scope oldVariableScope = scopesByLocal.get(local);
Scope newVariableScope = Scope.getInnermostEnclosingScope(oldVariableScope, currentScope);
newVariableScope.addUsedName(local.getName());
if (newVariableScope != oldVariableScope) {
scopesByLocal.put(local, newVariableScope);
}
localsByName.put(local.getName(), local);
}
@Override
public void endVisit(JMethodBody x, Context ctx) {
// Fix clashing variables here. Two locals are clashing if they have the same name and their
// computed lifetimes are intersecting. By using the scope to model lifetimes two variables
// clash if their computed scopes are nested.
for (String name : localsByName.keySet()) {
Collection<JLocal> localSet = localsByName.get(name);
if (localSet.size() == 1) {
continue;
}
JLocal[] locals = localSet.toArray(new JLocal[localSet.size()]);
// TODO(rluble): remove n^2 behaviour in conflict checking.
// In practice each method has only a handful of locals so this process is not expected
// to be a performance problem.
for (int i = 0; i < locals.length; i++ ) {
// See if local i conflicts with any local j > i
for (int j = i + 1; j < locals.length; j++ ) {
Scope iLocalScope = scopesByLocal.get(locals[i]);
Scope jLocalScope = scopesByLocal.get(locals[j]);
Scope commonAncestor = Scope.getInnermostEnclosingScope(iLocalScope, jLocalScope);
if (commonAncestor != iLocalScope && commonAncestor != jLocalScope) {
// no conflict
continue;
}
// conflicting locals => find a unique name rename local i to it;
int n = 0;
String baseName = locals[i].getName();
String newName;
do {
// The active namer will clean up these potentially long names.
newName = baseName + n++;
} while (iLocalScope.isConflictingName(newName));
locals[i].setName(newName);
iLocalScope.addUsedName(newName);
// There is no need to update the localsByNameMap as newNames are always guaranteed to
// be clash free.
break;
}
}
}
// Only valid for the duration of one method body visit/endVisit pair.
currentScope = null;
scopesByLocal = null;
localsByName = null;
}
}
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 = new HashMap<String, String>();
private final Stack<JsScope> scopeStack = new Stack<JsScope>();
@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) {
pop();
}
@Override
public void endVisit(JsCastMap x, Context ctx) {
/*
* Intern JsCastMaps, at this stage, they are only present in Array initialization,
* so we always intern them even if they occur once, since every array initialization
* makes a copy.
*/
internedCastMap.add(castMapToString(x));
}
@Override
public void endVisit(JField x, Context ctx) {
String name = x.getName();
String mangleName = mangleName(x);
if (x.isStatic()) {
JsName jsName = topScope.declareName(mangleName, name);
names.put(x, jsName);
recordSymbol(x, jsName);
} else {
JsName jsName;
if (specialObfuscatedFields.containsKey(x)) {
jsName = peek().declareName(mangleNameSpecialObfuscate(x));
jsName.setObfuscatable(false);
} else {
jsName = peek().declareName(mangleName, name);
}
names.put(x, jsName);
recordSymbol(x, jsName);
}
}
@Override
public void endVisit(JInterfaceType x, Context ctx) {
pop();
}
@Override
public void endVisit(JLabel x, Context ctx) {
if (names.get(x) != null) {
return;
}
names.put(x, 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 = peek();
JsName jsName = scope.declareName(x.getName());
names.put(x, jsName);
}
@Override
public void endVisit(JMethod x, Context ctx) {
pop();
}
@Override
public void endVisit(JParameter x, Context ctx) {
names.put(x, 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.getName()));
JField nullField = x.getNullField();
JsName nullFieldName = objectScope.declareName(nullField.getName());
names.put(nullField, nullFieldName);
/*
* put nullMethod in the global scope, too; it's the replacer for clinits
*/
nullFunc = createGlobalFunction("function " + nullMethod.getName() + "(){}");
names.put(nullMethod, nullFunc.getName());
/*
* Create names for instantiable array types since JProgram.traverse()
* doesn't iterate over them.
*/
for (JArrayType arrayType : program.getAllArrayTypes()) {
if (typeOracle.isInstantiatedType(arrayType)) {
accept(arrayType);
}
}
// Generate symbolic names for all query type ids.
if (!output.shouldMinimize()) {
setupSymbolicCastMaps();
}
}
@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) {
push(myScope);
return false;
}
// My seed function name
JsName jsName = topScope.declareName(getNameString(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);
push(myScope);
return true;
}
@Override
public boolean visit(JInterfaceType x, Context ctx) {
// interfaces have no name at run time
push(interfaceScope);
return true;
}
@Override
public boolean visit(JMethod x, Context ctx) {
// my polymorphic name
String name = x.getName();
if (x.needsVtable()) {
if (polymorphicNames.get(x) == null) {
JsName polyName;
if (x.isPrivate()) {
polyName = interfaceScope.declareName(mangleNameForPrivatePoly(x), name);
} else if (specialObfuscatedMethodSigs.containsKey(x.getSignature())) {
polyName = interfaceScope.declareName(mangleNameSpecialObfuscate(x));
polyName.setObfuscatable(false);
} else {
polyName = interfaceScope.declareName(mangleNameForPoly(x), name);
}
polymorphicNames.put(x, polyName);
}
}
if (x.isAbstract()) {
// just push a dummy scope that we can pop in endVisit
push(null);
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,
* or stack-stripping is disabled.
*/
if (!stripStack || !polymorphicNames.containsKey(x) || x.isNative()) {
globalName = topScope.declareName(mangleName, name);
names.put(x, globalName);
recordSymbol(x, globalName);
}
JsFunction jsFunction;
if (x.isNative()) {
// set the global name of the JSNI peer
JsniMethodBody body = (JsniMethodBody) x.getBody();
jsFunction = body.getFunc();
jsFunction.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.
*/
jsFunction = new JsFunction(x.getSourceInfo(), topScope, globalName, true);
}
if (polymorphicNames.containsKey(x)) {
polymorphicJsFunctions.add(jsFunction);
}
methodBodyMap.put(x.getBody(), jsFunction);
push(jsFunction.getScope());
if (program.getIndexedMethods().contains(x)) {
indexedFunctions =
Maps.put(indexedFunctions, x.getEnclosingType().getShortName() + "." + x.getName(),
jsFunction);
}
return true;
}
@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(), peek(), arg.getTarget().getName());
JsParameter jsParam = jsCatch.getParameter();
names.put(arg.getTarget(), jsParam.getName());
catchMap.put(catchBlock, jsCatch);
catchParamIdentifiers.add(jsParam.getName());
push(jsCatch.getScope());
accept(catchBlock);
pop();
}
// TODO: normalize this so it's never null?
if (x.getFinallyBlock() != null) {
accept(x.getFinallyBlock());
}
return false;
}
private JsFunction createGlobalFunction(String code) {
try {
List<JsStatement> stmts =
JsParser.parse(SourceOrigin.UNKNOWN, topScope, new StringReader(code));
assert stmts.size() == 1;
JsExprStmt stmt = (JsExprStmt) stmts.get(0);
List<JsStatement> globalStmts = jsProgram.getGlobalBlock().getStatements();
globalStmts.add(0, stmt);
return (JsFunction) stmt.getExpression();
} catch (Exception e) {
throw new InternalCompilerException("Unexpected exception parsing '" + code + "'", e);
}
}
/**
* 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 JsScope peek() {
return scopeStack.peek();
}
private void pop() {
scopeStack.pop();
}
private void push(JsScope scope) {
scopeStack.push(scope);
}
private void recordSymbol(JReferenceType x, JsName jsName) {
StringBuilder sb = new StringBuilder();
sb.append('{');
JsCastMap castMap = program.getCastMap(x);
if (castMap != null) {
boolean isFirst = true;
for (JExpression expr : castMap.getExprs()) {
JsQueryType queryType = (JsQueryType) expr;
if (isFirst) {
isFirst = false;
} else {
sb.append(',');
}
sb.append(queryType.getQueryId());
sb.append(":1");
}
}
sb.append('}');
CastableTypeMap castableTypeMap = new StandardCastableTypeMap(sb.toString());
StandardSymbolData symbolData =
StandardSymbolData.forClass(x.getName(), x.getSourceInfo().getFileName(), x
.getSourceInfo().getStartLine(), program.getQueryId(x), castableTypeMap,
x instanceof JClassType || x instanceof JArrayType ? getSeedId(x) : -1);
assert !symbolTable.containsKey(symbolData);
symbolTable.put(symbolData, jsName);
}
private <T extends HasEnclosingType & HasName & HasSourceInfo> void recordSymbol(T x,
JsName jsName) {
/*
* NB: The use of x.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 methodSig;
if (x instanceof JMethod) {
StringBuilder sb = new StringBuilder();
sb.append('(');
JMethod method = ((JMethod) x);
for (JType t : method.getOriginalParamTypes()) {
sb.append(t.getJsniSignatureName());
}
sb.append(')');
sb.append(method.getOriginalReturnType().getJsniSignatureName());
methodSig = StringInterner.get().intern(sb.toString());
} else {
methodSig = null;
}
StandardSymbolData symbolData =
StandardSymbolData.forMember(x.getEnclosingType().getName(), x.getName(), methodSig,
makeUriString(x), x.getSourceInfo().getStartLine());
assert !symbolTable.containsKey(symbolData) : "Duplicate symbol " + "recorded "
+ jsName.getIdent() + " for " + x.getName() + " and key " + symbolData.getJsniIdent();
symbolTable.put(symbolData, jsName);
}
/**
* Create more readable output by generating symbolic constants for query
* ids.
*/
private void setupSymbolicCastMaps() {
namesByQueryId = new ArrayList<JsName>();
for (JReferenceType type : program.getTypesByQueryId()) {
String shortName;
String longName;
if (type instanceof JArrayType) {
JArrayType arrayType = (JArrayType) type;
JType leafType = arrayType.getLeafType();
if (leafType instanceof JReferenceType) {
shortName = ((JReferenceType) leafType).getShortName();
} else {
shortName = leafType.getName();
}
shortName += "_$" + arrayType.getDims();
longName = getNameString(leafType) + "_$" + arrayType.getDims();
} else {
shortName = type.getShortName();
longName = getNameString(type);
}
JsName name = topScope.declareName("Q$" + longName, "Q$" + shortName);
namesByQueryId.add(name);
}
// TODO(cromwellian): see about moving this into an immortal type
StringBuilder sb = new StringBuilder();
sb.append("function makeCastMap(a) {");
sb.append(" var result = {};");
sb.append(" for (var i = 0, c = a.length; i < c; ++i) {");
sb.append(" result[a[i]] = 1;");
sb.append(" }");
sb.append(" return result;");
sb.append("}");
makeMapFunction = createGlobalFunction(sb.toString());
}
}
private class GenerateJavaScriptVisitor extends GenerateJavaScriptLiterals {
private final Set<JClassType> alreadyRan = new HashSet<JClassType>();
private final JsName arrayLength = objectScope.declareName("length");
private final Set<String> castMapSeen = new HashSet<String>();
private final Map<JClassType, JsFunction> clinitMap = new HashMap<JClassType, JsFunction>();
private JMethod currentMethod = null;
/**
* The JavaScript functions corresponding to the entry methods of the
* program ({@link JProgram#getEntryMethods()}).
*/
private JsFunction[] entryFunctions;
/**
* A reverse index for the entry methods of the program (
* {@link JProgram#getEntryMethods()}). Each entry method is mapped to its
* integer index.
*/
private Map<JMethod, Integer> entryMethodToIndex;
private final JsName globalTemp = topScope.declareName("_");
private final JsName prototype = objectScope.declareName("prototype");
// Methods where inlining hasn't happened yet because they are native or
// contain calls to native methods.
Set<JMethod> methodsForJsInlining = new HashSet<JMethod>();
// 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 final Set<JsNode> functionsForJsInlining = new LinkedHashSet<JsNode>();
{
globalTemp.setObfuscatable(false);
prototype.setObfuscatable(false);
arrayLength.setObfuscatable(false);
}
public GenerateJavaScriptVisitor(Set<JMethod> methodsForJsInlining) {
this.methodsForJsInlining = methodsForJsInlining;
}
@Override
public void endVisit(JAbsentArrayDimension x, Context ctx) {
throw new InternalCompilerException("Should not get here.");
}
@Override
public void endVisit(JArrayLength x, Context ctx) {
assert x.getInstance() != null : "Can't access the length of a null array";
JsExpression qualifier = (JsExpression) pop();
JsNameRef ref = arrayLength.makeRef(x.getSourceInfo());
ref.setQualifier(qualifier);
push(ref);
}
@Override
public void endVisit(JArrayRef x, Context ctx) {
JsArrayAccess jsArrayAccess = new JsArrayAccess(x.getSourceInfo());
jsArrayAccess.setIndexExpr((JsExpression) pop());
jsArrayAccess.setArrayExpr((JsExpression) pop());
push(jsArrayAccess);
}
@Override
public void endVisit(JAssertStatement x, Context ctx) {
throw new InternalCompilerException("Should not get here.");
}
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
JsExpression rhs = (JsExpression) pop(); // rhs
JsExpression lhs = (JsExpression) pop(); // lhs
JsBinaryOperator myOp = JavaToJsOperatorMap.get(x.getOp());
/*
* Use === and !== on reference types, or else you can get wrong answers
* when Object.toString() == 'some string'.
*/
if (myOp == JsBinaryOperator.EQ && x.getLhs().getType() instanceof JReferenceType
&& x.getRhs().getType() instanceof JReferenceType) {
myOp = JsBinaryOperator.REF_EQ;
} else if (myOp == JsBinaryOperator.NEQ && x.getLhs().getType() instanceof JReferenceType
&& x.getRhs().getType() instanceof JReferenceType) {
myOp = JsBinaryOperator.REF_NEQ;
}
push(new JsBinaryOperation(x.getSourceInfo(), myOp, lhs, rhs));
}
@Override
public void endVisit(JBlock x, Context ctx) {
JsBlock jsBlock = new JsBlock(x.getSourceInfo());
List<JsStatement> stmts = jsBlock.getStatements();
popList(stmts, x.getStatements().size()); // stmts
Iterator<JsStatement> iterator = stmts.iterator();
while (iterator.hasNext()) {
JsStatement stmt = iterator.next();
if (stmt instanceof JsEmpty) {
iterator.remove();
}
}
push(jsBlock);
}
@Override
public void endVisit(JBreakStatement x, Context ctx) {
JsNameRef labelRef = null;
if (x.getLabel() != null) {
JsLabel label = (JsLabel) pop(); // label
labelRef = label.getName().makeRef(x.getSourceInfo());
}
push(new JsBreak(x.getSourceInfo(), labelRef));
}
@Override
public void endVisit(JCaseStatement x, Context ctx) {
if (x.getExpr() == null) {
push(new JsDefault(x.getSourceInfo()));
} else {
JsCase jsCase = new JsCase(x.getSourceInfo());
jsCase.setCaseExpr((JsExpression) pop()); // expr
push(jsCase);
}
}
@Override
public void endVisit(JCastOperation x, Context ctx) {
// These are left in when cast checking is disabled.
}
@Override
public void endVisit(JClassLiteral x, Context ctx) {
JsName classLit = names.get(x.getField());
push(classLit.makeRef(x.getSourceInfo()));
}
@Override
public void endVisit(JClassType x, Context ctx) {
if (alreadyRan.contains(x)) {
return;
}
if (program.getTypeClassLiteralHolder() == x) {
// Handled in generateClassLiterals.
return;
}
if (program.immortalCodeGenTypes.contains(x)) {
// Handled in generateImmortalTypes
return;
}
alreadyRan.add(x);
List<JsFunction> jsFuncs = popList(x.getMethods().size()); // methods
List<JsNode> jsFields = popList(x.getFields().size()); // fields
if (x.getClinitTarget() == x) {
JsFunction superClinit = clinitMap.get(x.getSuperClass());
JsFunction myClinit = jsFuncs.get(0);
handleClinit(myClinit, superClinit);
clinitMap.put(x, myClinit);
} else {
jsFuncs.set(0, null);
}
List<JsStatement> globalStmts = jsProgram.getGlobalBlock().getStatements();
// declare all methods into the global scope
for (int i = 0; i < jsFuncs.size(); ++i) {
JsFunction func = jsFuncs.get(i);
// don't add polymorphic JsFuncs, inline decl into vtable assignment
if (func != null && !polymorphicJsFunctions.contains(func)) {
globalStmts.add(func.makeStmt());
}
}
if (typeOracle.isInstantiatedType(x) && !program.isJavaScriptObject(x)) {
generateClassSetup(x, globalStmts);
}
// setup fields
JsVars vars = new JsVars(x.getSourceInfo());
for (int i = 0; i < jsFields.size(); ++i) {
JsNode node = jsFields.get(i);
if (node instanceof JsVar) {
vars.add((JsVar) node);
} else {
assert (node instanceof JsStatement);
JsStatement stmt = (JsStatement) node;
globalStmts.add(stmt);
typeForStatMap.put(stmt, x);
}
}
if (!vars.isEmpty()) {
globalStmts.add(vars);
}
for (JNode node : x.getArtificialRescues()) {
if (node instanceof JMethod) {
JsName jsName = names.get(node);
if (jsName != null) {
JsFunction func = (JsFunction) jsName.getStaticRef();
func.setArtificiallyRescued(true);
}
}
}
// TODO(zundel): Check that each unique method has a unique
// name / poly name.
}
@Override
public void endVisit(JConditional x, Context ctx) {
JsExpression elseExpr = (JsExpression) pop(); // elseExpr
JsExpression thenExpr = (JsExpression) pop(); // thenExpr
JsExpression ifTest = (JsExpression) pop(); // ifTest
push(new JsConditional(x.getSourceInfo(), ifTest, thenExpr, elseExpr));
}
@Override
public void endVisit(JContinueStatement x, Context ctx) {
JsNameRef labelRef = null;
if (x.getLabel() != null) {
JsLabel label = (JsLabel) pop(); // label
labelRef = label.getName().makeRef(x.getSourceInfo());
}
push(new JsContinue(x.getSourceInfo(), labelRef));
}
@Override
public void endVisit(JDebuggerStatement x, Context ctx) {
push(new JsDebugger(x.getSourceInfo()));
}
@Override
public void endVisit(JDeclarationStatement x, Context ctx) {
if (x.getInitializer() == null) {
pop(); // variableRef
/*
* Declaration statements can only appear in blocks, so it's okay to
* push null instead of an empty statement
*/
push(null);
return;
}
JsExpression initializer = (JsExpression) pop(); // initializer
JsNameRef localRef = (JsNameRef) pop(); // localRef
JVariable target = x.getVariableRef().getTarget();
if (target instanceof JField) {
JField field = (JField) target;
if (initializeAtTopScope(field)) {
// Will initialize at top scope; no need to double-initialize.
push(null);
return;
}
}
JsBinaryOperation binOp =
new JsBinaryOperation(x.getSourceInfo(), JsBinaryOperator.ASG, localRef, initializer);
push(binOp.makeStmt());
}
@Override
public void endVisit(JDoStatement x, Context ctx) {
JsDoWhile stmt = new JsDoWhile(x.getSourceInfo());
if (x.getBody() != null) {
stmt.setBody((JsStatement) pop()); // body
} else {
stmt.setBody(new JsEmpty(x.getSourceInfo()));
}
stmt.setCondition((JsExpression) pop()); // testExpr
push(stmt);
}
@Override
public void endVisit(JExpressionStatement x, Context ctx) {
JsExpression expr = (JsExpression) pop(); // expr
push(expr.makeStmt());
}
@Override
public void endVisit(JField x, Context ctx) {
// if we need an initial value, create an assignment
if (initializeAtTopScope(x)) {
// setup the constant value
accept(x.getLiteralInitializer());
} else if (x.getEnclosingType() == program.getTypeJavaLangObject()) {
// Special fields whose initialization is done somewhere else.
push(null);
} else if (x.getType().getDefaultValue() == JNullLiteral.INSTANCE) {
// Fields whose default value is null are left uninitialized and will
// have a JS value of undefined.
push(null);
} else {
// setup the default value, see Issue 380
accept(x.getType().getDefaultValue());
}
JsExpression rhs = (JsExpression) pop();
JsName name = names.get(x);
if (program.getIndexedFields().contains(x)) {
indexedFields =
Maps.put(indexedFields, x.getEnclosingType().getShortName() + "." + x.getName(), name);
}
if (x.isStatic()) {
// setup a var for the static
JsVar var = new JsVar(x.getSourceInfo(), name);
var.setInitExpr(rhs);
push(var);
} else {
// for non-statics, only setup an assignment if needed
if (rhs != null) {
JsNameRef fieldRef = name.makeRef(x.getSourceInfo());
fieldRef.setQualifier(globalTemp.makeRef(x.getSourceInfo()));
JsExpression asg = createAssignment(fieldRef, rhs);
push(new JsExprStmt(x.getSourceInfo(), asg));
} else {
push(null);
}
}
}
@Override
public void endVisit(JFieldRef x, Context ctx) {
JField field = x.getField();
JsName jsFieldName = names.get(field);
JsNameRef nameRef = jsFieldName.makeRef(x.getSourceInfo());
JsExpression curExpr = nameRef;
/*
* Note: the comma expressions here would cause an illegal tree state if
* the result expression ended up on the lhs of an assignment. A hack in
* in endVisit(JBinaryOperation) rectifies the situation.
*/
// See if we need a clinit
JsInvocation jsInvocation = maybeCreateClinitCall(field);
if (jsInvocation != null) {
curExpr = createCommaExpression(jsInvocation, curExpr);
}
if (x.getInstance() != null) {
JsExpression qualifier = (JsExpression) pop();
if (field.isStatic()) {
// unnecessary qualifier, create a comma expression
curExpr = createCommaExpression(qualifier, curExpr);
} else {
// necessary qualifier, qualify the name ref
nameRef.setQualifier(qualifier);
}
}
push(curExpr);
}
@Override
public void endVisit(JForStatement x, Context ctx) {
JsFor jsFor = new JsFor(x.getSourceInfo());
// body
if (x.getBody() != null) {
jsFor.setBody((JsStatement) pop());
} else {
jsFor.setBody(new JsEmpty(x.getSourceInfo()));
}
// increments
if (x.getIncrements() != null) {
jsFor.setIncrExpr((JsExpression) pop());
}
// condition
if (x.getCondition() != null) {
jsFor.setCondition((JsExpression) pop());
}
// initializers
JsExpression initExpr = null;
List<JsExprStmt> initStmts = popList(x.getInitializers().size());
for (int i = 0; i < initStmts.size(); ++i) {
JsExprStmt initStmt = initStmts.get(i);
if (initStmt != null) {
initExpr = createCommaExpression(initExpr, initStmt.getExpression());
}
}
jsFor.setInitExpr(initExpr);
push(jsFor);
}
@Override
public void endVisit(JGwtCreate x, Context ctx) {
throw new InternalCompilerException("Should not get here.");
}
@Override
public void endVisit(JIfStatement x, Context ctx) {
JsIf stmt = new JsIf(x.getSourceInfo());
if (x.getElseStmt() != null) {
stmt.setElseStmt((JsStatement) pop()); // elseStmt
}
if (x.getThenStmt() != null) {
stmt.setThenStmt((JsStatement) pop()); // thenStmt
} else {
stmt.setThenStmt(new JsEmpty(x.getSourceInfo()));
}
stmt.setIfExpr((JsExpression) pop()); // ifExpr
push(stmt);
}
@Override
public void endVisit(JInstanceOf x, Context ctx) {
throw new InternalCompilerException("Should not get here.");
}
@Override
public void endVisit(JInterfaceType x, Context ctx) {
List<JsFunction> jsFuncs = popList(x.getMethods().size()); // methods
List<JsVar> jsFields = popList(x.getFields().size()); // fields
List<JsStatement> globalStmts = jsProgram.getGlobalBlock().getStatements();
if (x.getClinitTarget() == x) {
JsFunction clinitFunc = jsFuncs.get(0);
handleClinit(clinitFunc, null);
globalStmts.add(clinitFunc.makeStmt());
}
// setup fields
JsVars vars = new JsVars(x.getSourceInfo());
for (int i = 0; i < jsFields.size(); ++i) {
vars.add(jsFields.get(i));
}
if (!vars.isEmpty()) {
globalStmts.add(vars);
}
}
@Override
public void endVisit(JLabel x, Context ctx) {
push(new JsLabel(x.getSourceInfo(), names.get(x)));
}
@Override
public void endVisit(JLabeledStatement x, Context ctx) {
JsStatement body = (JsStatement) pop(); // body
JsLabel label = (JsLabel) pop(); // label
label.setStmt(body);
push(label);
}
@Override
public void endVisit(JLocal x, Context ctx) {
push(names.get(x).makeRef(x.getSourceInfo()));
}
@Override
public void endVisit(JLocalRef x, Context ctx) {
push(names.get(x.getTarget()).makeRef(x.getSourceInfo()));
}
@Override
public void endVisit(JLongLiteral x, Context ctx) {
super.endVisit(x, ctx);
JsExpression longLiteralAllocation = pop();
// My seed function name
String nameString = Long.toString(x.getValue(), 16);
if (nameString.charAt(0) == '-') {
nameString = "N" + nameString.substring(1);
} else {
nameString = "P" + nameString;
}
nameString += "_longLit";
JsName longLit = topScope.declareName(nameString);
longLits.put(x.getValue(), longLit);
longObjects.put(longLit, longLiteralAllocation);
push(longLit.makeRef(x.getSourceInfo()));
}
@Override
public void endVisit(JMethod x, Context ctx) {
if (x.isAbstract()) {
push(null);
return;
}
JsFunction jsFunc = (JsFunction) pop(); // body
// Collect the resulting function to be considered by the JsInliner.
if (methodsForJsInlining.contains(x)) {
functionsForJsInlining.add(jsFunc);
}
List<JsParameter> params = popList(x.getParams().size()); // params
if (!x.isNative()) {
// Setup params on the generated function. A native method already got
// its jsParams set in BuildTypeMap.
// TODO: Do we really need to do that in BuildTypeMap?
List<JsParameter> jsParams = jsFunc.getParameters();
for (int i = 0; i < params.size(); ++i) {
JsParameter param = params.get(i);
jsParams.add(param);
}
}
JsInvocation jsInvocation = maybeCreateClinitCall(x);
if (jsInvocation != null) {
jsFunc.getBody().getStatements().add(0, jsInvocation.makeStmt());
}
if (x.isTrace()) {
jsFunc.setTrace();
}
push(jsFunc);
Integer entryIndex = entryMethodToIndex.get(x);
if (entryIndex != null) {
entryFunctions[entryIndex] = jsFunc;
}
currentMethod = null;
}
@Override
public void endVisit(JMethodBody x, Context ctx) {
JsBlock body = (JsBlock) pop();
List<JsNameRef> locals = popList(x.getLocals().size()); // locals
JsFunction jsFunc = methodBodyMap.get(x);
jsFunc.setBody(body); // 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(x.getSourceInfo());
Set<String> alreadySeen = new HashSet<String>();
for (int i = 0; i < locals.size(); ++i) {
JsName name = names.get(x.getLocals().get(i));
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(x.getSourceInfo(), name));
}
}
if (!vars.isEmpty()) {
jsFunc.getBody().getStatements().add(0, vars);
}
push(jsFunc);
}
@Override
public void endVisit(JMethodCall x, Context ctx) {
JMethod method = x.getTarget();
JsInvocation jsInvocation = new JsInvocation(x.getSourceInfo());
popList(jsInvocation.getArguments(), x.getArgs().size()); // args
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) {
if (x.getInstance() != null) {
pop(); // instance
}
// generate a null expression, which will get optimized out
push(JsNullLiteral.INSTANCE);
return;
} else if (type != clinitTarget) {
// replace the method with its retargeted clinit
method = clinitTarget.getClinitMethod();
}
}
JsNameRef qualifier;
JsExpression unnecessaryQualifier = null;
if (method.isStatic()) {
if (x.getInstance() != null) {
unnecessaryQualifier = (JsExpression) pop(); // instance
}
qualifier = names.get(method).makeRef(x.getSourceInfo());
} else {
if (x.isStaticDispatchOnly()) {
/*
* Dispatch statically (odd case). This happens when a call that must
* be static is targeting an instance method that could not be
* transformed into a static. Super/this constructor calls work this
* way. Have to use a "call" construct.
*/
JsName callName = objectScope.declareName("call");
callName.setObfuscatable(false);
qualifier = callName.makeRef(x.getSourceInfo());
qualifier.setQualifier(names.get(method).makeRef(x.getSourceInfo()));
jsInvocation.getArguments().add(0, (JsExpression) pop()); // instance
} else {
// Dispatch polymorphically (normal case).
qualifier = polymorphicNames.get(method).makeRef(x.getSourceInfo());
qualifier.setQualifier((JsExpression) pop()); // instance
}
}
jsInvocation.setQualifier(qualifier);
push(createCommaExpression(unnecessaryQualifier, jsInvocation));
}
@Override
public void endVisit(JMultiExpression x, Context ctx) {
List<JsExpression> exprs = popList(x.getNumberOfExpressions());
JsExpression cur = null;
for (int i = 0; i < exprs.size(); ++i) {
JsExpression next = exprs.get(i);
cur = createCommaExpression(cur, next);
}
if (cur == null) {
// the multi-expression was empty; use undefined
cur = new JsNameRef(x.getSourceInfo(), JsRootScope.INSTANCE.getUndefined());
}
push(cur);
}
@Override
public void endVisit(JNameOf x, Context ctx) {
JsName name = names.get(x.getNode());
assert name != null : "Missing JsName for " + x.getNode().getName();
push(new JsNameOf(x.getSourceInfo(), name));
}
@Override
public void endVisit(JNewArray x, Context ctx) {
throw new InternalCompilerException("Should not get here.");
}
@Override
public void endVisit(JNewInstance x, Context ctx) {
JsNameRef nameRef = names.get(x.getTarget()).makeRef(x.getSourceInfo());
JsNew newOp = new JsNew(x.getSourceInfo(), nameRef);
popList(newOp.getArguments(), x.getArgs().size()); // args
push(newOp);
}
@Override
public void endVisit(JNumericEntry x, Context ctx) {
push(new JsNumericEntry(x.getSourceInfo(), x.getKey(), x.getValue()));
}
@Override
public void endVisit(JParameter x, Context ctx) {
push(new JsParameter(x.getSourceInfo(), names.get(x)));
}
@Override
public void endVisit(JParameterRef x, Context ctx) {
push(names.get(x.getTarget()).makeRef(x.getSourceInfo()));
}
@Override
public void endVisit(JPostfixOperation x, Context ctx) {
JsUnaryOperation op =
new JsPostfixOperation(x.getSourceInfo(), JavaToJsOperatorMap.get(x.getOp()),
((JsExpression) pop())); // arg
push(op);
}
@Override
public void endVisit(JPrefixOperation x, Context ctx) {
JsUnaryOperation op =
new JsPrefixOperation(x.getSourceInfo(), JavaToJsOperatorMap.get(x.getOp()),
((JsExpression) pop())); // arg
push(op);
}
@Override
public void endVisit(JProgram x, Context ctx) {
List<JsStatement> globalStmts = jsProgram.getGlobalBlock().getStatements();
// Generate entry methods
generateGwtOnLoad(Lists.create(entryFunctions), globalStmts);
// Add a few things onto the beginning.
// Reserve the "_" identifier.
JsVars vars = new JsVars(jsProgram.getSourceInfo());
vars.add(new JsVar(jsProgram.getSourceInfo(), globalTemp));
globalStmts.add(0, vars);
// Long lits must go at the top, they can be constant field initializers.
generateLongLiterals(vars);
generateImmortalTypes(vars);
generateQueryIdConstants(vars);
generateInternedCastMapLiterals(vars);
// Class objects, but only if there are any.
if (x.getDeclaredTypes().contains(x.getTypeClassLiteralHolder())) {
// TODO: perhaps they could be constant field initializers also?
vars = new JsVars(jsProgram.getSourceInfo());
generateClassLiterals(vars);
if (!vars.isEmpty()) {
globalStmts.add(vars);
}
}
if (program.getRunAsyncs().size() > 0) {
// Prevent onLoad from being pruned.
JMethod onLoadMethod = program.getIndexedMethod("AsyncFragmentLoader.onLoad");
JsName name = names.get(onLoadMethod);
assert name != null;
JsFunction func = (JsFunction) name.getStaticRef();
func.setArtificiallyRescued(true);
}
}
@Override
public void endVisit(JReboundEntryPoint x, Context ctx) {
throw new InternalCompilerException("Should not get here.");
}
@Override
public void endVisit(JReturnStatement x, Context ctx) {
if (x.getExpr() != null) {
push(new JsReturn(x.getSourceInfo(), (JsExpression) pop())); // expr
} else {
push(new JsReturn(x.getSourceInfo()));
}
}
@Override
public void endVisit(JSeedIdOf x, Context ctx) {
JsName name = names.get(x.getNode());
push(new JsSeedIdOf(x.getSourceInfo(), name, getSeedId((JReferenceType) x.getNode())));
}
@Override
public void endVisit(JsCastMap x, Context ctx) {
super.endVisit(x, ctx);
JsArrayLiteral arrayLit = (JsArrayLiteral) pop();
SourceInfo sourceInfo = x.getSourceInfo();
if (namesByQueryId == null || x.getExprs().size() == 0) {
String stringMap = castMapToString(x);
// if interned, use variable reference
if (namesByCastMap.containsKey(stringMap)) {
push(namesByCastMap.get(stringMap).makeRef(x.getSourceInfo()));
} else if (internedCastMap.contains(stringMap)) {
// interned variable hasn't been created yet
String internName = "CM$";
boolean first = true;
for (JExpression expr : x.getExprs()) {
if (first) {
first = false;
} else {
internName += "_";
}
// Name is CM$queryId_queryId_queryId
internName += ((JsQueryType) expr).getQueryId();
}
JsName internedCastMapName = topScope.declareName(internName, internName);
namesByCastMap.put(stringMap, internedCastMapName);
castMapByString.put(stringMap, castMapToObjectLiteral(arrayLit, sourceInfo));
push(internedCastMapName.makeRef(x.getSourceInfo()));
} else {
push(castMapToObjectLiteral(arrayLit, sourceInfo));
}
} else {
// makeMap([Q_Object, Q_Foo, Q_Bar]);
JsInvocation inv = new JsInvocation(sourceInfo);
inv.setQualifier(makeMapFunction.getName().makeRef(sourceInfo));
inv.getArguments().add(arrayLit);
push(inv);
}
}
@Override
public void endVisit(JsniMethodRef x, Context ctx) {
JMethod method = x.getTarget();
JsNameRef nameRef = names.get(method).makeRef(x.getSourceInfo());
push(nameRef);
}
@Override
public void endVisit(JsonArray x, Context ctx) {
JsArrayLiteral jsArrayLiteral = new JsArrayLiteral(x.getSourceInfo());
popList(jsArrayLiteral.getExpressions(), x.getExprs().size());
push(jsArrayLiteral);
}
@Override
public void endVisit(JsonObject x, Context ctx) {
JsObjectLiteral jsObjectLiteral = new JsObjectLiteral(x.getSourceInfo());
popList(jsObjectLiteral.getPropertyInitializers(), x.propInits.size());
push(jsObjectLiteral);
}
@Override
public void endVisit(JsonPropInit init, Context ctx) {
JsExpression valueExpr = (JsExpression) pop();
JsExpression labelExpr = (JsExpression) pop();
push(new JsPropertyInitializer(init.getSourceInfo(), labelExpr, valueExpr));
}
@Override
public void endVisit(JsQueryType x, Context ctx) {
if (namesByQueryId == null || x.getQueryId() < 0) {
super.endVisit(x, ctx);
} else {
JsName name = namesByQueryId.get(x.getQueryId());
push(name.makeRef(x.getSourceInfo()));
}
}
@Override
public void endVisit(JThisRef x, Context ctx) {
push(new JsThisRef(x.getSourceInfo()));
}
@Override
public void endVisit(JThrowStatement x, Context ctx) {
push(new JsThrow(x.getSourceInfo(), (JsExpression) pop())); // expr
}
@Override
public void endVisit(JTryStatement x, Context ctx) {
JsTry jsTry = new JsTry(x.getSourceInfo());
if (x.getFinallyBlock() != null) {
JsBlock finallyBlock = (JsBlock) pop(); // finallyBlock
if (finallyBlock.getStatements().size() > 0) {
jsTry.setFinallyBlock(finallyBlock);
}
}
int size = x.getCatchClauses().size();
assert (size < 2);
if (size == 1) {
JsBlock catchBlock = (JsBlock) pop(); // catchBlocks
pop(); // catchArgs
JsCatch jsCatch = catchMap.get(x.getCatchClauses().get(0).getBlock());
jsCatch.setBody(catchBlock);
jsTry.getCatches().add(jsCatch);
}
jsTry.setTryBlock((JsBlock) pop()); // tryBlock
push(jsTry);
}
@Override
public void endVisit(JWhileStatement x, Context ctx) {
JsWhile stmt = new JsWhile(x.getSourceInfo());
if (x.getBody() != null) {
stmt.setBody((JsStatement) pop()); // body
} else {
stmt.setBody(new JsEmpty(x.getSourceInfo()));
}
stmt.setCondition((JsExpression) pop()); // testExpr
push(stmt);
}
@Override
public boolean visit(JClassType x, Context ctx) {
if (alreadyRan.contains(x)) {
return false;
}
if (program.getTypeClassLiteralHolder() == x) {
// Handled in generateClassLiterals.
return false;
}
if (program.immortalCodeGenTypes.contains(x)) {
// Handled in generateImmortalTypes
return false;
}
// force super type to generate code first, this is required for prototype
// chaining to work properly
if (x.getSuperClass() != null && !alreadyRan.contains(x)) {
accept(x.getSuperClass());
}
return super.visit(x, ctx);
}
@Override
public boolean visit(JDeclaredType x, Context ctx) {
checkForDupMethods(x);
return true;
}
@Override
public boolean visit(JMethod x, Context ctx) {
if (x.isAbstract()) {
return false;
}
currentMethod = x;
return true;
}
@Override
public boolean visit(JProgram x, Context ctx) {
/*
* Arrange for entryFunctions to be filled in as functions are visited.
* See their Javadoc comments for more details.
*/
List<JMethod> entryMethods = x.getEntryMethods();
entryFunctions = new JsFunction[entryMethods.size()];
entryMethodToIndex = new IdentityHashMap<JMethod, Integer>();
for (int i = 0; i < entryMethods.size(); i++) {
entryMethodToIndex.put(entryMethods.get(i), i);
}
for (JDeclaredType type : x.getDeclaredTypes()) {
if (program.typeOracle.isInstantiatedType(type)) {
internCastMap(program.getCastMap(type));
}
}
return true;
}
@Override
public boolean visit(JsniMethodBody x, Context ctx) {
final Map<String, JNode> jsniMap = new HashMap<String, JNode>();
for (JsniClassLiteral ref : x.getClassRefs()) {
jsniMap.put(ref.getIdent(), ref.getField());
}
for (JsniFieldRef ref : x.getJsniFieldRefs()) {
jsniMap.put(ref.getIdent(), ref.getField());
}
for (JsniMethodRef ref : x.getJsniMethodRefs()) {
jsniMap.put(ref.getIdent(), ref.getTarget());
}
final JsFunction jsFunc = x.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) {
// Replace invocation to ctor with a new op.
if (x.getQualifier() instanceof JsNameRef) {
JsNameRef ref = (JsNameRef) x.getQualifier();
String ident = ref.getIdent();
if (isJsniIdent(ident)) {
JNode node = jsniMap.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) {
String ident = x.getIdent();
if (isJsniIdent(ident)) {
JNode node = jsniMap.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 = 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, jsFunc.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 = nullFunc.getName();
}
x.resolve(jsName);
}
}
}
}
@Override
public boolean visit(JsInvocation x, JsContext ctx) {
if (x.getQualifier() instanceof JsNameRef) {
dontReplaceCtor = (JsNameRef) x.getQualifier();
}
return true;
}
private boolean isJsniIdent(String ident) {
return ident.charAt(0) == '@';
}
}.accept(jsFunc);
push(jsFunc);
// Do NOT visit JsniMethodRefs/JsniFieldRefs.
return false;
}
@Override
public boolean visit(JSwitchStatement x, Context ctx) {
/*
* What a pain.. JSwitchStatement and JsSwitch are modeled completely
* differently. Here we try to resolve those differences.
*/
JsSwitch jsSwitch = new JsSwitch(x.getSourceInfo());
accept(x.getExpr());
jsSwitch.setExpr((JsExpression) pop()); // expr
List<JStatement> bodyStmts = x.getBody().getStatements();
if (bodyStmts.size() > 0) {
List<JsStatement> curStatements = null;
for (int i = 0; i < bodyStmts.size(); ++i) {
JStatement stmt = bodyStmts.get(i);
accept(stmt);
if (stmt instanceof JCaseStatement) {
// create a new switch member
JsSwitchMember switchMember = (JsSwitchMember) pop(); // stmt
jsSwitch.getCases().add(switchMember);
curStatements = switchMember.getStmts();
} else {
// add to statements for current case
assert (curStatements != null);
JsStatement newStmt = (JsStatement) pop(); // stmt
if (newStmt != null) {
// Empty JDeclarationStatement produces a null
curStatements.add(newStmt);
}
}
}
}
push(jsSwitch);
return false;
}
private JsObjectLiteral castMapToObjectLiteral(JsArrayLiteral arrayLit, SourceInfo sourceInfo) {
JsObjectLiteral objLit = new JsObjectLiteral(sourceInfo);
List<JsPropertyInitializer> props = objLit.getPropertyInitializers();
JsNumberLiteral one = new JsNumberLiteral(sourceInfo, 1);
for (JsExpression expr : arrayLit.getExpressions()) {
JsPropertyInitializer prop = new JsPropertyInitializer(sourceInfo, expr, one);
props.add(prop);
}
return objLit;
}
private void checkForDupMethods(JDeclaredType x) {
// Sanity check to see that all methods are uniquely named.
List<JMethod> methods = x.getMethods();
Set<String> methodSignatures = Sets.create();
for (JMethod method : methods) {
String sig = method.getSignature();
if (methodSignatures.contains(sig)) {
throw new InternalCompilerException("Signature collision in Type " + x.getName()
+ " for method " + sig);
}
methodSignatures = Sets.add(methodSignatures, sig);
}
}
private JsExpression createAssignment(JsExpression lhs, JsExpression rhs) {
return new JsBinaryOperation(lhs.getSourceInfo(), JsBinaryOperator.ASG, lhs, rhs);
}
private JsExpression createCommaExpression(JsExpression lhs, JsExpression rhs) {
if (lhs == null) {
return rhs;
} else if (rhs == null) {
return lhs;
}
return new JsBinaryOperation(lhs.getSourceInfo(), JsBinaryOperator.COMMA, lhs, rhs);
}
private JsNameRef createNativeToStringRef(JsExpression qualifier) {
JsName toStringName = objectScope.declareName("toString");
toStringName.setObfuscatable(false);
JsNameRef toStringRef = toStringName.makeRef(qualifier.getSourceInfo());
toStringRef.setQualifier(qualifier);
return toStringRef;
}
private JsExpression generateCastableTypeMap(JClassType x) {
JsCastMap castMap = program.getCastMap(x);
if (castMap != null) {
JField castableTypeMapField = program.getIndexedField("Object.castableTypeMap");
JsName castableTypeMapName = names.get(castableTypeMapField);
if (castableTypeMapName == null) {
// Was pruned; this compilation must have no dynamic casts.
return new JsObjectLiteral(SourceOrigin.UNKNOWN);
}
accept(castMap);
return (JsExpression) pop();
}
return new JsObjectLiteral(SourceOrigin.UNKNOWN);
}
private void generateClassLiteral(JDeclarationStatement decl, JsVars vars) {
JField field = (JField) decl.getVariableRef().getTarget();
JsName jsName = names.get(field);
this.accept(decl.getInitializer());
JsExpression classObjectAlloc = pop();
JsVar var = new JsVar(decl.getSourceInfo(), jsName);
var.setInitExpr(classObjectAlloc);
vars.add(var);
}
private void generateClassLiterals(JsVars vars) {
/*
* 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) {
generateClassLiteral((JDeclarationStatement) stmt, vars);
}
}
}
private void generateClassSetup(JClassType x, List<JsStatement> globalStmts) {
generateSeedFuncAndPrototype(x, globalStmts);
generateVTables(x, globalStmts);
if (x == program.getTypeJavaLangObject()) {
// special: setup a "toString" alias for java.lang.Object.toString()
generateToStringAlias(x, globalStmts);
// special: setup the identifying typeMarker field
generateTypeMarker(globalStmts);
}
}
private void generateGwtOnLoad(List<JsFunction> entryFuncs, List<JsStatement> globalStmts) {
/**
* <pre>
* var $entry = Impl.registerEntry();
* function gwtOnLoad(errFn, modName, modBase, softPermutationId){
* $moduleName = modName;
* $moduleBase = modBase;
* CollapsedPropertyHolder.permutationId = softPermutationId;
* if (errFn) {
* try {
* $entry(init)();
* } catch(e) {
* errFn(modName);
* }
* } else {
* $entry(init)();
* }
* }
* </pre>
*/
SourceInfo sourceInfo = SourceOrigin.UNKNOWN;
JsName entryName = topScope.declareName("$entry");
JsVar entryVar = new JsVar(sourceInfo, entryName);
JsInvocation registerEntryCall = new JsInvocation(sourceInfo);
JsFunction registerEntryFunction = indexedFunctions.get("Impl.registerEntry");
registerEntryCall.setQualifier(registerEntryFunction.getName().makeRef(sourceInfo));
entryVar.setInitExpr(registerEntryCall);
JsVars entryVars = new JsVars(sourceInfo);
entryVars.add(entryVar);
globalStmts.add(entryVars);
JsName gwtOnLoadName = topScope.declareName("gwtOnLoad");
gwtOnLoadName.setObfuscatable(false);
JsFunction gwtOnLoad = new JsFunction(sourceInfo, topScope, gwtOnLoadName, true);
gwtOnLoad.setArtificiallyRescued(true);
globalStmts.add(gwtOnLoad.makeStmt());
JsBlock body = new JsBlock(sourceInfo);
gwtOnLoad.setBody(body);
JsScope fnScope = gwtOnLoad.getScope();
List<JsParameter> params = gwtOnLoad.getParameters();
JsName errFn = fnScope.declareName("errFn");
JsName modName = fnScope.declareName("modName");
JsName modBase = fnScope.declareName("modBase");
JsName softPermutationId = fnScope.declareName("softPermutationId");
params.add(new JsParameter(sourceInfo, errFn));
params.add(new JsParameter(sourceInfo, modName));
params.add(new JsParameter(sourceInfo, modBase));
params.add(new JsParameter(sourceInfo, softPermutationId));
JsExpression asg =
createAssignment(topScope.findExistingUnobfuscatableName("$moduleName").makeRef(
sourceInfo), modName.makeRef(sourceInfo));
body.getStatements().add(asg.makeStmt());
asg =
createAssignment(topScope.findExistingUnobfuscatableName("$moduleBase").makeRef(
sourceInfo), modBase.makeRef(sourceInfo));
body.getStatements().add(asg.makeStmt());
// Assignment to CollapsedPropertyHolder.permutationId only if it's used
JsName permutationIdFieldName =
names.get(program.getIndexedField("CollapsedPropertyHolder.permutationId"));
if (permutationIdFieldName != null) {
asg =
createAssignment(permutationIdFieldName.makeRef(sourceInfo), softPermutationId
.makeRef(sourceInfo));
body.getStatements().add(asg.makeStmt());
}
JsIf jsIf = new JsIf(sourceInfo);
body.getStatements().add(jsIf);
jsIf.setIfExpr(errFn.makeRef(sourceInfo));
JsTry jsTry = new JsTry(sourceInfo);
jsIf.setThenStmt(jsTry);
JsBlock callBlock = new JsBlock(sourceInfo);
jsIf.setElseStmt(callBlock);
jsTry.setTryBlock(callBlock);
for (JsFunction func : entryFuncs) {
if (func == registerEntryFunction) {
continue;
} else if (func != null) {
JsInvocation call = new JsInvocation(sourceInfo);
call.setQualifier(entryName.makeRef(sourceInfo));
call.getArguments().add(func.getName().makeRef(sourceInfo));
JsInvocation entryCall = new JsInvocation(sourceInfo);
entryCall.setQualifier(call);
callBlock.getStatements().add(entryCall.makeStmt());
}
}
JsCatch jsCatch = new JsCatch(sourceInfo, fnScope, "e");
jsTry.getCatches().add(jsCatch);
JsBlock catchBlock = new JsBlock(sourceInfo);
jsCatch.setBody(catchBlock);
JsInvocation errCall = new JsInvocation(sourceInfo);
catchBlock.getStatements().add(errCall.makeStmt());
errCall.setQualifier(errFn.makeRef(sourceInfo));
errCall.getArguments().add(modName.makeRef(sourceInfo));
}
private void generateImmortalTypes(JsVars globals) {
List<JsStatement> globalStmts = jsProgram.getGlobalBlock().getStatements();
List<JClassType> immortalTypes = new ArrayList<JClassType>(
program.immortalCodeGenTypes);
// visit in reverse order since insertions start at head
Collections.reverse(immortalTypes);
JMethod createObjMethod = program.getIndexedMethod("JavaScriptObject.createObject");
JMethod createArrMethod = program.getIndexedMethod("JavaScriptObject.createArray");
for (JClassType x : immortalTypes) {
// 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 synthensized $init() method. We must skip both of these inserted methods.
*/
if (method.needsVtable() || method instanceof JConstructor) {
continue;
}
if (JProgram.isClinit(method)) {
/**
* Emit empty clinits that will be pruned. If a type B extends A, then even if
* B and A have no fields to initialize, there will be a call inserted in B's clinit
* to invoke A's clinit. Likewise, if you have a static field initialized to
* JavaScriptObject.createObject(), the clinit() will include this initializer code,
* which we don't want.
*/
JsFunction func = new JsFunction(x.getSourceInfo(), topScope,
topScope.declareName(mangleNameForGlobal(method)), true);
func.setBody(new JsBlock(method.getBody().getSourceInfo()));
push(func);
} else {
accept(method);
}
// add after var declaration, but before everything else
JsFunction func = (JsFunction) pop();
assert func.getName() != null;
globalStmts.add(1, func.makeStmt());
}
// insert fields into global var declaration
for (JField field : x.getFields()) {
assert field.isStatic() : "All fields on immortal types must be static.";
accept(field);
JsNode node = pop();
assert node instanceof JsVar;
JsVar fieldVar = (JsVar) node;
JExpression init = field.getInitializer();
if (init != null
&& field.getLiteralInitializer() == null) {
// no literal, but it could be a JavaScriptObject
if (init.getType() == program.getJavaScriptObject()) {
assert init instanceof JMethodCall;
JMethod meth = ((JMethodCall) init).getTarget();
// immortal types can only have non-primitive literal initializers of createArray,createObject
if (meth == createObjMethod) {
fieldVar.setInitExpr(new JsObjectLiteral(init.getSourceInfo()));
} else if (meth == createArrMethod) {
fieldVar.setInitExpr(new JsArrayLiteral(init.getSourceInfo()));
} else {
assert false : "Illegal initializer expression for immortal field " + field;
}
}
}
globals.add(fieldVar);
}
}
}
private void generateInternedCastMapLiterals(JsVars vars) {
SourceInfo info = vars.getSourceInfo();
int id = 0;
for (Map.Entry<String, JsName> castMapEntry : namesByCastMap.entrySet()) {
JsVar var = new JsVar(info, castMapEntry.getValue());
var.setInitExpr(castMapByString.get(castMapEntry.getKey()));
vars.add(var);
}
}
private void generateLongLiterals(JsVars vars) {
for (Entry<Long, JsName> entry : longLits.entrySet()) {
JsName jsName = entry.getValue();
JsExpression longObjectAlloc = longObjects.get(jsName);
JsVar var = new JsVar(vars.getSourceInfo(), jsName);
var.setInitExpr(longObjectAlloc);
vars.add(var);
}
}
private void generateQueryIdConstants(JsVars vars) {
if (namesByQueryId != null) {
SourceInfo info = vars.getSourceInfo();
int id = 0;
for (JsName jsName : namesByQueryId) {
JsVar var = new JsVar(info, jsName);
var.setInitExpr(new JsNumberLiteral(info, id++));
vars.add(var);
}
}
}
private void generateSeedFuncAndPrototype(JClassType x, List<JsStatement> globalStmts) {
SourceInfo sourceInfo = x.getSourceInfo();
if (x != program.getTypeJavaLangString()) {
JsInvocation defineSeed = new JsInvocation(x.getSourceInfo());
JsName seedNameRef = indexedFunctions.get(
"SeedUtil.defineSeed").getName();
defineSeed.setQualifier(seedNameRef.makeRef(x.getSourceInfo()));
int newSeed = getSeedId(x);
assert newSeed > 0;
JClassType superClass = x.getSuperClass();
int superSeed = (superClass == null) ? -1 : getSeedId(x.getSuperClass());
// SeedUtil.defineSeed(queryId, superId, castableMap, constructors)
defineSeed.getArguments().add(new JsNumberLiteral(x.getSourceInfo(),
newSeed));
defineSeed.getArguments().add(new JsNumberLiteral(x.getSourceInfo(),
superSeed));
JsExpression castMap = generateCastableTypeMap(x);
defineSeed.getArguments().add(castMap);
// Chain assign the same prototype to every live constructor.
for (JMethod method : x.getMethods()) {
if (liveCtors.contains(method)) {
defineSeed.getArguments().add(names.get(method).makeRef(
sourceInfo));
}
}
JsStatement tmpAsgStmt = defineSeed.makeStmt();
globalStmts.add(tmpAsgStmt);
typeForStatMap.put(tmpAsgStmt, x);
} else {
/*
* MAGIC: java.lang.String is implemented as a JavaScript String
* primitive with a modified prototype.
*/
JsNameRef rhs = prototype.makeRef(sourceInfo);
rhs.setQualifier(JsRootScope.INSTANCE.findExistingUnobfuscatableName("String").makeRef(
sourceInfo));
JsExpression tmpAsg = createAssignment(globalTemp.makeRef(sourceInfo), rhs);
JsExprStmt tmpAsgStmt = tmpAsg.makeStmt();
globalStmts.add(tmpAsgStmt);
typeForStatMap.put(tmpAsgStmt, x);
JField castableTypeMapField = program.getIndexedField("Object.castableTypeMap");
JsName castableTypeMapName = names.get(castableTypeMapField);
JsNameRef ctmRef = castableTypeMapName.makeRef(sourceInfo);
ctmRef.setQualifier(globalTemp.makeRef(sourceInfo));
JsExpression castMapLit = generateCastableTypeMap(x);
JsExpression ctmAsg = createAssignment(ctmRef,
castMapLit);
JsExprStmt ctmAsgStmt = ctmAsg.makeStmt();
globalStmts.add(ctmAsgStmt);
typeForStatMap.put(ctmAsgStmt, x);
}
}
private void generateToStringAlias(JClassType x, List<JsStatement> globalStmts) {
JMethod toStringMeth = program.getIndexedMethod("Object.toString");
if (x.getMethods().contains(toStringMeth)) {
SourceInfo sourceInfo = x.getSourceInfo();
// _.toString = function(){return this.java_lang_Object_toString();}
// lhs
JsNameRef lhs = createNativeToStringRef(globalTemp.makeRef(sourceInfo));
// rhs
JsInvocation call = new JsInvocation(sourceInfo);
JsNameRef toStringRef = new JsNameRef(sourceInfo, polymorphicNames.get(toStringMeth));
toStringRef.setQualifier(new JsThisRef(sourceInfo));
call.setQualifier(toStringRef);
JsReturn jsReturn = new JsReturn(sourceInfo, call);
JsFunction rhs = new JsFunction(sourceInfo, topScope);
JsBlock body = new JsBlock(sourceInfo);
body.getStatements().add(jsReturn);
rhs.setBody(body);
// asg
JsExpression asg = createAssignment(lhs, rhs);
JsExprStmt stmt = asg.makeStmt();
globalStmts.add(stmt);
typeForStatMap.put(stmt, program.getTypeJavaLangObject());
}
}
private void generateTypeMarker(List<JsStatement> globalStmts) {
JField typeMarkerField = program.getIndexedField("Object.typeMarker");
JsName typeMarkerName = names.get(typeMarkerField);
if (typeMarkerName == null) {
// Was pruned; this compilation must have no JSO instanceof tests.
return;
}
SourceInfo sourceInfo = jsProgram.createSourceInfoSynthetic(GenerateJavaScriptAST.class);
JsNameRef fieldRef = typeMarkerName.makeRef(sourceInfo);
fieldRef.setQualifier(globalTemp.makeRef(sourceInfo));
JsExpression asg = createAssignment(fieldRef, nullFunc.getName().makeRef(sourceInfo));
JsExprStmt stmt = asg.makeStmt();
globalStmts.add(stmt);
typeForStatMap.put(stmt, program.getTypeJavaLangObject());
}
private void generateVTables(JClassType x, List<JsStatement> globalStmts) {
boolean isString = (x == program.getTypeJavaLangString());
for (JMethod method : x.getMethods()) {
SourceInfo sourceInfo = method.getSourceInfo();
if (method.needsVtable() && !method.isAbstract()) {
JsNameRef lhs = polymorphicNames.get(method).makeRef(sourceInfo);
lhs.setQualifier(globalTemp.makeRef(sourceInfo));
JsExpression rhs;
if (isString && "toString".equals(method.getName())) {
// special-case String.toString: alias to the native JS toString()
rhs = createNativeToStringRef(globalTemp.makeRef(sourceInfo));
} else {
/*
* Inline JsFunction rather than reference, e.g. _.vtableName =
* function functionName() { ... }
*/
rhs = methodBodyMap.get(method.getBody());
}
JsExpression asg = createAssignment(lhs, rhs);
JsExprStmt asgStat = new JsExprStmt(x.getSourceInfo(), asg);
globalStmts.add(asgStat);
vtableInitForMethodMap.put(asgStat, method);
}
}
}
private void handleClinit(JsFunction clinitFunc, JsFunction superClinit) {
clinitFunc.setExecuteOnce(true);
clinitFunc.setImpliedExecute(superClinit);
List<JsStatement> statements = clinitFunc.getBody().getStatements();
SourceInfo sourceInfo = clinitFunc.getSourceInfo();
// self-assign to the null method immediately (to prevent reentrancy)
JsExpression asg =
createAssignment(clinitFunc.getName().makeRef(sourceInfo), nullFunc.getName().makeRef(
sourceInfo));
statements.add(0, asg.makeStmt());
}
private void internCastMap(JsCastMap x) {
String stringMap = castMapToString(x);
if (castMapSeen.contains(stringMap)) {
internedCastMap.add(stringMap);
} else {
castMapSeen.add(stringMap);
}
}
private JsInvocation maybeCreateClinitCall(JField x) {
if (!x.isStatic()) {
return null;
}
JDeclaredType targetType = x.getEnclosingType().getClinitTarget();
if (!currentMethod.getEnclosingType().checkClinitTo(targetType)) {
return null;
} else if (targetType.equals(program.getTypeClassLiteralHolder())) {
return null;
}
JMethod clinitMethod = targetType.getClinitMethod();
SourceInfo sourceInfo = x.getSourceInfo();
JsInvocation jsInvocation = new JsInvocation(sourceInfo);
jsInvocation.setQualifier(names.get(clinitMethod).makeRef(sourceInfo));
return jsInvocation;
}
private JsInvocation maybeCreateClinitCall(JMethod x) {
if (!crossClassTargets.contains(x)) {
return null;
}
if (x.canBePolymorphic() || program.isStaticImpl(x)) {
return null;
}
JDeclaredType enclosingType = x.getEnclosingType();
if (enclosingType == null || !enclosingType.hasClinit()) {
return null;
}
// avoid recursion sickness
if (JProgram.isClinit(x)) {
return null;
}
JMethod clinitMethod = enclosingType.getClinitTarget().getClinitMethod();
SourceInfo sourceInfo = x.getSourceInfo();
JsInvocation jsInvocation = new JsInvocation(sourceInfo);
jsInvocation.setQualifier(names.get(clinitMethod).makeRef(sourceInfo));
return jsInvocation;
}
/**
* 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.
*
* Technically, to match JVM semantics, we should only do this for final or static fields. For
* non-final/non-static fields, a super class's cstr, when it calls a polymorphic method that is