blob: f1a4246b0258268125fe40c8ed522a6e9d753da8 [file] [log] [blame]
/*
* Copyright 2011 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.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.javac.CompilationProblemReporter;
import com.google.gwt.dev.javac.CompilationUnit;
import com.google.gwt.dev.javac.CompiledClass;
import com.google.gwt.dev.jdt.RebindPermutationOracle;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.JJSOptions;
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.HasName;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBlock;
import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
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.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JEnumType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JFieldRef;
import com.google.gwt.dev.jjs.ast.JGwtCreate;
import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JInterfaceType;
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.JModVisitor;
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.JNonNullType;
import com.google.gwt.dev.jjs.ast.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JVariable;
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.js.ast.JsNestingScope;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.dev.util.Name;
import com.google.gwt.dev.util.Name.BinaryName;
import com.google.gwt.dev.util.Name.InternalName;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.dev.util.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
* Take independently-compiled types and merge them into a single AST.
*
* Works kind of like {@link ControlFlowAnalyzer} in terms of reachability,
* except that in some cases it's easier to be conservative and visit relatively
* more nodes than CFA would.
*
* Operates based on a work-queue to prevent recursion sickness.
*
* Must handle:
*
* - Type reference resolution
*
* - Field and method reference resolution
*
* - General code flow like ControlFlowAnalyzer
*
* - GWT.create(), GWT.runAsync(), Impl.getNameOf()
*
* - Stitch native methods into JsProgram
*
* - Class.desiredAssertionStatus, Class.isClassMetaDataEnabled, GWT.isClient,
* GWT.isProdMode, GWT.isScript.
*
* TODO: SOYC correlations.
*/
public class UnifyAst {
private class UnifyVisitor extends JModVisitor {
private JMethod currentMethod;
@Override
public void endVisit(JArrayType x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
// Concat ops need to resolve string type.
x.setType(translate(x.getType()));
}
@Override
public void endVisit(JCastOperation x, Context ctx) {
x.resolve(translate(x.getCastType()));
}
@Override
public void endVisit(JClassLiteral x, Context ctx) {
JType refType = translate(x.getRefType());
x.resolve(refType);
// ImplementClassLiteralsAsFields: rescue enumType.values()/valueOf().
if (refType instanceof JArrayType) {
JType leafType = ((JArrayType) refType).getLeafType();
if (leafType instanceof JReferenceType) {
refType = leafType;
}
}
if (refType instanceof JClassType) {
JClassType classType = (JClassType) refType;
JEnumType enumType = classType.isEnumOrSubclass();
if (enumType != null) {
for (JMethod method : enumType.getMethods()) {
if (method.isStatic()) {
if (method.getSignature().startsWith("values()")) {
flowInto(method);
} else if (method.getSignature().startsWith("valueOf(Ljava/lang/String;)")) {
flowInto(method);
}
}
}
}
}
}
@Override
public void endVisit(JClassType x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JConditional x, Context ctx) {
x.setType(translate(x.getType()));
}
@Override
public void endVisit(JConstructor x, Context ctx) {
// Process as method.
super.endVisit(x, ctx);
instantiate(x.getEnclosingType());
}
@Override
public void endVisit(JDeclaredType x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JExpression x, Context ctx) {
assert !x.getType().isExternal() || errorsFound;
}
@Override
public void endVisit(JField x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JFieldRef x, Context ctx) {
JField field = translate(x.getField());
flowInto(field);
x.resolve(field);
// Should not have an overridden type at this point.
assert x.getType() == x.getField().getType();
assert !x.getEnclosingType().isExternal();
}
@Override
public void endVisit(JInstanceOf x, Context ctx) {
x.resolve(translate(x.getTestType()));
}
@Override
public void endVisit(JInterfaceType x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JMethod x, Context ctx) {
currentMethod = null;
}
@Override
public void endVisit(JMethodCall x, Context ctx) {
// Already resolved during visit().
JMethod target = x.getTarget();
if (target.isExternal()) {
assert errorsFound;
return;
}
if (magicMethodCalls.contains(target)) {
JExpression result = handleMagicMethodCall(x);
if (result == null) {
// Error of some sort.
result = JNullLiteral.INSTANCE;
}
result = this.accept(result);
ctx.replaceMe(result);
return;
}
if (!(x instanceof JNewInstance)) {
// Should not have an overridden type at this point.
assert x.getType() == target.getType();
}
flowInto(target);
}
@Override
public void endVisit(JNameOf x, Context ctx) {
HasName node = x.getNode();
if (node instanceof JType) {
node = translate((JType) node);
} else if (node instanceof JField) {
node = translate((JField) node);
} else if (node instanceof JMethod) {
node = translate((JMethod) node);
} else {
assert false : "Should not get here";
}
x.resolve(node, (JClassType) translate(x.getType().getUnderlyingType()));
}
@Override
public void endVisit(JNewArray x, Context ctx) {
x.setType((JArrayType) translate(x.getArrayType()));
}
@Override
public void endVisit(JNewInstance x, Context ctx) {
flowInto(x.getTarget());
assert !x.getEnclosingType().isExternal();
}
@Override
public void endVisit(JsniFieldRef x, Context ctx) {
endVisit((JFieldRef) x, ctx);
}
@Override
public void endVisit(JsniMethodBody x, Context ctx) {
JsNestingScope funcScope = (JsNestingScope) x.getFunc().getScope();
assert funcScope.getParent() == JsRootScope.INSTANCE;
funcScope.nestInto(jsProgram.getScope());
}
@Override
public void endVisit(JsniMethodRef x, Context ctx) {
JMethod target = translate(x.getTarget());
x.resolve(target, program.getJavaScriptObject());
flowInto(target);
}
@Override
public void endVisit(JsonArray x, Context ctx) {
x.resolve(translate(x.getType()));
}
@Override
public void endVisit(JsonObject x, Context ctx) {
x.resolve(translate(x.getType()));
}
@Override
public void endVisit(JStringLiteral x, Context ctx) {
JClassType stringType = program.getTypeJavaLangString();
x.resolve(stringType);
instantiate(stringType);
}
@Override
public void endVisit(JThisRef x, Context ctx) {
assert !x.getType().isExternal();
}
@Override
public void endVisit(JVariable x, Context ctx) {
x.setType(translate(x.getType()));
}
@Override
public boolean visit(JMethod x, Context ctx) {
currentMethod = x;
return true;
}
@Override
public boolean visit(JMethodCall x, Context ctx) {
JMethod target = translate(x.getTarget());
x.resolve(target);
// Special handling.
return !magicMethodCalls.contains(target);
}
private JExpression handleGwtCreate(JMethodCall x) {
assert (x.getArgs().size() == 1);
JExpression arg = x.getArgs().get(0);
if (!(arg instanceof JClassLiteral)) {
error(x, "Only class literals may be used as arguments to GWT.create()");
return null;
}
JClassLiteral classLiteral = (JClassLiteral) arg;
if (!(classLiteral.getRefType() instanceof JDeclaredType)) {
error(x, "Only classes and interfaces may be used as arguments to GWT.create()");
return null;
}
JDeclaredType type = (JDeclaredType) classLiteral.getRefType();
String reqType = JGwtCreate.nameOf(type);
List<String> answers;
try {
answers = Lists.create(rpo.getAllPossibleRebindAnswers(logger, reqType));
rpo.getGeneratorContext().finish(logger);
} catch (UnableToCompleteException e) {
error(x, "Failed to resolve '" + reqType + "' via deferred binding");
return null;
}
ArrayList<JExpression> instantiationExpressions = new ArrayList<JExpression>(answers.size());
for (String answer : answers) {
JDeclaredType answerType = searchForTypeBySource(answer);
if (answerType == null) {
error(x, "Rebind result '" + answer + "' could not be found");
return null;
}
if (!(answerType instanceof JClassType)) {
error(x, "Rebind result '" + answer + "' must be a class");
return null;
}
if (answerType.isAbstract()) {
error(x, "Rebind result '" + answer + "' cannot be abstract");
return null;
}
JExpression result =
JGwtCreate.createInstantiationExpression(x.getSourceInfo(), (JClassType) answerType,
currentMethod.getEnclosingType());
if (result == null) {
error(x, "Rebind result '" + answer + "' has no default (zero argument) constructors");
return null;
}
instantiationExpressions.add(result);
}
assert answers.size() == instantiationExpressions.size();
if (answers.size() == 1) {
return instantiationExpressions.get(0);
} else {
return new JGwtCreate(x.getSourceInfo(), reqType, answers, program.getTypeJavaLangObject(),
instantiationExpressions);
}
}
private JExpression handleImplNameOf(final JMethodCall x) {
assert (x.getArgs().size() == 1);
JExpression arg = x.getArgs().get(0);
if (!(arg instanceof JStringLiteral)) {
error(x, "Only string literals may be used as arguments to Impl.getNameOf()");
return null;
}
JStringLiteral stringLiteral = (JStringLiteral) arg;
String stringValue = stringLiteral.getValue();
JNode node = null;
JsniRef ref = JsniRef.parse(stringValue);
if (ref != null) {
if (Name.isBinaryName(ref.className())) {
searchForTypeByBinary(ref.className());
}
node = JsniRefLookup.findJsniRefTarget(ref, program, new JsniRefLookup.ErrorReporter() {
public void reportError(String errMsg) {
error(x, errMsg);
}
});
} else {
// See if it's just @foo.Bar, which would result in the class seed
String typeName = stringValue.charAt(0) == '@' ? stringValue.substring(1) : stringValue;
node = searchForTypeByBinary(typeName);
}
if (node == null) {
// Not found, must be null
return null;
} else {
return new JNameOf(x.getSourceInfo(), program.getTypeJavaLangString(), (HasName) node);
}
}
private JExpression handleMagicMethodCall(JMethodCall x) {
JMethod target = x.getTarget();
String sig = target.getEnclosingType().getName() + '.' + target.getSignature();
if (GWT_CREATE.equals(sig)) {
return handleGwtCreate(x);
} else if (IMPL_GET_NAME_OF.equals(sig)) {
return handleImplNameOf(x);
}
throw new InternalCompilerException("Unknown magic method");
}
}
private static final String CLASS_DESIRED_ASSERTION_STATUS =
"java.lang.Class.desiredAssertionStatus()Z";
private static final String CLASS_IS_CLASS_METADATA_ENABLED =
"java.lang.Class.isClassMetadataEnabled()Z";
private static final String GWT_CREATE =
"com.google.gwt.core.client.GWT.create(Ljava/lang/Class;)Ljava/lang/Object;";
private static final String GWT_IS_CLIENT = "com.google.gwt.core.client.GWT.isClient()Z";
private static final String GWT_IS_PROD_MODE = "com.google.gwt.core.client.GWT.isProdMode()Z";
private static final String GWT_IS_SCRIPT = "com.google.gwt.core.client.GWT.isScript()Z";
private static final String IMPL_GET_NAME_OF =
"com.google.gwt.core.client.impl.Impl.getNameOf(Ljava/lang/String;)Ljava/lang/String;";
/**
* Methods for which the call site must be replaced with magic AST nodes.
*/
private static final Set<String> MAGIC_METHOD_CALLS = new LinkedHashSet<String>(Arrays.asList(
GWT_CREATE, IMPL_GET_NAME_OF));
/**
* Methods with magic implementations that the compiler must insert.
*/
private static final Set<String> MAGIC_METHOD_IMPLS = new LinkedHashSet<String>(Arrays.asList(
GWT_IS_CLIENT, GWT_IS_PROD_MODE, GWT_IS_SCRIPT, CLASS_DESIRED_ASSERTION_STATUS,
CLASS_IS_CLASS_METADATA_ENABLED));
private final Map<String, CompiledClass> classFileMap;
private final Map<String, CompiledClass> classFileMapBySource;
private boolean errorsFound = false;
private final Set<CompilationUnit> failedUnits = new IdentityHashSet<CompilationUnit>();
private final Map<String, JField> fieldMap = new HashMap<String, JField>();
/**
* The set of types currently known to be instantiable. Like
* {@link ControlFlowAnalyzer#instantiatedTypes}.
*/
private final Set<JDeclaredType> instantiatedTypes = new IdentityHashSet<JDeclaredType>();
private final JsProgram jsProgram;
/**
* Fields and methods that are referenceable. Like
* {@link ControlFlowAnalyzer#liveFieldsAndMethods}.
*/
private final Set<JNode> liveFieldsAndMethods = new IdentityHashSet<JNode>();
private TreeLogger logger;
private Set<JMethod> magicMethodCalls = new IdentityHashSet<JMethod>();
private final Map<String, JMethod> methodMap = new HashMap<String, JMethod>();
private final JJSOptions options;
private final JProgram program;
private final RebindPermutationOracle rpo;
/**
* A work queue of methods whose bodies we need to traverse. Prevents
* excessive stack use.
*/
private final Queue<JMethod> todo = new LinkedList<JMethod>();
private final Set<String> virtualMethodsLive = new HashSet<String>();
private final Map<String, List<JMethod>> virtualMethodsPending =
new java.util.HashMap<String, List<JMethod>>();
public UnifyAst(JProgram program, JsProgram jsProgram, JJSOptions options,
RebindPermutationOracle rpo) {
this.program = program;
this.jsProgram = jsProgram;
this.options = options;
this.rpo = rpo;
this.classFileMap = rpo.getCompilationState().getClassFileMap();
this.classFileMapBySource = rpo.getCompilationState().getClassFileMapBySource();
}
public void addRootTypes(Collection<String> sourceTypeNames) {
for (String sourceTypeName : sourceTypeNames) {
searchForTypeBySource(sourceTypeName);
}
}
/**
* Special AST construction, useful for tests. Everything is resolved,
* translated, and unified.
*/
public void buildEverything(TreeLogger logger) throws UnableToCompleteException {
this.logger = logger;
for (String internalName : classFileMap.keySet()) {
String typeName = InternalName.toBinaryName(internalName);
searchForTypeByBinary(typeName);
}
for (JDeclaredType type : program.getDeclaredTypes()) {
instantiate(type);
for (JField field : type.getFields()) {
flowInto(field);
}
for (JMethod method : type.getMethods()) {
flowInto(method);
}
}
mainLoop();
computeOverrides();
if (errorsFound) {
throw new UnableToCompleteException();
}
}
/**
* For normal compilation, only translate and stitch types reachable from
* entry points. This reduces memory and improves compile speed. Any
* unreachable elements are pruned.
*/
public void exec(TreeLogger logger) throws UnableToCompleteException {
this.logger = logger;
// Trace execution from entry points.
for (JMethod entryMethod : program.getEntryMethods()) {
flowInto(entryMethod);
}
// Trace execution from compiler code gen types.
for (JClassType type : program.codeGenTypes) {
for (JMethod method : type.getMethods()) {
flowInto(method);
}
}
/*
* Since we're not actually optimizing here, it's easier to just visit
* certain things up front instead of duplicating the exacting semantics of
* ControlFlowAnalyzer.
*/
// String literals.
instantiate(program.getTypeJavaLangString());
// ControlFlowAnalyzer.rescueByConcat().
flowInto(program.getIndexedMethod("Object.toString"));
mapApi(program.getTypeJavaLangString());
flowInto(methodMap.get("java.lang.String.valueOf(C)Ljava/lang/String;"));
// Additional pre-optimization code gen.
// TODO: roll these into this class?
// EnumNameObfuscator
flowInto(program.getIndexedMethod("Enum.obfuscatedName"));
// FixAssignmentToUnbox
AutoboxUtils autoboxUtils = new AutoboxUtils(program);
for (JMethod method : autoboxUtils.getBoxMethods()) {
flowInto(method);
}
for (JMethod method : autoboxUtils.getUnboxMethods()) {
flowInto(method);
}
// ReplaceRunAsyncs
if (options.isRunAsyncEnabled()) {
flowInto(program.getIndexedMethod("AsyncFragmentLoader.onLoad"));
flowInto(program.getIndexedMethod("AsyncFragmentLoader.runAsync"));
}
// ImplementClassLiteralsAsFields
staticInitialize(program.getTypeClassLiteralHolder());
for (JMethod method : program.getTypeJavaLangClass().getMethods()) {
if (method.isStatic() && method.getName().startsWith("createFor")) {
flowInto(method);
}
}
mainLoop();
// Post-stitching clean-ups.
// Prune any untranslated types, fields, and methods.
for (Iterator<JDeclaredType> it = program.getDeclaredTypes().iterator(); it.hasNext();) {
JDeclaredType type = it.next();
boolean isInstantiated = instantiatedTypes.contains(type);
for (int i = 0; i < type.getFields().size(); ++i) {
JField field = type.getFields().get(i);
if (!liveFieldsAndMethods.contains(field) || (!field.isStatic() && !isInstantiated)) {
type.removeField(i);
--i;
}
}
// Special clinit handling.
JMethod clinit = type.getMethods().get(0);
if (!liveFieldsAndMethods.contains(clinit)) {
clinit.setBody(new JMethodBody(SourceOrigin.UNKNOWN));
}
for (int i = 1; i < type.getMethods().size(); ++i) {
JMethod method = type.getMethods().get(i);
if (!liveFieldsAndMethods.contains(method) || (!method.isStatic() && !isInstantiated)) {
type.removeMethod(i);
--i;
}
}
}
computeOverrides();
if (errorsFound) {
throw new UnableToCompleteException();
}
}
private void assimilateUnit(CompilationUnit unit) {
if (unit.isError()) {
if (failedUnits.add(unit)) {
CompilationProblemReporter.reportErrors(logger, unit, false);
errorsFound = true;
}
return;
}
// TODO(zundel): ask for a recompile if deserialization fails?
List<JDeclaredType> types = unit.getTypes();
assert containsAllTypes(unit, types);
for (JDeclaredType t : types) {
program.addType(t);
}
for (JDeclaredType t : types) {
resolveType(t);
}
/*
* Eagerly instantiate any JavaScriptObject subtypes. That way we don't have
* to copy the exact semantics of ControlFlowAnalyzer.
*/
for (JDeclaredType t : types) {
if (t instanceof JClassType && isJso((JClassType) t)) {
instantiate(t);
}
}
}
private void collectUpRefs(JDeclaredType type, Map<String, Set<JMethod>> collected) {
if (type == null) {
return;
}
for (JMethod method : type.getMethods()) {
if (method.canBePolymorphic()) {
Set<JMethod> set = collected.get(method.getSignature());
if (set != null) {
set.add(method);
}
}
}
collectUpRefsInSupers(type, collected);
}
private void collectUpRefsInSupers(JDeclaredType type, Map<String, Set<JMethod>> collected) {
collectUpRefs(type.getSuperClass(), collected);
for (JInterfaceType intfType : type.getImplements()) {
collectUpRefs(intfType, collected);
}
}
/**
* Compute all overrides.
*/
private void computeOverrides() {
for (JDeclaredType type : program.getDeclaredTypes()) {
Map<String, Set<JMethod>> collected = new HashMap<String, Set<JMethod>>();
for (JMethod method : type.getMethods()) {
if (method.canBePolymorphic()) {
collected.put(method.getSignature(), new LinkedHashSet<JMethod>());
}
}
collectUpRefsInSupers(type, collected);
for (JMethod method : type.getMethods()) {
if (method.canBePolymorphic()) {
Set<JMethod> uprefs = collected.get(method.getSignature());
method.addOverrides(Lists.create(uprefs));
}
}
}
}
private boolean containsAllTypes(CompilationUnit unit, List<JDeclaredType> types) {
Set<String> binaryTypeNames = new HashSet<String>();
for (JDeclaredType type : types) {
binaryTypeNames.add(type.getName());
}
for (CompiledClass cc : unit.getCompiledClasses()) {
if (!binaryTypeNames.contains(InternalName.toBinaryName(cc.getInternalName()))) {
return false;
}
}
return true;
}
private void error(JNode x, String errorMessage) {
errorsFound = true;
TreeLogger branch =
logger
.branch(TreeLogger.ERROR, "Errors in '" + x.getSourceInfo().getFileName() + "'", null);
// Append 'Line #: msg' to the error message.
StringBuffer msgBuf = new StringBuffer();
int line = x.getSourceInfo().getStartLine();
if (line > 0) {
msgBuf.append("Line ");
msgBuf.append(line);
msgBuf.append(": ");
}
msgBuf.append(errorMessage);
branch.log(TreeLogger.ERROR, msgBuf.toString());
}
private void flowInto(JField field) {
if (field.isExternal()) {
assert errorsFound;
return;
}
if (field == JField.NULL_FIELD) {
return;
}
if (!liveFieldsAndMethods.contains(field)) {
liveFieldsAndMethods.add(field);
field.setType(translate(field.getType()));
if (field.isStatic()) {
staticInitialize(field.getEnclosingType());
}
}
}
private void flowInto(JMethod method) {
if (method.isExternal()) {
assert errorsFound;
return;
}
if (method == JMethod.NULL_METHOD) {
return;
}
if (!liveFieldsAndMethods.contains(method)) {
liveFieldsAndMethods.add(method);
JType originalReturnType = translate(method.getOriginalReturnType());
List<JType> originalParamTypes = new ArrayList<JType>(method.getOriginalParamTypes().size());
for (JType originalParamType : method.getOriginalParamTypes()) {
originalParamTypes.add(translate(originalParamType));
}
JType returnType = translate(method.getType());
List<JClassType> thrownExceptions =
new ArrayList<JClassType>(method.getThrownExceptions().size());
for (JClassType thrownException : method.getThrownExceptions()) {
thrownExceptions.add(translate(thrownException));
}
method.resolve(originalReturnType, originalParamTypes, returnType, thrownExceptions);
if (method.isStatic()) {
staticInitialize(method.getEnclosingType());
} else if (method.canBePolymorphic()) {
String signature = method.getSignature();
if (!virtualMethodsLive.contains(signature)) {
virtualMethodsLive.add(signature);
List<JMethod> pending = virtualMethodsPending.remove(signature);
if (pending != null) {
for (JMethod p : pending) {
assert instantiatedTypes.contains(p.getEnclosingType());
flowInto(p);
}
}
}
}
// Queue up visit / resolve on the body.
todo.add(method);
}
}
private void implementMagicMethod(JMethod method, JExpression returnValue) {
JMethodBody body = (JMethodBody) method.getBody();
JBlock block = body.getBlock();
SourceInfo info;
if (block.getStatements().size() > 0) {
info = block.getStatements().get(0).getSourceInfo();
} else {
info = method.getSourceInfo();
}
block.clear();
block.addStmt(new JReturnStatement(info, returnValue));
}
private void instantiate(JDeclaredType type) {
if (type.isExternal()) {
assert errorsFound;
return;
}
if (!instantiatedTypes.contains(type)) {
instantiatedTypes.add(type);
if (type.getSuperClass() != null) {
instantiate(type.getSuperClass());
}
for (JInterfaceType intf : type.getImplements()) {
instantiate(intf);
}
staticInitialize(type);
// Flow into any reachable virtual methods.
for (JMethod method : type.getMethods()) {
if (method.canBePolymorphic()) {
String signature = method.getSignature();
if (virtualMethodsLive.contains(signature)) {
assert !virtualMethodsPending.containsKey(signature);
flowInto(method);
} else {
List<JMethod> pending = virtualMethodsPending.get(signature);
if (pending == null) {
pending = Lists.create(method);
} else {
pending = Lists.add(pending, method);
}
virtualMethodsPending.put(signature, pending);
}
}
}
}
}
private boolean isJso(JClassType type) {
if (type == null) {
return false;
}
return type == program.getJavaScriptObject() || isJso(type.getSuperClass());
}
/**
* Main loop: run through the queue doing deferred resolution. We could have
* made this entirely recursive, but a work queue uses much less max stack.
*/
private void mainLoop() {
UnifyVisitor visitor = new UnifyVisitor();
while (!todo.isEmpty()) {
visitor.accept(todo.poll());
}
}
private void mapApi(JDeclaredType type) {
assert !type.isExternal();
for (JField field : type.getFields()) {
String sig = type.getName() + '.' + field.getSignature();
fieldMap.put(sig, field);
}
for (JMethod method : type.getMethods()) {
String sig = type.getName() + '.' + method.getSignature();
methodMap.put(sig, method);
if (MAGIC_METHOD_CALLS.contains(sig)) {
magicMethodCalls.add(method);
}
if (MAGIC_METHOD_IMPLS.contains(sig)) {
if (sig.startsWith("com.google.gwt.core.client.GWT.")) {
// GWT.isClient, GWT.isScript, GWT.isProdMode all true.
implementMagicMethod(method, JBooleanLiteral.TRUE);
} else {
assert sig.startsWith("java.lang.Class.");
if (CLASS_DESIRED_ASSERTION_STATUS.equals(sig)) {
implementMagicMethod(method, JBooleanLiteral.get(options.isEnableAssertions()));
} else if (CLASS_IS_CLASS_METADATA_ENABLED.equals(sig)) {
implementMagicMethod(method, JBooleanLiteral.get(!options.isClassMetadataDisabled()));
} else {
assert false;
}
}
}
}
}
private void resolveType(JDeclaredType type) {
assert !type.isExternal();
if (type instanceof JClassType && type.getSuperClass() != null) {
((JClassType) type).setSuperClass(translate(type.getSuperClass()));
}
List<JInterfaceType> resolvedInterfaces = new ArrayList<JInterfaceType>();
for (JInterfaceType intf : type.getImplements()) {
resolvedInterfaces.add((JInterfaceType) translate(intf));
}
List<JNode> resolvedRescues = new ArrayList<JNode>();
for (JNode node : type.getArtificialRescues()) {
if (node instanceof JType) {
node = translate((JType) node);
} else if (node instanceof JField) {
node = translate((JField) node);
} else if (node instanceof JMethod) {
node = translate((JMethod) node);
} else {
assert false : "Unknown artificial rescue node.";
}
resolvedRescues.add(node);
}
type.resolve(resolvedInterfaces, resolvedRescues);
}
private JDeclaredType searchForTypeByBinary(String binaryTypeName) {
JDeclaredType type = program.getFromTypeMap(binaryTypeName);
if (type == null) {
CompiledClass cc = classFileMap.get(BinaryName.toInternalName(binaryTypeName));
if (cc == null) {
// TODO: error
throw new NoClassDefFoundError(binaryTypeName);
}
assimilateUnit(cc.getUnit());
type = program.getFromTypeMap(binaryTypeName);
assert type != null || errorsFound;
}
return type;
}
private JDeclaredType searchForTypeBySource(String sourceTypeName) {
JDeclaredType type = program.getFromTypeMap(sourceTypeName);
if (type == null) {
CompiledClass cc = classFileMapBySource.get(sourceTypeName);
if (cc == null) {
// TODO: error
throw new NoClassDefFoundError(sourceTypeName);
}
assimilateUnit(cc.getUnit());
type = program.getFromTypeMap(sourceTypeName);
assert type != null || errorsFound;
}
return type;
}
private void staticInitialize(JDeclaredType type) {
if (type.isExternal()) {
assert errorsFound;
return;
}
JMethod clinit = type.getMethods().get(0);
if (!liveFieldsAndMethods.contains(clinit)) {
flowInto(clinit);
if (type.getSuperClass() != null) {
staticInitialize(type.getSuperClass());
}
for (JNode node : type.getArtificialRescues()) {
if (node instanceof JType) {
if (node instanceof JDeclaredType) {
instantiate((JDeclaredType) node);
}
} else if (node instanceof JField) {
JField field = (JField) node;
flowInto(field);
if (!field.isFinal()) {
field.setVolatile();
}
} else if (node instanceof JMethod) {
flowInto((JMethod) node);
} else {
assert false : "Unknown artificial rescue node.";
}
}
}
}
private JClassType translate(JClassType type) {
return (JClassType) translate((JDeclaredType) type);
}
private JDeclaredType translate(JDeclaredType type) {
if (!type.isExternal()) {
return type;
}
String typeName = type.getName();
JDeclaredType newType = searchForTypeByBinary(typeName);
if (newType == null) {
assert errorsFound;
return type;
}
assert !newType.isExternal();
return newType;
}
private JField translate(JField field) {
if (!field.isExternal()) {
return field;
}
JDeclaredType enclosingType = field.getEnclosingType();
String sig = enclosingType.getName() + '.' + field.getSignature();
JField newField = fieldMap.get(sig);
if (newField != null) {
return newField;
}
enclosingType = translate(enclosingType);
if (enclosingType.isExternal()) {
assert errorsFound;
return field;
}
mapApi(enclosingType);
// Now the field should be there.
field = fieldMap.get(sig);
if (field == null) {
// TODO: error logging
throw new NoSuchFieldError(sig);
}
assert !field.isExternal();
return field;
}
private JMethod translate(JMethod method) {
if (!method.isExternal()) {
return method;
}
JDeclaredType enclosingType = method.getEnclosingType();
String sig = enclosingType.getName() + '.' + method.getSignature();
JMethod newMethod = methodMap.get(sig);
if (newMethod != null) {
return newMethod;
}
enclosingType = translate(enclosingType);
if (enclosingType.isExternal()) {
assert errorsFound;
return method;
}
mapApi(enclosingType);
// Now the method should be there.
method = methodMap.get(sig);
if (method == null) {
// TODO: error logging
throw new NoSuchMethodError(sig);
}
assert !method.isExternal();
return method;
}
private JReferenceType translate(JReferenceType type) {
if (type instanceof JArrayType) {
JArrayType arrayType = (JArrayType) type;
type = program.getTypeArray(translate(arrayType.getElementType()));
} else if (type.isExternal()) {
if (type instanceof JDeclaredType) {
type = translate((JDeclaredType) type);
} else if (type instanceof JNonNullType) {
type = translate(type.getUnderlyingType()).getNonNull();
} else {
assert false : "Unknown external type";
}
}
assert !type.isExternal();
return type;
}
private JType translate(JType type) {
if (type instanceof JPrimitiveType) {
return type;
}
return translate((JReferenceType) type);
}
}