* 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.Lists;
import com.google.gwt.dev.util.collect.Maps;
import com.google.gwt.dev.util.collect.Sets;
import java.io.StringReader;
import java.util.ArrayList;
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 {
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
{
JsExpression incrExpr = null;
List<JsExprStmt> exprStmts = popList(x.getIncrements().size());
for (int i = 0; i < exprStmts.size(); ++i) {
JsExprStmt exprStmt = exprStmts.get(i);
incrExpr = createCommaExpression(incrExpr, exprStmt.getExpression());
}
jsFor.setIncrExpr(incrExpr);
}
// condition
if (x.getTestExpr() != 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.exprs.size());
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);
// setup Array.proto
JsFunction patchFunc = indexedFunctions.get("ArrayPrototypePatcher.patch");
JsName patchFuncName = patchFunc.getName();
JsInvocation callPatchFunc = new JsInvocation(x.getSourceInfo());
callPatchFunc.setQualifier(patchFuncName.makeRef(x.getSourceInfo()));
globalStmts.add(callPatchFunc.makeStmt());
}
}
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
* overridden in the subclass, should actually see default values (not the literal initializer)
* before the subclass's cstr runs.
*
* However, cstr's calling polymorphic methods is admittedly an uncommon case, so we apply some
* heuristics to see if we can initialize the field on the prototype anyway.
*/
private boolean initializeAtTopScope(JField x) {
if (x.getLiteralInitializer() == null) {
return false;
}
if (x.isFinal() || x.isStatic()) {
// we can definitely initialize at top-scope, as JVM does so as well
return true;
}
// if the superclass can observe the field, we need to leave the default value
JDeclaredType current = x.getEnclosingType().getSuperClass();
while (current != null) {
if (canObserveSubclassFields.contains(current)) {
return false;
}
current = current.getSuperClass();
}
// should be safe to initialize at top-scope, as no one can observe the difference
return true;
}
}
private static class JavaToJsOperatorMap {
private static final Map<JBinaryOperator, JsBinaryOperator> bOpMap =
new EnumMap<JBinaryOperator, JsBinaryOperator>(JBinaryOperator.class);
private static final Map<JUnaryOperator, JsUnaryOperator> uOpMap =
new EnumMap<JUnaryOperator, JsUnaryOperator>(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);
}
}
/**
* Determines which classes can potentially see uninitialized values of their subclasses' fields.
*
* If a class can not observe subclass uninitialized fields then the initialization of those could
* be hoisted to the prototype.
*/
private class CanObserveSubclassUninitializedFieldsVisitor extends JVisitor {
private JDeclaredType currentClass;
@Override
public boolean visit(JConstructor x, Context ctx) {
// Only look at constructor bodies.
assert currentClass == null;
currentClass = x.getEnclosingType();
return true;
}
@Override
public void endVisit(JConstructor x, Context ctx) {
currentClass = null;
}
@Override
public boolean visit(JMethod x, Context ctx) {
if (x.getName().equals("$$init")) {
assert currentClass == null;
currentClass = x.getEnclosingType();
return true;
}
// Do not traverse the method body if it is not a constructor.
return false;
}
@Override
public void endVisit(JMethod x, Context ctx) {
currentClass = null;
}
@Override
public void endVisit(JMethodCall x, Context ctx) {
// This is a method call inside a constructor.
assert currentClass != null;
// Calls to this/super constructors are okay, as they will also get examined
if (x.getTarget().isConstructor() && x.getInstance() instanceof JThisRef) {
return;
}
// Calls to the instance initializer are okay, as execution will not escape it
if (x.getTarget().getName().equals("$$init")) {
return;
}
// Calls to static methods with no arguments are safe, because they have no
// way to trace back into our new instance.
if (x.getTarget().isStatic() && x.getTarget().getOriginalParamTypes().size() == 0) {
return;
}
// Technically we could get fancier about trying to track the "this" reference
// through other variable assignments, field assignments, and methods calls, to
// see if it calls a polymorphic method against ourself (which would let subclasses
// observe their fields), but that gets tricky, so we'll use the above heuristics
// for now.
canObserveSubclassFields.add(currentClass);
}
}
private class RecordCrossClassCallsAndJSInlinableMethods extends JVisitor {
private JMethod currentMethod;
// Java methods that would have not been already exploited for inline opportunities due to
// either beign nativeor that contain classes to native methods are collected here.
Set<JMethod> methodsForJsInlining = new HashSet<JMethod>();
@Override
public void endVisit(JMethod x, Context ctx) {
if (x.isNative()) {
methodsForJsInlining.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());
}
if (x.getTarget().isNative()
&& ((JsniMethodBody) x.getTarget().getBody()).getFunc().getBody().getStatements().size()
<= JsInliner.MAX_INLINE_FN_SIZE) {
// currentMethod calls a jsni method, currentMethod
// will be consider for JavaScript inlining
methodsForJsInlining.add(currentMethod);
}
}
@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 {
// TODO(rluble): this visitor seems to traverse statements innecessarily.
private final HasNameSort hasNameSort = new HasNameSort();
private final Comparator<JMethod> methodSort = new Comparator<JMethod>() {
@Override
public int compare(JMethod m1, JMethod m2) {
return m1.getSignature().compareTo(m2.getSignature());
}
};
@Override
public void endVisit(JClassType x, Context ctx) {
x.sortFields(hasNameSort);
x.sortMethods(methodSort);
}
@Override
public void endVisit(JInterfaceType x, Context ctx) {
x.sortFields(hasNameSort);
x.sortMethods(methodSort);
}
@Override
public void endVisit(JMethodBody x, Context ctx) {
x.sortLocals(hasNameSort);
}
@Override
public void endVisit(JProgram x, Context ctx) {
Collections.sort(x.getEntryMethods(), methodSort);
Collections.sort(x.getDeclaredTypes(), hasNameSort);
}
}
/**
* 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 program a Java AST
* @param jsProgram an (empty) JavaScript AST
* @param outputOption options that affect this transformation for this transformation
* e.g. OBFUSCATED, etc.
* @param symbolTable an (empty) symbol table that will be populated here
* @param propertyOracles property oracles that correspond to the permutation being compiled.
* @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(JProgram program,
JsProgram jsProgram, JsOutputOption outputOption, Map<StandardSymbolData, JsName> symbolTable,
PropertyOracle[] propertyOracles) {
GenerateJavaScriptAST generateJavaScriptAST =
new GenerateJavaScriptAST(program, jsProgram, outputOption, symbolTable, propertyOracles);
return generateJavaScriptAST.execImpl();
}
private Map<String, JsExpression> castMapByString = new HashMap<String, JsExpression>();
private final Map<JBlock, JsCatch> catchMap = new IdentityHashMap<JBlock, JsCatch>();
private final Set<JsName> catchParamIdentifiers = new HashSet<JsName>();
private final Map<JClassType, JsScope> classScopes = new IdentityHashMap<JClassType, JsScope>();
/**
* A list of methods that are called from another class (ie might need to
* clinit).
*/
private final Set<JMethod> crossClassTargets = new HashSet<JMethod>();
private Map<String, JsFunction> indexedFunctions = Maps.create();
private Map<String, JsName> indexedFields = Maps.create();
/**
* 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 Set<String> internedCastMap = new HashSet<String>();
private final JsProgram jsProgram;
private final Set<JConstructor> liveCtors = new IdentityHashSet<JConstructor>();
/**
* Classes that could potentially see uninitialized values for fields that are initialized in the
* declaration.
*/
private final Set<JDeclaredType> canObserveSubclassFields = new HashSet<JDeclaredType>();
/**
* Sorted to avoid nondeterministic iteration.
*/
private final Map<Long, JsName> longLits = new TreeMap<Long, JsName>();
private final Map<JsName, JsExpression> longObjects = new IdentityHashMap<JsName, JsExpression>();
private JsFunction makeMapFunction;
private final Map<JAbstractMethodBody, JsFunction> methodBodyMap =
new IdentityHashMap<JAbstractMethodBody, JsFunction>();
private final Map<HasName, JsName> names = new IdentityHashMap<HasName, JsName>();
private int nextSeedId = 1;
private List<JsName> namesByQueryId;
private Map<String, JsName> namesByCastMap = new HashMap<String, JsName>();
private JsFunction nullFunc;
/**
* 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 JsOutputOption output;
private final Set<JsFunction> polymorphicJsFunctions = new IdentityHashSet<JsFunction>();
private final Map<JMethod, JsName> polymorphicNames = new IdentityHashMap<JMethod, JsName>();
private final JProgram program;
/**
* Map of class type to allocated seed id.
*/
private final Map<JReferenceType, Integer> seedIdMap = new HashMap<JReferenceType, Integer>();
/**
* All of the fields in String and Array need special handling for interop.
*/
private final Map<JField, String> specialObfuscatedFields = new HashMap<JField, String>();
/**
* All of the methods in String and Array need special handling for interop.
*/
private final Map<String, String> specialObfuscatedMethodSigs = new HashMap<String, String>();
/**
* If true, polymorphic functions are made anonymous vtable declarations and
* not assigned topScope identifiers.
*/
private final boolean stripStack;
/**
* 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, JClassType> typeForStatMap =
new HashMap<JsStatement, JClassType>();
private final JTypeOracle typeOracle;
private final Map<JsStatement, JMethod> vtableInitForMethodMap =
new HashMap<JsStatement, JMethod>();
private GenerateJavaScriptAST(JProgram program, JsProgram jsProgram, JsOutputOption output,
Map<StandardSymbolData, JsName> symbolTable, PropertyOracle[] propertyOracles) {
this.program = program;
typeOracle = program.typeOracle;
this.jsProgram = jsProgram;
topScope = jsProgram.getScope();
objectScope = jsProgram.getObjectScope();
interfaceScope = new JsNormalScope(objectScope, "Interfaces");
this.output = output;
this.symbolTable = symbolTable;
this.stripStack =
JsStackEmulator.getStackMode(propertyOracles) == JsStackEmulator.StackMode.STRIP;
/*
* Because we modify the JavaScript String prototype, all fields and
* polymorphic methods on String and super types need special handling.
*/
// Object polymorphic
Map<String, String> namesToIdents = new HashMap<String, String>();
namesToIdents.put("clone", "cL");
namesToIdents.put("getClass", "gC");
namesToIdents.put("hashCode", "hC");
namesToIdents.put("equals", "eQ");
namesToIdents.put("toString", "tS");
namesToIdents.put("finalize", "fZ");
// String polymorphic
namesToIdents.put("charAt", "cA");
namesToIdents.put("compareTo", "cT");
namesToIdents.put("length", "lN");
namesToIdents.put("subSequence", "sS");
List<JMethod> methods = new ArrayList<JMethod>(program.getTypeJavaLangObject().getMethods());
methods.addAll(program.getIndexedType("Comparable").getMethods());
methods.addAll(program.getIndexedType("CharSequence").getMethods());
for (JMethod method : methods) {
if (method.canBePolymorphic()) {
String ident = namesToIdents.get(method.getName());
assert ident != null;
specialObfuscatedMethodSigs.put(method.getSignature(), ident);
}
}
namesToIdents.clear();
// Object fields
namesToIdents.put("expando", "eX");
namesToIdents.put("typeMarker", "tM");
namesToIdents.put("castableTypeMap", "cM");
namesToIdents.put("___clazz", "cZ");
// Array magic field
namesToIdents.put("queryId", "qI");
List<JField> fields = new ArrayList<JField>(program.getTypeJavaLangObject().getFields());
fields.addAll(program.getIndexedType("Array").getFields());
for (JField field : fields) {
if (!field.isStatic()) {
String ident = namesToIdents.get(field.getName());
assert ident != null;
specialObfuscatedFields.put(field, ident);
}
}
// force java.lang.Object,java.lang.String
// to have seed ids 1,2
getSeedId(program.getTypeJavaLangObject());
getSeedId(program.getTypeJavaLangString());
}
String castMapToString(JsCastMap x) {
if (x == null || x.getExprs() == null || x.getExprs().size() == 0) {
return "{}";
} else {
TextOutput textOutput = new DefaultTextOutput(true);
ToStringGenerationVisitor toStringer = new ToStringGenerationVisitor(textOutput);
toStringer.accept(x);
String stringMap = textOutput.toString();
return stringMap;
}
}
String getNameString(HasName hasName) {
String s = hasName.getName().replaceAll("_", "_1").replace('.', '_');
return s;
}
/**
* Looks up or assigns a seed id for a type..
*/
int getSeedId(JReferenceType type) {
Integer val = seedIdMap.get(type);
int seedId = val == null ? 0 : val;
if (seedId == 0) {
seedId = nextSeedId++;
seedIdMap.put(type, seedId);
}
return seedId;
}
String mangleName(JField x) {
String s = getNameString(x.getEnclosingType()) + '_' + getNameString(x);
return s;
}
String mangleNameForGlobal(JMethod x) {
String s = getNameString(x.getEnclosingType()) + '_' + getNameString(x) + "__";
for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) {
JType type = x.getOriginalParamTypes().get(i);
s += type.getJavahSignatureName();
}
s += x.getOriginalReturnType().getJavahSignatureName();
return s;
}
String mangleNameForPoly(JMethod x) {
assert !x.isPrivate() && !x.isStatic();
StringBuffer sb = new StringBuffer();
sb.append(getNameString(x));
sb.append("__");
for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) {
JType type = x.getOriginalParamTypes().get(i);
sb.append(type.getJavahSignatureName());
}
sb.append(x.getOriginalReturnType().getJavahSignatureName());
return sb.toString();
}
String mangleNameForPrivatePoly(JMethod x) {
assert x.isPrivate() && !x.isStatic();
StringBuffer sb = new StringBuffer();
/*
* 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.
*/
sb.append("private$");
sb.append(getNameString(x.getEnclosingType()));
sb.append("$");
sb.append(getNameString(x));
sb.append("__");
for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) {
JType type = x.getOriginalParamTypes().get(i);
sb.append(type.getJavahSignatureName());
}
sb.append(x.getOriginalReturnType().getJavahSignatureName());
return sb.toString();
}
String mangleNameSpecialObfuscate(JField x) {
assert (specialObfuscatedFields.containsKey(x));
switch (output) {
case OBFUSCATED:
return specialObfuscatedFields.get(x);
case PRETTY:
return x.getName() + "$";
case DETAILED:
return mangleName(x) + "$";
}
throw new InternalCompilerException("Unknown output mode");
}
String mangleNameSpecialObfuscate(JMethod x) {
assert (specialObfuscatedMethodSigs.containsKey(x.getSignature()));
switch (output) {
case OBFUSCATED:
return specialObfuscatedMethodSigs.get(x.getSignature());
case PRETTY:
return x.getName() + "$";
case DETAILED:
return mangleNameForPoly(x) + "$";
}
throw new InternalCompilerException("Unknown output mode");
}
private Pair<JavaToJavaScriptMap, Set<JsNode>> execImpl() {
CanObserveSubclassUninitializedFieldsVisitor canObserve =
new CanObserveSubclassUninitializedFieldsVisitor();
canObserve.accept(program);
SortVisitor sorter = new SortVisitor();
sorter.accept(program);
RecordCrossClassCallsAndJSInlinableMethods recorder =
new RecordCrossClassCallsAndJSInlinableMethods();
recorder.accept(program);
CreateNamesAndScopesVisitor creator = new CreateNamesAndScopesVisitor();
creator.accept(program);
GenerateJavaScriptVisitor generator =
new GenerateJavaScriptVisitor(recorder.methodsForJsInlining);
generator.accept(program);
final Map<JsName, JMethod> nameToMethodMap = new HashMap<JsName, JMethod>();
final HashMap<JsName, JField> nameToFieldMap = new HashMap<JsName, JField>();
final HashMap<JsName, JClassType> constructorNameToTypeMap = new HashMap<JsName, JClassType>();
for (JDeclaredType type : program.getDeclaredTypes()) {
JsName typeName = names.get(type);
if (type instanceof JClassType && typeName != null) {
constructorNameToTypeMap.put(typeName, (JClassType) type);
}
for (JField field : type.getFields()) {
if (field.isStatic()) {
JsName fieldName = names.get(field);
if (fieldName != null) {
nameToFieldMap.put(fieldName, field);
}
}
}
for (JMethod method : type.getMethods()) {
JsName methodName = names.get(method);
if (methodName != null) {
nameToMethodMap.put(methodName, method);
}
}
}
jsProgram.setIndexedFields(indexedFields);
jsProgram.setIndexedFunctions(indexedFunctions);
// TODO(spoon): Instead of gathering the information here, get it via
// SourceInfo
JavaToJavaScriptMap jjsMap = new JavaToJavaScriptMap() {
@Override
public JsName nameForMethod(JMethod method) {
return names.get(method);
}
@Override
public JsName nameForType(JClassType type) {
return names.get(type);
}
@Override
public JField nameToField(JsName name) {
return nameToFieldMap.get(name);
}
@Override
public JMethod nameToMethod(JsName name) {
return nameToMethodMap.get(name);
}
@Override
public JClassType nameToType(JsName name) {
return constructorNameToTypeMap.get(name);
}
@Override
public JClassType typeForStatement(JsStatement stat) {
return typeForStatMap.get(stat);
}
@Override
public JMethod vtableInitToMethod(JsStatement stat) {
return vtableInitForMethodMap.get(stat);
}
};
return Pair.create(jjsMap, generator.functionsForJsInlining);
}
}
Seems to be a bug in the GWT compiler.
-- Adding following line to the GenerateJavaScriptAST solves the problem:
line number 2627: namesToIdents.put("clone", "cL");
See patched file
Am Mittwoch, 16. Oktober 2013 15:30:56 UTC+2 schrieb Rocco De Angelis:
Am Mittwoch, 16. Oktober 2013 15:30:56 UTC+2 schrieb Rocco De Angelis:
Hi All,I checked out the latest version (trunk) of GWT from git and tried to compile my current project with this state of GWT.But during the compiling of the permutation I get an error message:Compiling 1 permutationCreating PermutationWorkerFactory instancesCompiling permutation 0...[ERROR] An internal compiler exception occurredcom.google.gwt.dev.jjs.InternalCompilerException: Unexpected error during visit. at com.google.gwt.dev.jjs.ast.JVisitor.translateException( JVisitor.java:110) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 148) at com.google.gwt.dev.jjs.ast.JVisitor. acceptWithInsertRemoveImmutabl e(JVisitor.java:171) at com.google.gwt.dev.jjs.ast.JClassType.traverse( JClassType.java:98) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 126) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 121) at com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST$ CreateNamesAndScopesVisitor. visit(GenerateJavaScriptAST. java:332) at com.google.gwt.dev.jjs.ast.JClassType.traverse( JClassType.java:96) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 126) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 121) at com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST$ CreateNamesAndScopesVisitor. visit(GenerateJavaScriptAST. java:332) at com.google.gwt.dev.jjs.ast.JClassType.traverse( JClassType.java:96) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 145) at com.google.gwt.dev.jjs.ast.JProgram.traverse(JProgram. java:974) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 126) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 121) at com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST. execImpl( GenerateJavaScriptAST.java: 2790) at com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST.exec( GenerateJavaScriptAST.java: 2501) at com.google.gwt.dev.jjs.JavaToJavaScriptCompiler. compilePermutation( JavaToJavaScriptCompiler.java: 355) at com.google.gwt.dev.jjs.UnifiedAst.compilePermutation( UnifiedAst.java:134) at com.google.gwt.dev.CompilePerms.compile( CompilePerms.java:195) at com.google.gwt.dev.ThreadedPermutationWorkerFacto ry$ThreadedPermutationWorker. compile( ThreadedPermutationWorkerFacto ry.java:49) at com.google.gwt.dev.PermutationWorkerFactory$ Manager$WorkerThread.run( PermutationWorkerFactory.java: 73) at java.lang.Thread.run(Thread.java:662) Caused by: java.lang.NullPointerExceptionat com.google.gwt.thirdparty.guava.common.base. Preconditions.checkNotNull( Preconditions.java:192) at com.google.gwt.thirdparty.guava.common.collect. MapMakerInternalMap. putIfAbsent( MapMakerInternalMap.java:3507) at com.google.gwt.thirdparty.guava.common.collect. Interners$WeakInterner.intern( Interners.java:85) at com.google.gwt.dev.js.ast.JsScope.maybeMangleKeyword( JsScope.java:53) at com.google.gwt.dev.js.ast.JsScope.declareName(JsScope. java:68) at com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST$ CreateNamesAndScopesVisitor. visit(GenerateJavaScriptAST. java:369) at com.google.gwt.dev.jjs.ast.JMethod.traverse(JMethod.java: 417) at com.google.gwt.dev.jjs.ast.JVisitor.accept(JVisitor.java: 145) ... 22 more[ERROR] at Object.java(79): protected Object clone();com.google.gwt.dev.jjs.ast.JMethod [ERROR] at Object.java(26): class Objectcom.google.gwt.dev.jjs.ast.JClassType [ERROR] at AbstractStyledText.java(12): abstract class AbstractStyledText extends Objectcom.google.gwt.dev.jjs.ast.JClassType [ERROR] at DefaultStyledText.java(13): final class DefaultStyledText extends AbstractStyledTextcom.google.gwt.dev.jjs.ast.JClassType [ERROR] at Unknown(0): <JProgram>com.google.gwt.dev.jjs.ast.JProgram [ERROR] Unrecoverable exception, shutting downcom.google.gwt.core.ext.UnableToCompleteException: (see previous log entries) at com.google.gwt.dev.javac.CompilationProblemReporter. logAndTranslateException( CompilationProblemReporter. java:90) at com.google.gwt.dev.jjs.JavaToJavaScriptCompiler. compilePermutation( JavaToJavaScriptCompiler.java: 549) at com.google.gwt.dev.jjs.UnifiedAst.compilePermutation( UnifiedAst.java:134) at com.google.gwt.dev.CompilePerms.compile( CompilePerms.java:195) at com.google.gwt.dev.ThreadedPermutationWorkerFacto ry$ThreadedPermutationWorker. compile( ThreadedPermutationWorkerFacto ry.java:49) at com.google.gwt.dev.PermutationWorkerFactory$ Manager$WorkerThread.run( PermutationWorkerFactory.java: 73) at java.lang.Thread.run(Thread.java:662) [ERROR] Not all permutation were compiled , completed (0/1)Shutting down PersistentUnitCache threadWhat looks very strange for me: The class DefaultStyledText is not in my source path and it is also not final??public class DefaultStyledText extends AbstractStyledText<AStyledDocument> { public DefaultStyledText(AStyledDocument document) { super(document);}@Overridepublic String getPlainText() {return getValue().getPlainText();}}I have my own Object.java implementation. I simply added a implementation for the clone method:protected native Object clone() throws CloneNotSupportedException /*-{var r = {};// prevents to use same hash code@com.google.gwt.core.client.impl.Impl::getHashCode(Ljava/ lang/Object;)(r); var o = this;for(var i in o){if(!(i in r)){r[i] = o[i];}}return r;}-*/;Anybody of the GWT gurus here an idea???
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To post to this group, send email to google-web-toolkit@googlegroups.com.
Visit this group at http://groups.google.com/group/google-web-toolkit.
For more options, visit https://groups.google.com/groups/opt_out.
No comments:
Post a Comment