Fix for issue #937; static initializers must statically initialize super
class. Also includes a fix for a possible ICE in GenerateJavaAST.java
when JDT hands us a ThisRef while processing field initializers in an
interface.
Review by: mmendez, alex.tkachman
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@956 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JFieldRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JFieldRef.java
index bdd7689..c78e8c7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JFieldRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JFieldRef.java
@@ -62,9 +62,8 @@
// A cross-class reference to a static, non constant field forces clinit
if (field.isStatic()
&& (!field.isFinal() || field.constInitializer == null)) {
- JReferenceType fieldEncloser = field.getEnclosingType();
- if (enclosingType != fieldEncloser
- && program.typeOracle.hasClinit(fieldEncloser)) {
+ if (program.typeOracle.checkClinit(enclosingType,
+ field.getEnclosingType())) {
// Therefore, we have side effects
return true;
}
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 855349c..8311ceb 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
@@ -98,7 +98,7 @@
JClassType cType = (JClassType) type;
if (qType instanceof JClassType) {
- return getOrCreate(subClassMap, cType).contains(qType);
+ return isSubClass(cType, (JClassType) qType);
} else if (qType instanceof JInterfaceType) {
return getOrCreate(couldImplementMap, cType).contains(qType);
}
@@ -148,15 +148,15 @@
JClassType cType = (JClassType) type;
if (qType instanceof JClassType) {
- return getOrCreate(superClassMap, cType).contains(qType);
+ return isSuperClass(cType, (JClassType) qType);
} else if (qType instanceof JInterfaceType) {
- return getOrCreate(implementsMap, cType).contains(qType);
+ return implementsInterface(cType, (JInterfaceType) qType);
}
} else if (type instanceof JInterfaceType) {
JInterfaceType iType = (JInterfaceType) type;
if (qType instanceof JInterfaceType) {
- return getOrCreate(superInterfaceMap, iType).contains(qType);
+ return extendsInterface(iType, (JInterfaceType) qType);
}
} else if (type instanceof JNullType) {
@@ -166,6 +166,33 @@
return false;
}
+ /**
+ * Returns <code>true</code> if a static field access of <code>toType</code>
+ * from within <code>fromType</code> should generate a clinit call. This
+ * will be true in cases where <code>toType</code> has a live clinit method
+ * which we cannot statically know has already run. We can statically know the
+ * clinit method has already run when:
+ * <ol>
+ * <li><code>fromType == toType</code></li>
+ * <li><code>toType</code> is a superclass of <code>fromType</code>
+ * (because <code>toType</code>'s clinit would have already run
+ * <code>fromType</code>'s clinit; see JLS 12.4)</li>
+ * </ol>
+ */
+ public boolean checkClinit(JReferenceType fromType, JReferenceType toType) {
+ if (fromType == toType) {
+ return false;
+ }
+ if (!hasClinit(toType)) {
+ return false;
+ }
+ if (fromType instanceof JClassType && toType instanceof JClassType
+ && isSuperClass((JClassType) fromType, (JClassType) toType)) {
+ return false;
+ }
+ return true;
+ }
+
public void computeAfterAST() {
recomputeClinits();
}
@@ -209,6 +236,13 @@
}
}
+ /**
+ * Returns true if qType is a superinterface of type, directly or indirectly.
+ */
+ public boolean extendsInterface(JInterfaceType type, JInterfaceType qType) {
+ return getOrCreate(superInterfaceMap, type).contains(qType);
+ }
+
public JMethod[] getAllVirtualOverrides(JMethod method) {
Set/* <JMethod> */results = new HashSet/* <JMethod> */();
Map/* <JClassType, Set<JMethod>> */overrideMap = getOrCreateMap(
@@ -228,9 +262,27 @@
}
public boolean hasClinit(JReferenceType type) {
+ if (hasDirectClinit(type)) {
+ return true;
+ }
+ if (type != null && type.extnds != null) {
+ return hasClinit(type.extnds);
+ }
+ return false;
+ }
+
+ public boolean hasDirectClinit(JReferenceType type) {
return hasClinitSet.contains(type);
}
+ /**
+ * Returns true if qType is an implemented interface of type, directly or
+ * indirectly.
+ */
+ public boolean implementsInterface(JClassType type, JInterfaceType qType) {
+ return getOrCreate(implementsMap, type).contains(qType);
+ }
+
public boolean isInstantiatedType(JReferenceType type) {
if (type instanceof JNullType) {
return true;
@@ -245,6 +297,20 @@
return instantiatedTypes.contains(type);
}
+ /**
+ * Returns true if qType is a subclass of type, directly or indirectly.
+ */
+ public boolean isSubClass(JClassType type, JClassType qType) {
+ return getOrCreate(subClassMap, type).contains(qType);
+ }
+
+ /**
+ * Returns true if qType is a superclass of type, directly or indirectly.
+ */
+ public boolean isSuperClass(JClassType type, JClassType qType) {
+ return getOrCreate(superClassMap, type).contains(qType);
+ }
+
public void recomputeClinits() {
hasClinitSet.clear();
for (int i = 0; i < program.getDeclaredTypes().size(); ++i) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index a5759b9..f0bc1f5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -886,14 +886,14 @@
JMethod method = (JMethod) typeMap.get(x.binding);
assert (type == method.getType());
- JExpression qualifier = dispProcessExpression(x.receiver);
+ JExpression qualifier;
if (x.receiver instanceof ThisReference) {
if (method.isStatic()) {
// don't bother qualifying it, it's a no-op
- // TODO(???): this may be handled by later optimizations now
qualifier = null;
} else if (x.receiver instanceof QualifiedThisReference) {
- // do nothing, the qualifier is correct
+ // use the supplied qualifier
+ qualifier = dispProcessExpression(x.receiver);
} else {
/*
* In cases where JDT had to synthesize a this ref for us, it could
@@ -902,6 +902,8 @@
*/
qualifier = createThisRef(info, method.getEnclosingType());
}
+ } else {
+ qualifier = dispProcessExpression(x.receiver);
}
JMethodCall call = new JMethodCall(program, info, qualifier, method);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 36ad3b3..66d473c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -526,8 +526,13 @@
List/* <JsFunction> */jsFuncs = popList(x.methods.size()); // methods
List/* <JsStatement> */jsFields = popList(x.fields.size()); // fields
- if (typeOracle.hasClinit(x)) {
- handleClinit((JsFunction) jsFuncs.get(0));
+ if (typeOracle.hasDirectClinit(x)) {
+ // see if there's a super class we need to chain to
+ JClassType superType = x.extnds;
+ while (superType != null && !typeOracle.hasDirectClinit(superType)) {
+ superType = superType.extnds;
+ }
+ handleClinit((JsFunction) jsFuncs.get(0), superType);
} else {
jsFuncs.set(0, null);
}
@@ -751,9 +756,9 @@
JsStatements globalStmts = jsProgram.getGlobalBlock().getStatements();
- if (typeOracle.hasClinit(x)) {
+ if (typeOracle.hasDirectClinit(x)) {
JsFunction clinitFunc = (JsFunction) jsFuncs.get(0);
- handleClinit(clinitFunc);
+ handleClinit(clinitFunc, null);
globalStmts.add(clinitFunc.makeStmt());
}
@@ -1467,11 +1472,19 @@
}
}
- private void handleClinit(JsFunction clinitFunc) {
+ private void handleClinit(JsFunction clinitFunc, JReferenceType chainTo) {
+ JsStatements statements = clinitFunc.getBody().getStatements();
// self-assign to the null method immediately (to prevent reentrancy)
JsExpression asg = createAssignment(clinitFunc.getName().makeRef(),
nullMethodName.makeRef());
- clinitFunc.getBody().getStatements().add(0, asg.makeStmt());
+ statements.add(0, asg.makeStmt());
+ if (chainTo != null) {
+ JMethod chainToMeth = (JMethod) chainTo.methods.get(0);
+ JsInvocation jsInvocation = new JsInvocation();
+ JsNameRef qualifier = getName(chainToMeth).makeRef();
+ jsInvocation.setQualifier(qualifier);
+ statements.add(1, jsInvocation.makeStmt());
+ }
}
private JsInvocation maybeCreateClinitCall(JField x) {
@@ -1480,12 +1493,14 @@
}
JReferenceType enclosingType = x.getEnclosingType();
- if (!typeOracle.hasClinit(enclosingType)) {
+ if (!typeOracle.checkClinit(currentMethod.getEnclosingType(), enclosingType)) {
return null;
}
- // don't need to clinit on my own static fields
- if (enclosingType == currentMethod.getEnclosingType()) {
- return null;
+
+ // The actual target class might not have a direct clinit; find the super
+ // class that does.
+ while (!typeOracle.hasDirectClinit(enclosingType)) {
+ enclosingType = enclosingType.extnds;
}
JMethod clinitMethod = (JMethod) enclosingType.methods.get(0);
JsInvocation jsInvocation = new JsInvocation();
@@ -1497,21 +1512,23 @@
if (!x.isStatic()) {
return null;
}
-
JReferenceType enclosingType = x.getEnclosingType();
if (!typeOracle.hasClinit(enclosingType)) {
return null;
}
-
+ if (program.isStaticImpl(x)) {
+ return null;
+ }
// avoid recursion sickness
if (x == enclosingType.methods.get(0)) {
return null;
}
- if (program.isStaticImpl(x)) {
- return null;
+ // The actual target class might not have a direct clinit; find the super
+ // class that does.
+ while (!typeOracle.hasDirectClinit(enclosingType)) {
+ enclosingType = enclosingType.extnds;
}
-
JMethod clinitMethod = (JMethod) enclosingType.methods.get(0);
JsInvocation jsInvocation = new JsInvocation();
jsInvocation.setQualifier(getName(clinitMethod).makeRef());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
index 51c68ff..71b4a7d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
@@ -204,20 +204,14 @@
private boolean checkClinitViolation(JMethodCall x,
JExpression resultExpression) {
JReferenceType targetEnclosingType = x.getTarget().getEnclosingType();
- if (!program.typeOracle.hasClinit(targetEnclosingType)) {
- // No clinit needed; target doesn't have one.
+ if (!program.typeOracle.checkClinit(currentMethod.getEnclosingType(), targetEnclosingType)) {
+ // Access from this class to the target class won't trigger a clinit
return false;
}
if (program.isStaticImpl(x.getTarget())) {
// No clinit needed; target is really an instance method.
return false;
}
- if (currentMethod.getEnclosingType() == targetEnclosingType) {
- // No clinit needed; intra-class call.
- // TODO: we could maybe broaden the test condition to include types that
- // we can statically determine must have been initialized.
- return false;
- }
/*
* Potential clinit violation! We can only allow this if the result is
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
index 3d6baab..1dde413 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
@@ -90,6 +90,16 @@
}
}
+ private static class SideEffectCauser6Super {
+ static {
+ CompilerTest.sideEffectChecker++;
+ }
+ }
+
+ private static class SideEffectCauser6 extends SideEffectCauser6Super {
+ public static String causeClinitSideEffectOnRead = "bar";
+ }
+
private static final class UninstantiableType {
public Object field;
@@ -185,8 +195,10 @@
assertEquals(3, sideEffectChecker);
String foo = SideEffectCauser4.causeClinitSideEffectOnRead;
assertEquals(4, sideEffectChecker);
- String bar = jsniReadSideEffectCauser5();
+ jsniReadSideEffectCauser5();
assertEquals(5, sideEffectChecker);
+ foo = SideEffectCauser6.causeClinitSideEffectOnRead;
+ assertEquals(6, sideEffectChecker);
String checkRescued = NonSideEffectCauser.NOT_A_COMPILE_TIME_CONSTANT;
assertEquals(null, checkRescued);
}