Cleans up JsInterop related code in UnifyAst
This also fixes the problem where we don't always correctly
collect the root types (i.e. in case of non-class-wide JsExport).
This is not a complete cleanup as there are still unnecessary
checks in UnifyAst. I want to reduce the likelihood of behavior
change in this patch.
Change-Id: Ia8e9594e028df7977fd83e4e403dbbc8bcf63a35
Review-Link: https://gwt-review.googlesource.com/#/c/11651/
diff --git a/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
index 2ea39a4..f9eecd3 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
@@ -45,6 +45,7 @@
private final boolean isError;
private final boolean isGenerated;
private final boolean isSuperSource;
+ private final boolean hasJsInteropRootType;
private transient List<JsniMethod> jsniMethods;
private final long lastModified;
private final MethodArgNamesLookup methodArgNamesLookup;
@@ -64,6 +65,7 @@
String resourceLocation) {
assert unit != null;
this.compiledClasses = CompiledClass.copyForUnit(unit.getCompiledClasses(), this);
+ this.hasJsInteropRootType = unit.hasJsInteropRootType;
this.contentId = unit.getContentId();
this.dependencies = unit.getDependencies();
this.resourcePath = unit.getResourcePath();
@@ -94,6 +96,7 @@
CachedCompilationUnit(CompilationUnit unit, long astToken) {
assert unit != null;
this.compiledClasses = CompiledClass.copyForUnit(unit.getCompiledClasses(), this);
+ this.hasJsInteropRootType = unit.hasJsInteropRootType();
this.contentId = unit.getContentId();
this.dependencies = unit.getDependencies();
this.resourcePath = unit.getResourcePath();
@@ -181,6 +184,11 @@
}
@Override
+ boolean hasJsInteropRootType() {
+ return hasJsInteropRootType;
+ }
+
+ @Override
ContentId getContentId() {
return contentId;
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
index c3a1079..2979a4a 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
@@ -24,6 +24,9 @@
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.base.Function;
+import com.google.gwt.thirdparty.guava.common.base.Predicates;
+import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
import java.util.Collection;
import java.util.Collections;
@@ -166,6 +169,18 @@
return exposedUnits;
}
+ public Iterable<String> getQualifiedJsInteropRootTypesNames() {
+ Function<CompilationUnit, String> toRootTypeName = new Function<CompilationUnit, String>() {
+ @Override
+ public String apply(CompilationUnit compilationUnit) {
+ return compilationUnit.hasJsInteropRootType() ? compilationUnit.getTypeName() : null;
+ }
+ };
+ return FluentIterable.from(unitMap.values())
+ .transform(toRootTypeName)
+ .filter(Predicates.notNull());
+ }
+
public CompilerContext getCompilerContext() {
return compilerContext;
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
index a340edf..f9d2866 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
@@ -445,6 +445,12 @@
abstract CategorizedProblem[] getProblems();
+ /**
+ * Returns true if this compilation unit has any JsInterop root types (i.e. type with an export or
+ * a JsType).
+ */
+ abstract boolean hasJsInteropRootType();
+
private List<String> getJdtClassNames(String topLevelClass) {
List<String> classNames = new ArrayList<String>();
for (CompiledClass cc : getCompiledClasses()) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitImpl.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitImpl.java
index 43c8102..18cf8d3 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitImpl.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitImpl.java
@@ -18,6 +18,8 @@
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.util.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.base.Predicate;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
@@ -37,6 +39,7 @@
private final Dependencies dependencies;
private final List<CompiledClass> exposedCompiledClasses;
private final boolean hasErrors;
+ private final boolean hasJsInteropRootType;
private final List<JsniMethod> jsniMethods;
private final MethodArgNamesLookup methodArgs;
private final CategorizedProblem[] problems;
@@ -59,6 +62,11 @@
}
}
this.hasErrors = hasAnyErrors;
+ this.hasJsInteropRootType = Iterables.any(types, new Predicate<JDeclaredType>() {
+ @Override public boolean apply(JDeclaredType type) {
+ return type.hasAnyExports() || type.isOrExtendsJsType();
+ }
+ });
for (CompiledClass cc : compiledClasses) {
cc.initUnit(this);
}
@@ -100,6 +108,11 @@
}
@Override
+ boolean hasJsInteropRootType() {
+ return hasJsInteropRootType;
+ }
+
+ @Override
Dependencies getDependencies() {
return dependencies;
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
index e0e7e25..e53bbde 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
@@ -44,7 +44,6 @@
if (in == null) {
return null;
}
- CompiledClass[] orig = new CompiledClass[in.size()];
List<CompiledClass> copy = new ArrayList<CompiledClass>();
Map<CompiledClass, CompiledClass> enclosingClassMap = new HashMap<CompiledClass, CompiledClass>();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 185ed0f..3e3f72c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -148,6 +148,7 @@
import com.google.gwt.soyc.io.ArtifactsOutputDirectory;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
@@ -158,7 +159,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.annotation.Annotation;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
@@ -891,8 +891,6 @@
protected RebindPermutationOracle rpo;
protected String[] entryPointTypeNames;
- private static final String JS_EXPORT_ANN = "com.google.gwt.core.client.js.JsExport";
- private static final String JS_TYPE_ANN = "com.google.gwt.core.client.js.JsType";
public Precompiler(RebindPermutationOracle rpo, String[] entryPointTypeNames) {
this.rpo = rpo;
@@ -1036,7 +1034,7 @@
CompilationState compilationState = rpo.getCompilationState();
Memory.maybeDumpMemory("CompStateBuilt");
recordJsoTypes(compilationState.getTypeOracle());
- populateRootTypes(allRootTypes, additionalRootTypes, compilationState.getTypeOracle());
+ populateRootTypes(allRootTypes, additionalRootTypes, compilationState);
String entryMethodHolderTypeName =
buildEntryMethodHolder(rpo.getGeneratorContext(), allRootTypes);
beforeUnifyAst(allRootTypes);
@@ -1132,7 +1130,10 @@
}
private void populateRootTypes(Set<String> allRootTypes, String[] additionalRootTypes,
- TypeOracle typeOracle) {
+ CompilationState compilationState) {
+ if (jprogram.typeOracle.isJsInteropEnabled()) {
+ Iterables.addAll(allRootTypes, compilationState.getQualifiedJsInteropRootTypesNames());
+ }
Collections.addAll(allRootTypes, entryPointTypeNames);
Collections.addAll(allRootTypes, additionalRootTypes);
allRootTypes.addAll(JProgram.CODEGEN_TYPES_SET);
@@ -1141,49 +1142,11 @@
* Add all SingleJsoImpl types that we know about. It's likely that the concrete types are
* never explicitly referenced.
*/
+ TypeOracle typeOracle = compilationState.getTypeOracle();
for (com.google.gwt.core.ext.typeinfo.JClassType singleJsoIntf :
typeOracle.getSingleJsoImplInterfaces()) {
allRootTypes.add(typeOracle.getSingleJsoImpl(singleJsoIntf).getQualifiedSourceName());
}
-
- if (jprogram.typeOracle.isJsInteropEnabled()) {
- // find any types with @JsExport could be entry points as well
- nextType:
- for (com.google.gwt.dev.javac.typemodel.JClassType type :
- typeOracle.getTypes()) {
- for (Annotation ann : type.getAnnotations()) {
- // If the type immediately exports symbols to JS.
- if (ann.annotationType().getName().equals(JS_EXPORT_ANN)) {
- allRootTypes.add(type.getQualifiedSourceName());
- continue nextType;
- }
- }
- if (isJsType(type)) {
- // If the type or any transitive interface is a JS type.
- allRootTypes.add(type.getQualifiedSourceName());
- }
- }
- }
- }
-
- /**
- * Returns true if the type, or any super-interface has a JsType
- * annotation.
- */
- private boolean isJsType(com.google.gwt.dev.javac.typemodel.JClassType type) {
- for (Annotation ann : type.getAnnotations()) {
- if (ann.annotationType().getName().equals(JS_TYPE_ANN)) {
- return true;
- }
- }
-
- for (com.google.gwt.dev.javac.typemodel.JClassType intf : type
- .getImplementedInterfaces()) {
- if (isJsType(intf)) {
- return true;
- }
- }
- return false;
}
private void recordJsoTypes(TypeOracle typeOracle) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
index d50a567..167b15c 100755
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
@@ -309,6 +309,34 @@
return jsInteropType != JsInteropType.NONE;
}
+ public boolean isOrExtendsJsType() {
+ if (isJsType()) {
+ return true;
+ }
+ for (JInterfaceType subIntf : getImplements()) {
+ if (subIntf.isJsType()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasAnyExports() {
+ for (JMethod method : getMethods()) {
+ if (method.isExported()) {
+ return true;
+ }
+ }
+
+ for (JField field : getFields()) {
+ if (field.isExported()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public JsInteropType getJsInteropType() {
return jsInteropType;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
index c1696db..353d23b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
@@ -52,10 +52,6 @@
private String exportName;
private boolean noExport = false;
- public boolean isNoExport() {
- return noExport;
- }
-
public void setNoExport(boolean noExport) {
this.noExport = noExport;
}
@@ -68,6 +64,10 @@
return exportName;
}
+ public boolean isExported() {
+ return exportName != null && !noExport;
+ }
+
private static class ExternalSerializedForm implements Serializable {
private final JDeclaredType enclosingType;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index aed3155..26faa37 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -68,6 +68,10 @@
return exportName;
}
+ public boolean isExported() {
+ return exportName != null && !noExport;
+ }
+
public void setJsProperty(boolean jsProperty) {
this.jsProperty = jsProperty;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 5e42e85..620e408 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -256,11 +256,11 @@
}
public boolean isExportedField(JField field) {
- return isJsInteropEnabled() && field.getExportName() != null && !field.isNoExport();
+ return isJsInteropEnabled() && field.isExported();
}
public boolean isExportedMethod(JMethod method) {
- return isJsInteropEnabled() && method.getExportName() != null && !method.isNoExport();
+ return isJsInteropEnabled() && method.isExported();
}
public boolean isJsInteropEnabled() {
@@ -477,11 +477,6 @@
private final Map<String, String> jsoByInterface = Maps.newHashMap();
/**
- * A set of all JsTypes.
- */
- private final Set<JInterfaceType> jsInterfaces = Sets.newIdentityHashSet();
-
- /**
* A mapping from the type name to the actual type instance.
*/
private Map<String, JReferenceType> referenceTypesByName = Maps.newHashMap();
@@ -781,15 +776,7 @@
recordImmediateTypeRelations(moduleDeclaredTypes);
computeExtendedTypeRelations();
- jsInterfaces.clear();
-
for (JDeclaredType type : declaredTypes) {
-
- if (type instanceof JInterfaceType) {
- if (((JInterfaceType) type).isJsType()) {
- jsInterfaces.add((JInterfaceType) type);
- }
- }
// first time through, record all exported methods
for (JMethod method : type.getMethods()) {
if (isExportedMethod(method)) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
index 6191be3..d7da42e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -767,7 +767,7 @@
}
rootTypeBinaryNames.add(rootType.getName());
- if (jsInteropEnabled && (isJsType(rootType) || hasAnyExports(rootType))) {
+ if (jsInteropEnabled && (rootType.hasAnyExports() || rootType.isOrExtendsJsType())) {
fullFlowIntoType(rootType);
}
}
@@ -963,31 +963,12 @@
if (t instanceof JClassType && requiresDevirtualization(t)) {
instantiate(t);
}
- if (hasAnyExports(t) || isJsType(t)) {
+ if (jsInteropEnabled && (t.hasAnyExports() || t.isOrExtendsJsType())) {
instantiate(t);
}
}
}
- private boolean hasAnyExports(JDeclaredType t) {
- if (!jsInteropEnabled) {
- return false;
- }
-
- for (JMethod method : t.getMethods()) {
- if (program.typeOracle.isExportedMethod(method)) {
- return true;
- }
- }
-
- for (JField field : t.getFields()) {
- if (program.typeOracle.isExportedField(field)) {
- return true;
- }
- }
- return false;
- }
-
private boolean canAccessSuperMethod(JDeclaredType type, JMethod method) {
if (method.isPrivate()) {
return false;
@@ -1311,59 +1292,60 @@
if (program.isReferenceOnly(type) && !requiresDevirtualization(type)) {
return;
}
+ if (instantiatedTypes.contains(type)) {
+ return;
+ }
if (type.isExternal()) {
assert errorsFound;
return;
}
- if (!instantiatedTypes.contains(type)) {
- instantiatedTypes.add(type);
- if (type.getSuperClass() != null) {
- instantiate(translate(type.getSuperClass()));
- }
- for (JInterfaceType intf : type.getImplements()) {
- instantiate(translate(intf));
- }
- staticInitialize(type);
- boolean isJsType = isJsType(type);
+ instantiatedTypes.add(type);
+ if (type.getSuperClass() != null) {
+ instantiate(translate(type.getSuperClass()));
+ }
+ for (JInterfaceType intf : type.getImplements()) {
+ instantiate(translate(intf));
+ }
+ staticInitialize(type);
+ boolean isJsType = jsInteropEnabled && type.isOrExtendsJsType();
- if (!type.isAbstract()) {
- // this is a concrete type, add delegation methods for any unimplemented defender methods
- maybeImplementDefenderMethods(type);
- }
+ if (!type.isAbstract()) {
+ // this is a concrete type, add delegation methods for any unimplemented defender methods
+ maybeImplementDefenderMethods(type);
+ }
- // Flow into any reachable virtual methods.
- for (JMethod method : type.getMethods()) {
- if (method.canBePolymorphic()) {
- if (isJsType) {
- // Fake a call into the method to keep it around
- flowInto(method);
- continue;
- }
- 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);
- }
- } else if (program.typeOracle.isExportedMethod(method) &&
- (method.isStatic() || method.isConstructor())) {
- // rescue any @JsExport methods
+ // Flow into any reachable virtual methods.
+ for (JMethod method : type.getMethods()) {
+ if (method.canBePolymorphic()) {
+ if (isJsType) {
+ // Fake a call into the method to keep it around
flowInto(method);
+ continue;
}
+ 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);
+ }
+ } else if (program.typeOracle.isExportedMethod(method)
+ && (method.isStatic() || method.isConstructor())) {
+ // rescue any @JsExport methods
+ flowInto(method);
}
+ }
- for (JField field : type.getFields()) {
- if (field.isStatic() && program.typeOracle.isExportedField(field)) {
- flowInto(field);
- }
+ for (JField field : type.getFields()) {
+ if (field.isStatic() && program.typeOracle.isExportedField(field)) {
+ flowInto(field);
}
}
}
@@ -1434,22 +1416,6 @@
return type == program.getJavaScriptObject() || isJso(type.getSuperClass());
}
- private boolean isJsType(JDeclaredType intf) {
- if (!jsInteropEnabled) {
- return false;
- }
-
- if (intf.isJsType()) {
- return true;
- }
- for (JInterfaceType subIntf : intf.getImplements()) {
- if (isJsType(subIntf)) {
- return true;
- }
- }
- return false;
- }
-
/**
* 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.
diff --git a/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java b/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java
index c22303b..75f65c9 100644
--- a/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java
+++ b/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java
@@ -122,6 +122,11 @@
}
@Override
+ boolean hasJsInteropRootType() {
+ return false;
+ }
+
+ @Override
ContentId getContentId() {
return contentId;
}
diff --git a/user/test/com/google/gwt/core/client/interop/JsExportTest.java b/user/test/com/google/gwt/core/client/interop/JsExportTest.java
index 57c6475..afaf108 100644
--- a/user/test/com/google/gwt/core/client/interop/JsExportTest.java
+++ b/user/test/com/google/gwt/core/client/interop/JsExportTest.java
@@ -55,6 +55,16 @@
assertTrue(MyClassExportsMethod.calledFromFoo);
}
+ public void testMethodExport_notReferencedFromJava() {
+ // Exported by MyClassExportsMethodWithoutReference which is not referenced by Java. This
+ // ensures that we correctly collect root types.
+ assertEquals(42, onlyCalledFromJs());
+ }
+
+ private native int onlyCalledFromJs() /*-{
+ return $wnd.onlyCalledFromJs();
+ }-*/;
+
public void testClinit() {
ScriptInjector.fromString("new $wnd.MyClassExportsMethodWithClinit();").inject();
assertEquals(23, MyClassExportsMethodWithClinit.magicNumber);
diff --git a/user/test/com/google/gwt/core/client/interop/MyClassExportsMethodWithoutReference.java b/user/test/com/google/gwt/core/client/interop/MyClassExportsMethodWithoutReference.java
new file mode 100644
index 0000000..27ab623
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/interop/MyClassExportsMethodWithoutReference.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 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.core.client.interop;
+
+import com.google.gwt.core.client.js.JsExport;
+
+/**
+ * A test class exports a method but isn't referenced from any Java code.
+ */
+public class MyClassExportsMethodWithoutReference {
+ @JsExport("onlyCalledFromJs")
+ public static int callMe() {
+ return 42;
+ }
+}